diff options
author | Robert Guo <robert.guo@mongodb.com> | 2021-08-01 23:14:07 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-08-04 11:54:43 +0000 |
commit | e74e88defab5d9081e4e3d81bd2457b04c32af48 (patch) | |
tree | 48e7ad8fecfe8c7e1810c0dffd9c88ac853764ae /buildscripts | |
parent | f0defdba5d5839e4cbfd3b0fa69fb8369419ded4 (diff) | |
download | mongo-e74e88defab5d9081e4e3d81bd2457b04c32af48.tar.gz |
SERVER-58126 Avoid symlinking downloaded binaries on Windows
Diffstat (limited to 'buildscripts')
11 files changed, 108 insertions, 44 deletions
diff --git a/buildscripts/resmokelib/config.py b/buildscripts/resmokelib/config.py index ca9e82e055c..9272fbf52b9 100644 --- a/buildscripts/resmokelib/config.py +++ b/buildscripts/resmokelib/config.py @@ -6,6 +6,8 @@ import itertools import os.path import time +import buildscripts.resmokelib.setup_multiversion.config as multiversion_config + # Subdirectory under the dbpath prefix that contains directories with data files of mongod's started # by resmoke.py. FIXTURE_SUBDIR = "resmoke" @@ -37,7 +39,10 @@ DEFAULT_DBPATH_PREFIX = os.path.normpath("/data/db") # Default directory that we expect to contain binaries for multiversion testing. This directory is # added to the PATH when calling programs.make_process(). -DEFAULT_MULTIVERSION_DIR = os.path.normpath("/data/multiversion") +DEFAULT_MULTIVERSION_DIRS = [os.path.normpath("/data/multiversion")] +if os.path.isfile(multiversion_config.WINDOWS_BIN_PATHS_FILE): + with open(multiversion_config.WINDOWS_BIN_PATHS_FILE) as wbpf: + DEFAULT_MULTIVERSION_DIRS.extend(wbpf.read().split(os.pathsep)) # Default location for the genny executable. Override this in the YAML suite configuration if # desired. diff --git a/buildscripts/resmokelib/core/programs.py b/buildscripts/resmokelib/core/programs.py index a2d6b3fd5fd..b4aaae953db 100644 --- a/buildscripts/resmokelib/core/programs.py +++ b/buildscripts/resmokelib/core/programs.py @@ -42,10 +42,7 @@ def make_process(*args, **kwargs): def get_path_env_var(env_vars): """Return the path base on provided environment variable.""" - path = [ - os.getcwd(), - config.DEFAULT_MULTIVERSION_DIR, - ] + path = [os.getcwd()] + config.DEFAULT_MULTIVERSION_DIRS # If installDir is provided, add it early to the path if config.INSTALL_DIR is not None: path.append(config.INSTALL_DIR) diff --git a/buildscripts/resmokelib/hang_analyzer/extractor.py b/buildscripts/resmokelib/hang_analyzer/extractor.py index 01e1fd54fb5..70574e59dcc 100644 --- a/buildscripts/resmokelib/hang_analyzer/extractor.py +++ b/buildscripts/resmokelib/hang_analyzer/extractor.py @@ -2,6 +2,7 @@ import glob import os +import platform import shutil import tarfile import time @@ -9,6 +10,8 @@ import time from buildscripts.resmokelib import config from buildscripts.resmokelib.run import compare_start_time from buildscripts.resmokelib.setup_multiversion.setup_multiversion import SetupMultiversion +from buildscripts.resmokelib.utils import is_windows +from buildscripts.resmokelib.utils.filesystem import build_hygienic_bin_path _DEBUG_FILE_BASE_NAMES = ['mongo', 'mongod', 'mongos'] @@ -65,7 +68,7 @@ def _extracted_files_to_copy(): out = [] for ext in ['debug', 'dSYM', 'pdb']: for file in _DEBUG_FILE_BASE_NAMES: - haystack = os.path.join('dist-test', 'bin', '{file}.{ext}'.format(file=file, ext=ext)) + haystack = build_hygienic_bin_path(child='{file}.{ext}'.format(file=file, ext=ext)) for needle in glob.glob(haystack): out.append((needle, os.path.join(os.getcwd(), os.path.basename(needle)))) return out @@ -77,11 +80,11 @@ def download_debug_symbols(root_logger, download_url): while True: try: + install_dir_list = [] SetupMultiversion.setup_mongodb(artifacts_url=None, binaries_url=None, - symbols_url=download_url, install_dir=os.getcwd()) - + symbols_url=download_url, install_dir=os.getcwd(), + install_dir_list=install_dir_list) break - except tarfile.ReadError: root_logger.info("Debug symbols unavailable after %s secs, retrying in %s secs", compare_start_time(time.time()), retry_secs) diff --git a/buildscripts/resmokelib/powercycle/powercycle.py b/buildscripts/resmokelib/powercycle/powercycle.py index c493ac7c22a..cdaa9125bd7 100755 --- a/buildscripts/resmokelib/powercycle/powercycle.py +++ b/buildscripts/resmokelib/powercycle/powercycle.py @@ -36,6 +36,7 @@ from buildscripts.resmokelib.powercycle import powercycle_config, powercycle_con # See https://docs.python.org/2/library/sys.html#sys.platform from buildscripts.resmokelib.powercycle.lib.services import WindowsService, PosixService +from buildscripts.resmokelib.utils.filesystem import build_hygienic_bin_path _IS_WINDOWS = sys.platform == "win32" or sys.platform == "cygwin" _IS_LINUX = sys.platform.startswith("linux") diff --git a/buildscripts/resmokelib/run/generate_multiversion_exclude_tags.py b/buildscripts/resmokelib/run/generate_multiversion_exclude_tags.py index e42d3c83aa6..b26159e3b65 100755 --- a/buildscripts/resmokelib/run/generate_multiversion_exclude_tags.py +++ b/buildscripts/resmokelib/run/generate_multiversion_exclude_tags.py @@ -2,11 +2,9 @@ import logging import os import re -import sys import tempfile from collections import defaultdict from subprocess import check_output -from sys import platform import requests @@ -14,6 +12,7 @@ from buildscripts.ciconfig import tags as _tags from buildscripts.resmokelib import multiversionconstants from buildscripts.resmokelib.config import MultiversionOptions from buildscripts.resmokelib.core.programs import get_path_env_var +from buildscripts.resmokelib.utils import is_windows from buildscripts.util.fileops import read_yaml_file BACKPORT_REQUIRED_TAG = "backport_required_multiversion" @@ -26,15 +25,16 @@ BACKPORTS_REQUIRED_BASE_URL = "https://raw.githubusercontent.com/mongodb/mongo" def get_backports_required_hash_for_shell_version(mongo_shell_path=None): """Parse the old shell binary to get the commit hash.""" - env_vars = {} - path = get_path_env_var(env_vars=env_vars) - env_vars["PATH"] = os.pathsep.join(path) - - if platform.startswith("win"): - shell_version = check_output([mongo_shell_path + ".exe", "--version"], - env=env_vars).decode('utf-8') - else: - shell_version = check_output([mongo_shell_path, "--version"], env=env_vars).decode('utf-8') + env_vars = os.environ.copy() + paths = get_path_env_var(env_vars=env_vars) + env_vars["PATH"] = os.pathsep.join(paths) + + mongo_shell = mongo_shell_path + if is_windows(): + mongo_shell = mongo_shell_path + ".exe" + + shell_version = check_output(f"{mongo_shell} --version", shell=True, + env=env_vars).decode('utf-8') for line in shell_version.splitlines(): if "gitVersion" in line: version_line = line.split(':')[1] diff --git a/buildscripts/resmokelib/setup_multiversion/config.py b/buildscripts/resmokelib/setup_multiversion/config.py index f2c63921ef3..e81fd445f58 100644 --- a/buildscripts/resmokelib/setup_multiversion/config.py +++ b/buildscripts/resmokelib/setup_multiversion/config.py @@ -3,6 +3,9 @@ from typing import List SETUP_MULTIVERSION_CONFIG = "buildscripts/resmokeconfig/setup_multiversion/setup_multiversion_config.yml" +# Records the paths of installed multiversion binaries on Windows. +WINDOWS_BIN_PATHS_FILE = "windows_binary_paths.txt" + class Buildvariant: """Class represents buildvariant in setup multiversion config.""" diff --git a/buildscripts/resmokelib/setup_multiversion/download.py b/buildscripts/resmokelib/setup_multiversion/download.py index fa145bf2a76..ec0c5e8833d 100644 --- a/buildscripts/resmokelib/setup_multiversion/download.py +++ b/buildscripts/resmokelib/setup_multiversion/download.py @@ -11,7 +11,7 @@ import zipfile import requests import structlog -from buildscripts.resmokelib.utils.filesystem import mkdtemp_in_build_dir +from buildscripts.resmokelib.utils.filesystem import mkdtemp_in_build_dir, build_hygienic_bin_path S3_BUCKET = "mciuploads" @@ -104,23 +104,38 @@ def extract_archive(archive_file, install_dir): return install_dir -def symlink_version(suffix, installed_dir, link_dir): - """Symlink the binaries in the 'installed_dir' to the 'link_dir'.""" +def mkdir_p(path): + """Python equivalent of `mkdir -p`.""" try: - os.makedirs(link_dir) + os.makedirs(path) except OSError as exc: - if exc.errno == errno.EEXIST and os.path.isdir(link_dir): + if exc.errno == errno.EEXIST and os.path.isdir(path): pass else: raise - hygienic_bin_dir = os.path.join(installed_dir, "dist-test", "bin") + +def symlink_version(suffix, installed_dir, link_dir=None): + """ + Symlink the binaries in the 'installed_dir' to the 'link_dir'. + + If `link_dir` is None, link to the physical executable's directory (`bin_dir`). + """ + hygienic_bin_dir = build_hygienic_bin_path(parent=installed_dir) if os.path.isdir(hygienic_bin_dir): bin_dir = hygienic_bin_dir else: bin_dir = installed_dir + if link_dir is None: + link_dir = bin_dir + else: + mkdir_p(link_dir) + for executable in os.listdir(bin_dir): + if executable.endswith(".dll"): + LOGGER.debug("Skipping linking DLL", file=executable) + continue executable_name, executable_extension = os.path.splitext(executable) if suffix: @@ -132,6 +147,7 @@ def symlink_version(suffix, installed_dir, link_dir): executable = os.path.join(bin_dir, executable) executable_link = os.path.join(link_dir, link_name) + link_method = os.symlink if os.name == "nt": # os.symlink is not supported on Windows, use a direct method instead. def symlink_ms(source, symlink_name): @@ -144,8 +160,8 @@ def symlink_version(suffix, installed_dir, link_dir): if csl(symlink_name, source.replace("/", "\\"), flags) == 0: raise ctypes.WinError() - os.symlink = symlink_ms - os.symlink(executable, executable_link) + link_method = symlink_ms + link_method(executable, executable_link) LOGGER.debug("Symlink created.", executable=executable, executable_link=executable_link) except OSError as exc: @@ -155,3 +171,4 @@ def symlink_version(suffix, installed_dir, link_dir): raise LOGGER.info("Symlinks for all executables are created in the directory.", link_dir=link_dir) + return link_dir diff --git a/buildscripts/resmokelib/setup_multiversion/setup_multiversion.py b/buildscripts/resmokelib/setup_multiversion/setup_multiversion.py index 2e24cbd40eb..6fa0116ae21 100644 --- a/buildscripts/resmokelib/setup_multiversion/setup_multiversion.py +++ b/buildscripts/resmokelib/setup_multiversion/setup_multiversion.py @@ -16,7 +16,7 @@ import yaml from buildscripts.resmokelib.plugin import PluginInterface, Subcommand from buildscripts.resmokelib.setup_multiversion import config, download, github_conn -from buildscripts.resmokelib.utils import evergreen_conn +from buildscripts.resmokelib.utils import evergreen_conn, is_windows SUBCOMMAND = "setup-multiversion" @@ -44,17 +44,17 @@ class SetupMultiversion(Subcommand): """Main class for the setup multiversion subcommand.""" # pylint: disable=too-many-instance-attributes - def __init__(self, download_options, install_dir="", link_dir="", platform=None, edition=None, - architecture=None, use_latest=None, versions=None, evergreen_config=None, - github_oauth_token=None, debug=None, ignore_failed_push=False): + def __init__(self, download_options, install_dir="", link_dir="", mv_platform=None, + edition=None, architecture=None, use_latest=None, versions=None, + evergreen_config=None, github_oauth_token=None, debug=None, + ignore_failed_push=False): """Initialize.""" setup_logging(debug) - cwd = os.getcwd() - self.install_dir = os.path.join(cwd, install_dir) - self.link_dir = os.path.join(cwd, link_dir) + self.install_dir = os.path.abspath(install_dir) + self.link_dir = os.path.abspath(link_dir) self.edition = edition.lower() if edition else None - self.platform = platform.lower() if platform else None + self.platform = mv_platform.lower() if mv_platform else None self.architecture = architecture.lower() if architecture else None self.use_latest = use_latest self.versions = versions @@ -72,6 +72,9 @@ class SetupMultiversion(Subcommand): raw_yaml = yaml.safe_load(file_handle) self.config = config.SetupMultiversionConfig(raw_yaml) + self._is_windows = is_windows() + self._windows_bin_install_dirs = [] + @staticmethod def _get_bin_suffix(version, evg_project_id): """Get the multiversion bin suffix from the evergreen project ID.""" @@ -108,7 +111,6 @@ class SetupMultiversion(Subcommand): install_dir = os.path.join(self.install_dir, version) self.download_and_extract_from_urls(urls, bin_suffix, install_dir) - except (github_conn.GithubConnError, evergreen_conn.EvergreenConnError, download.DownloadError) as ex: LOGGER.error(ex) @@ -130,7 +132,18 @@ class SetupMultiversion(Subcommand): download_symbols_url = urls.get(" mongo-debugsymbols.zip", None) self.setup_mongodb(artifacts_url, binaries_url, download_symbols_url, install_dir, - bin_suffix, self.link_dir) + bin_suffix, link_dir=self.link_dir, + install_dir_list=self._windows_bin_install_dirs) + + if self._is_windows: + self._write_windows_install_paths(self._windows_bin_install_dirs) + + @staticmethod + def _write_windows_install_paths(paths): + with open(config.WINDOWS_BIN_PATHS_FILE, "w") as out: + out.write(os.pathsep.join(paths)) + + LOGGER.info(f"Finished writing binary paths on Windows to {config.WINDOWS_BIN_PATHS_FILE}") def get_latest_urls(self, version): """Return latest urls.""" @@ -204,7 +217,7 @@ class SetupMultiversion(Subcommand): @staticmethod def setup_mongodb(artifacts_url, binaries_url, symbols_url, install_dir, bin_suffix=None, - link_dir=None): + link_dir=None, install_dir_list=None): # pylint: disable=too-many-arguments """Download, extract and symlink.""" @@ -225,7 +238,17 @@ class SetupMultiversion(Subcommand): try_download(url) if binaries_url is not None: - download.symlink_version(bin_suffix, install_dir, link_dir) + if not link_dir: + raise ValueError("link_dir must be specified if downloading binaries") + + if not is_windows(): + link_dir = download.symlink_version(bin_suffix, install_dir, link_dir) + else: + LOGGER.info( + "Linking to install_dir on Windows; executable have to live in different working" + " directories to avoid DLLs for different versions clobbering each other") + link_dir = download.symlink_version(bin_suffix, install_dir, None) + install_dir_list.append(link_dir) def get_buildvariant_name(self, major_minor_version): """Return buildvariant name. @@ -269,7 +292,7 @@ class SetupMultiversionPlugin(PluginInterface): da=args.download_artifacts) return SetupMultiversion(install_dir=args.install_dir, link_dir=args.link_dir, - platform=args.platform, edition=args.edition, + mv_platform=args.platform, edition=args.edition, architecture=args.architecture, use_latest=args.use_latest, versions=args.versions, download_options=download_options, evergreen_config=args.evergreen_config, diff --git a/buildscripts/resmokelib/symbolizer/__init__.py b/buildscripts/resmokelib/symbolizer/__init__.py index d12aa264bf5..54111cff5f6 100644 --- a/buildscripts/resmokelib/symbolizer/__init__.py +++ b/buildscripts/resmokelib/symbolizer/__init__.py @@ -12,6 +12,7 @@ from buildscripts import mongosymb from buildscripts.resmokelib.plugin import PluginInterface, Subcommand from buildscripts.resmokelib.setup_multiversion.setup_multiversion import SetupMultiversion, _DownloadOptions from buildscripts.resmokelib.utils import evergreen_conn +from buildscripts.resmokelib.utils.filesystem import build_hygienic_bin_path _HELP = """ Symbolize a backtrace JSON file given an Evergreen Task ID. @@ -151,8 +152,8 @@ class Symbolizer(Subcommand): raise ValueError(f"Must not specify path_to_executable, the original path that " f"generated the symbols will be used: {sym_search_path}") # TODO: support non-hygienic builds. - self.mongosym_args.path_to_executable = os.path.join(self.dest_dir, "dist-test", "bin", - self.bin_name) + self.mongosym_args.path_to_executable = build_hygienic_bin_path( + parent=self.dest_dir, child=self.bin_name) self.mongosym_args.src_dir_to_move = self.dest_dir diff --git a/buildscripts/resmokelib/utils/filesystem.py b/buildscripts/resmokelib/utils/filesystem.py index 0cc8decb1b8..945c8312201 100644 --- a/buildscripts/resmokelib/utils/filesystem.py +++ b/buildscripts/resmokelib/utils/filesystem.py @@ -23,3 +23,17 @@ def remove_if_exists(path): def is_yaml_file(filename): """Return true if 'filename' ends in .yml or .yaml, and false otherwise.""" return os.path.splitext(filename)[1] in (".yaml", ".yml") + + +def build_hygienic_bin_path(parent=None, child=None): + """Get the hygienic bin directory, optionally from `parent` and with `child`.""" + pjoin = os.path.join + res = pjoin("dist-test", "bin") + + if parent: + res = pjoin(parent, res) + + if child: + res = pjoin(res, child) + + return res diff --git a/buildscripts/tests/resmokelib/setup_multiversion/test_setup_multiversion.py b/buildscripts/tests/resmokelib/setup_multiversion/test_setup_multiversion.py index 24786de4ac7..9365238172c 100644 --- a/buildscripts/tests/resmokelib/setup_multiversion/test_setup_multiversion.py +++ b/buildscripts/tests/resmokelib/setup_multiversion/test_setup_multiversion.py @@ -43,7 +43,7 @@ class TestSetupMultiversionBase(unittest.TestCase): install_dir="install", link_dir="link", edition=edition, - platform=platform, + mv_platform=platform, architecture=architecture, use_latest=False, versions=["4.2.1"], |