예제 #1
0
def activate_devtoolset(devtoolset_number: int) -> None:
    devtoolset_enable_script = ('/opt/rh/devtoolset-%d/enable' %
                                devtoolset_number)
    log("Enabling devtoolset-%s by sourcing the script %s", devtoolset_number,
        devtoolset_enable_script)
    if not os.path.exists(devtoolset_enable_script):
        raise IOError("Devtoolset script does not exist: %s" %
                      devtoolset_enable_script)

    cmd_args = ['bash', '-c', '. "%s" && env' % devtoolset_enable_script]
    log("Running command: %s", cmd_args)
    devtoolset_env_str = subprocess.check_output(cmd_args).decode('utf-8')

    found_vars = set()
    for line in devtoolset_env_str.split("\n"):
        line = line.strip()
        if not line:
            continue
        k, v = line.split("=", 1)
        if k in DEVTOOLSET_ENV_VARS:
            log("Setting %s to: %s", k, v)
            os.environ[k] = v
            found_vars.add(k)
    missing_vars = set()
    for var_name in DEVTOOLSET_ENV_VARS:
        if var_name not in found_vars:
            log("Did not set env var %s for devtoolset-%d", var_name,
                devtoolset_number)
            if var_name not in DEVTOOLSET_ENV_VARS_OK_IF_UNSET:
                missing_vars.add(var_name)
    if missing_vars:
        raise IOError(
            "Invalid environment after running devtoolset script %s. Did not set vars: %s"
            % (devtoolset_enable_script, ', '.join(sorted(missing_vars))))
예제 #2
0
def activate_devtoolset(devtoolset_number: int) -> None:
    devtoolset_enable_script_candidates = [
        f'/opt/rh/{toolset_name_prefix}-{devtoolset_number}/enable'
        for toolset_name_prefix in DEVTOOLSET_DIR_NAMES
    ]
    existing_devtoolset_enable_scripts = [
        script_path for script_path in devtoolset_enable_script_candidates
        if os.path.exists(script_path)
    ]
    if len(existing_devtoolset_enable_scripts) != 1:
        fatal(
            f"Expected exactly one of the scripts to exist: {devtoolset_enable_script_candidates}. "
            f"Found that {len(existing_devtoolset_enable_scripts)} exist.")
    devtoolset_enable_script = existing_devtoolset_enable_scripts[0]

    log("Enabling devtoolset-%s by sourcing the script %s",
        devtoolset_number, devtoolset_enable_script)
    if not os.path.exists(devtoolset_enable_script):
        raise IOError("Devtoolset script does not exist: %s" % devtoolset_enable_script)

    echo_env_vars_str = '; '.join(
        ['echo %s=$%s' % (k, shlex.quote(k)) for k in DEVTOOLSET_ENV_VARS])
    cmd_args = ['bash', '-c', '. "%s" && ( %s )' % (devtoolset_enable_script, echo_env_vars_str)]
    log("Running command: %s", cmd_args)
    devtoolset_env_str = subprocess.check_output(cmd_args).decode('utf-8')

    found_vars = set()
    for line in devtoolset_env_str.split("\n"):
        line = line.strip()
        if not line:
            continue
        k, v = line.split("=", 1)
        if k in DEVTOOLSET_ENV_VARS:
            log("Setting %s to: %s", k, v)
            os.environ[k] = v
            found_vars.add(k)
    missing_vars = set()
    for var_name in DEVTOOLSET_ENV_VARS:
        if var_name not in found_vars:
            log("Did not set env var %s for devtoolset-%d", var_name, devtoolset_number)
            if var_name not in DEVTOOLSET_ENV_VARS_OK_IF_UNSET:
                missing_vars.add(var_name)
    if missing_vars:
        raise IOError(
            "Invalid environment after running devtoolset script %s. Did not set vars: %s" % (
                devtoolset_enable_script, ', '.join(sorted(missing_vars))
            ))
def parse_cmd_line_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(prog=sys.argv[0])
    parser.add_argument(
        '--build-type',
        default=None,
        type=str,
        choices=BUILD_TYPES,
        help='Build only specific part of thirdparty dependencies.')
    parser.add_argument(
        '--skip-sanitizers',
        action='store_true',
        help='Do not build ASAN and TSAN instrumented dependencies.')
    parser.add_argument('--clean',
                        action='store_true',
                        default=False,
                        help='Clean, but keep downloads.')
    parser.add_argument('--clean-downloads',
                        action='store_true',
                        default=False,
                        help='Clean, including downloads.')
    parser.add_argument('--add_checksum',
                        help='Compute and add unknown checksums to %s' %
                        CHECKSUM_FILE_NAME,
                        action='store_true')
    parser.add_argument('--skip', help='Dependencies to skip')

    parser.add_argument(
        '--single-compiler-type',
        type=str,
        choices=['gcc', 'clang'],
        default=None,
        help=
        'Produce a third-party dependencies build using only a single compiler. '
        'This also implies that we are not using Linuxbrew.')

    parser.add_argument(
        '--compiler-prefix',
        type=str,
        help=
        'The prefix directory for looking for compiler executables. We will look for '
        'compiler executable in the bin subdirectory of this directory.')

    parser.add_argument(
        '--compiler-suffix',
        type=str,
        default='',
        help=
        'Suffix to append to compiler executables, such as the version number, '
        'potentially prefixed with a dash, to obtain names such as gcc-8, g++-8, '
        'clang-10, or clang++-10.')

    parser.add_argument('--devtoolset',
                        type=int,
                        help='Specifies a CentOS devtoolset')

    parser.add_argument(
        '-j',
        '--make-parallelism',
        help='How many cores should the build use. This is passed to '
        'Make/Ninja child processes. This can also be specified using the '
        'YB_MAKE_PARALLELISM environment variable.',
        type=int)

    parser.add_argument('--use-ccache',
                        action='store_true',
                        help='Use ccache to speed up compilation')

    parser.add_argument(
        '--use-compiler-wrapper',
        action='store_true',
        help='Use a compiler wrapper script. Allows additional validation but '
        'makes the build slower.')

    parser.add_argument(
        '--llvm-version',
        default=None,
        help='Version (tag) to use for dependencies based on LLVM codebase')

    parser.add_argument(
        '--remote-build-server',
        help=
        'Build third-party dependencies remotely on this server. The default value is '
        'determined by YB_THIRDPARTY_REMOTE_BUILD_SERVER environment variable.',
        default=os.getenv('YB_THIRDPARTY_REMOTE_BUILD_SERVER'))

    parser.add_argument(
        '--remote-build-dir',
        help=
        'The directory on the remote server to build third-party dependencies in. The '
        'value is determined by the YB_THIRDPARTY_REMOTE_BUILD_DIR environment variable.',
        default=os.getenv('YB_THIRDPARTY_REMOTE_BUILD_DIR'))

    parser.add_argument(
        '--local',
        help=
        'Forces the local build even if --remote-... options are specified or the '
        'corresponding environment variables are set.',
        action='store_true')

    parser.add_argument(
        '--download-extract-only',
        help=
        'Only download and extract archives. Do not build any dependencies.',
        action='store_true')

    parser.add_argument(
        '--multi-build',
        action='store_true',
        help=
        'Build multiple configurations in parallel. Most other arguments will get ignored '
        'if this is specified. Configurations to build are taken from .circleci/config.yml. '
        'The set of configurations to build can be customized using the '
        '--multi-build-conf-name-pattern flag.')

    parser.add_argument(
        '--multi-build-conf-name-pattern',
        help=
        'Only build configurations matching this glob-style pattern, anchored on both ends. '
        'This implies --multi-build.')

    parser.add_argument('--license-report',
                        action='store_true',
                        help='Generate a license report.')

    parser.add_argument(
        '--toolchain',
        help='Automatically download, install and use the given toolchain',
        choices=TOOLCHAIN_TYPES)

    parser.add_argument('dependencies',
                        nargs=argparse.REMAINDER,
                        help='Dependencies to build.')

    args = parser.parse_args()

    # ---------------------------------------------------------------------------------------------
    # Validating arguments
    # ---------------------------------------------------------------------------------------------

    if args.dependencies and args.skip:
        raise ValueError(
            "--skip is not compatible with specifying a list of dependencies to build"
        )

    if is_mac():
        if args.single_compiler_type not in [None, 'clang']:
            raise ValueError(
                "--single-compiler-type=%s is not allowed on macOS" %
                args.single_compiler_type)
        args.single_compiler_type = 'clang'

    if args.local and (args.remote_build_server is not None
                       or args.remote_build_dir is not None):
        log("Forcing a local build")
        args.remote_build_server = None
        args.remote_build_dir = None

    if (args.remote_build_server is None) != (args.remote_build_dir is None):
        raise ValueError(
            '--remote-build-server and --remote-build-dir have to be specified or unspecified '
            'at the same time. Note that their default values are provided by corresponding '
            'environment variables, YB_THIRDPARTY_REMOTE_BUILD_SERVER and '
            'YB_THIRDPARTY_REMOTE_BUILD_DIR.')
    if args.remote_build_dir is not None:
        assert os.path.isabs(args.remote_build_dir), (
            'Remote build directory path must be an absolute path: %s' %
            args.remote_build_dir)

    is_remote_build = args.remote_build_server is not None

    if args.devtoolset is not None and not is_remote_build:
        if not is_centos():
            raise ValueError("--devtoolset can only be used on CentOS Linux")
        if args.single_compiler_type not in [None, 'gcc']:
            raise ValueError(
                "--devtoolset is not compatible with compiler type: %s" %
                args.single_compiler_type)
        args.single_compiler_type = 'gcc'

    if args.toolchain:
        if args.devtoolset:
            raise ValueError("--devtoolset and --toolchain are incompatible")

        if args.compiler_prefix:
            raise ValueError(
                "--compiler-prefix and --toolchain are incompatible")

        if args.compiler_suffix:
            raise ValueError(
                "--compiler-suffix and --toolchain are incompatible")

    if args.multi_build_conf_name_pattern:
        args.multi_build = True

    return args
 def run_remote_bash_script(bash_script: str) -> None:
     bash_script = bash_script.strip()
     log("Running script remotely: %s", bash_script)
     # TODO: why exactly do we need shlex.quote here?
     run_ssh_cmd(['bash', '-c', shlex.quote(bash_script)])
def build_remotely(remote_server: str, remote_build_code_path: str) -> None:
    assert remote_server is not None
    assert remote_build_code_path is not None
    assert remote_build_code_path.startswith('/')

    def run_ssh_cmd(ssh_args: List[str]) -> None:
        log_and_run_cmd(['ssh', remote_server] + ssh_args)

    def run_remote_bash_script(bash_script: str) -> None:
        bash_script = bash_script.strip()
        log("Running script remotely: %s", bash_script)
        # TODO: why exactly do we need shlex.quote here?
        run_ssh_cmd(['bash', '-c', shlex.quote(bash_script)])

    quoted_remote_path = shlex.quote(remote_build_code_path)

    # Ensure the remote directory exists. We are not attempting to create it if it does not.
    run_remote_bash_script('[[ -d %s ]]' % quoted_remote_path)

    with PushDir(YB_THIRDPARTY_DIR):
        local_branch_name = get_current_git_branch_name()

        local_git_remotes = subprocess.check_output(
            shlex.split('git remote -v')).decode('utf-8')

        remote_url = '%s:%s' % (remote_server, remote_build_code_path)
        preferred_remote_name = 'remote-build-%s' % remote_server
        remote_name = None
        for remote_line in local_git_remotes.split('\n'):
            remote_line = remote_line.strip()
            if not remote_line:
                continue
            remote_components = remote_line.split('\t')
            if remote_components[1].endswith(' (push)'):
                parsed_remote_url = remote_components[1][:-7].strip()
                if parsed_remote_url == remote_url:
                    remote_name = remote_components[0]
                    log("Found existing remote %s for %s", remote_name,
                        remote_url)
                    break
        if remote_name is None:
            log_and_run_cmd(
                ['git', 'remote', 'add', preferred_remote_name, remote_url])
            remote_name = preferred_remote_name

        log("Local branch name: %s, checking it out remotely",
            local_branch_name)
        run_remote_bash_script(f"""
            set -euo pipefail
            cd {quoted_remote_path}
            git reset --hard HEAD
            git clean -df
            git checkout master
        """)

        log_and_run_cmd([
            'git', 'push', '--force', remote_name,
            '%s:%s' % (local_branch_name, local_branch_name)
        ])

        run_remote_bash_script(
            'cd %s && git checkout %s' %
            (quoted_remote_path, shlex.quote(local_branch_name)))

        rsync_code_to('%s:%s' % (remote_server, remote_build_code_path))
        remote_bash_script = 'cd %s && ./build_thirdparty.sh %s' % (
            quoted_remote_path, shlex_join(sys.argv[1:]))

        run_remote_bash_script(remote_bash_script)
def parse_cmd_line_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(prog=sys.argv[0])
    parser.add_argument(
        '--build-type',
        default=None,
        type=str,
        choices=BUILD_TYPES,
        help='Build only specific part of thirdparty dependencies.')
    parser.add_argument(
        '--skip-sanitizers',
        action='store_true',
        help='Do not build ASAN and TSAN instrumented dependencies.')
    parser.add_argument('--clean',
                        action='store_true',
                        default=False,
                        help='Clean, but keep downloads.')
    parser.add_argument('--clean-downloads',
                        action='store_true',
                        default=False,
                        help='Clean, including downloads.')
    parser.add_argument('--add-checksum',
                        help='Compute and add unknown checksums to %s' %
                        CHECKSUM_FILE_NAME,
                        action='store_true')
    parser.add_argument('--skip', help='Dependencies to skip')

    parser.add_argument(
        '--single-compiler-type',
        type=str,
        choices=['gcc', 'clang'],
        default=None,
        help=
        'Produce a third-party dependencies build using only a single compiler. '
        'This also implies that we are not using Linuxbrew.')

    parser.add_argument(
        '--compiler-prefix',
        type=str,
        help=
        'The prefix directory for looking for compiler executables. We will look for '
        'compiler executable in the bin subdirectory of this directory.')

    parser.add_argument(
        '--compiler-suffix',
        type=str,
        default='',
        help=
        'Suffix to append to compiler executables, such as the version number, '
        'potentially prefixed with a dash, to obtain names such as gcc-8, g++-8, '
        'clang-10, or clang++-10.')

    parser.add_argument('--devtoolset',
                        type=int,
                        help='Specifies a CentOS devtoolset')

    parser.add_argument(
        '-j',
        '--make-parallelism',
        help='How many cores should the build use. This is passed to '
        'Make/Ninja child processes. This can also be specified using the '
        'YB_MAKE_PARALLELISM environment variable.',
        type=int)

    parser.add_argument('--use-ccache',
                        action='store_true',
                        help='Use ccache to speed up compilation')

    parser.add_argument(
        '--use-compiler-wrapper',
        action='store_true',
        help='Use a compiler wrapper script. Allows additional validation but '
        'makes the build slower.')

    parser.add_argument(
        '--llvm-version',
        default=None,
        help='Version (tag) to use for dependencies based on LLVM codebase')

    parser.add_argument(
        '--remote-build-server',
        help=
        'Build third-party dependencies remotely on this server. The default value is '
        'determined by YB_THIRDPARTY_REMOTE_BUILD_SERVER environment variable.',
        default=os.getenv('YB_THIRDPARTY_REMOTE_BUILD_SERVER'))

    parser.add_argument(
        '--remote-build-dir',
        help=
        'The directory on the remote server to build third-party dependencies in. The '
        'value is determined by the YB_THIRDPARTY_REMOTE_BUILD_DIR environment variable.',
        default=os.getenv('YB_THIRDPARTY_REMOTE_BUILD_DIR'))

    parser.add_argument(
        '--local',
        help=
        'Forces the local build even if --remote-... options are specified or the '
        'corresponding environment variables are set.',
        action='store_true')

    parser.add_argument(
        '--download-extract-only',
        help=
        'Only download and extract archives. Do not build any dependencies.',
        action='store_true')

    parser.add_argument('--license-report',
                        action='store_true',
                        help='Generate a license report.')

    parser.add_argument(
        '--toolchain',
        help='Automatically download, install and use the given toolchain',
        choices=TOOLCHAIN_TYPES)

    parser.add_argument('--create-package',
                        help='Create the package tarball',
                        action='store_true')

    parser.add_argument(
        '--upload-as-tag',
        help='Upload the package tarball as a GitHub release under this tag. '
        'Implies --create-package. Requires GITHUB_TOKEN to be set. If GITHUB_TOKEN is not '
        'set, this is a no-op (with success exit code).')

    parser.add_argument(
        '--expected-major-compiler-version',
        type=int,
        help='Expect the major version of the compiler to be as specified')

    parser.add_argument('--verbose',
                        help='Show verbose output',
                        action='store_true')

    parser.add_argument('dependencies',
                        nargs=argparse.REMAINDER,
                        help='Dependencies to build.')

    parser.add_argument(
        '--enforce_arch',
        help=
        'Ensure that we use the given architecture, such as arm64. Useful for macOS systems '
        'with Apple Silicon CPUs and Rosetta 2 installed that can switch between '
        'architectures.')

    parser.add_argument(
        '--force',
        help=
        'Build dependencies even though the system does not detect any changes compared '
        'to an earlier completed build.',
        action='store_true')

    parser.add_argument(
        '--delete-build-dir',
        help=
        "Delete each dependency's build directory to start each build from scratch. "
        "Note that this does not affect the corresponding source directory.",
        action='store_true')

    args = parser.parse_args()

    # ---------------------------------------------------------------------------------------------
    # Validating arguments
    # ---------------------------------------------------------------------------------------------

    if args.dependencies and args.skip:
        raise ValueError(
            "--skip is not compatible with specifying a list of dependencies to build"
        )

    if is_macos():
        if args.single_compiler_type not in [None, 'clang']:
            raise ValueError(
                "--single-compiler-type=%s is not allowed on macOS" %
                args.single_compiler_type)
        args.single_compiler_type = 'clang'

    if args.local and (args.remote_build_server is not None
                       or args.remote_build_dir is not None):
        log("Forcing a local build")
        args.remote_build_server = None
        args.remote_build_dir = None

    if (args.remote_build_server is None) != (args.remote_build_dir is None):
        raise ValueError(
            '--remote-build-server and --remote-build-dir have to be specified or unspecified '
            'at the same time. Note that their default values are provided by corresponding '
            'environment variables, YB_THIRDPARTY_REMOTE_BUILD_SERVER and '
            'YB_THIRDPARTY_REMOTE_BUILD_DIR.')
    if args.remote_build_dir is not None:
        assert os.path.isabs(args.remote_build_dir), (
            'Remote build directory path must be an absolute path: %s' %
            args.remote_build_dir)

    is_remote_build = args.remote_build_server is not None

    if args.devtoolset is not None and not is_remote_build:
        if not local_sys_conf().is_redhat_family():
            raise ValueError(
                "--devtoolset can only be used on Red Hat Enterprise Linux OS family"
            )
        if args.single_compiler_type not in [None, 'gcc']:
            raise ValueError(
                "--devtoolset is not compatible with compiler type: %s" %
                args.single_compiler_type)
        args.single_compiler_type = 'gcc'

    if args.toolchain:
        if args.devtoolset:
            raise ValueError("--devtoolset and --toolchain are incompatible")

        if args.compiler_prefix:
            raise ValueError(
                "--compiler-prefix and --toolchain are incompatible")

        if args.compiler_suffix:
            raise ValueError(
                "--compiler-suffix and --toolchain are incompatible")

    if args.enforce_arch and platform.machine() != args.enforce_arch:
        raise ValueError("Machine architecture is %s but we expect %s" %
                         (platform.machine(), args.enforce_arch))

    return args