diff options
| author | Stefan Boberg <[email protected]> | 2023-02-08 16:12:56 +0100 |
|---|---|---|
| committer | Stefan Boberg <[email protected]> | 2023-02-08 16:12:56 +0100 |
| commit | 1f348612e825ff4c4d8a0af31a27902bdf20ef98 (patch) | |
| tree | 19485578ffb645e981296191d06f9793ac336a41 /scripts | |
| parent | 0.2.3-pre1 (diff) | |
| download | zen-1f348612e825ff4c4d8a0af31a27902bdf20ef98.tar.xz zen-1f348612e825ff4c4d8a0af31a27902bdf20ef98.zip | |
removed some deprecated scripts
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/deploybuild.py | 124 | ||||
| -rw-r--r-- | scripts/generateprojects.bat | 1 | ||||
| -rw-r--r-- | scripts/installdeps.bat | 1 | ||||
| -rw-r--r-- | scripts/p4utils.py | 356 | ||||
| -rw-r--r-- | scripts/peafour.py | 187 | ||||
| -rw-r--r-- | scripts/testsetup.bat | 18 | ||||
| -rw-r--r-- | scripts/upload_syms.bat | 3 | ||||
| -rw-r--r-- | scripts/vswhere.py | 317 |
8 files changed, 0 insertions, 1007 deletions
diff --git a/scripts/deploybuild.py b/scripts/deploybuild.py deleted file mode 100644 index 37518fd0c..000000000 --- a/scripts/deploybuild.py +++ /dev/null @@ -1,124 +0,0 @@ -import argparse -import sys -import os -import fileinput -import colorama -import shutil -import vswhere -import subprocess -from peafour import P4 -from colorama import Fore, Back, Style - -# Jazzy helpers - -def jazz_print(tag, detail = ""): - print(f"{Fore.WHITE}{Style.BRIGHT}||> {tag}{Style.RESET_ALL} {detail}") - -def jazz_fail(tag, detail = ""): - print(f"{Fore.RED}{Style.BRIGHT}||> {tag}{Style.RESET_ALL} {detail}") - -def copy_file(src, dst): - print(f"{Fore.WHITE}{Style.BRIGHT}||> COPY {Style.RESET_ALL} {src} -> {Fore.GREEN}{Style.BRIGHT}{dst}") - shutil.copy(src, dst) - -colorama.init() - -origcwd = os.getcwd() - -# Parse and validate arguments - -parser = argparse.ArgumentParser(description='Deploy a zen build to an UE tree') -parser.add_argument("root", help="Path to an UE5 root directory") -parser.add_argument("--sentry", action="store_true", help="Whether to upload symobls to Sentry") -args = parser.parse_args() - -engineroot = args.root -upload_symbols = args.sentry - -if not os.path.isfile(os.path.join(engineroot, "RunUAT.bat")): - print(f"{Fore.RED}Not a valid UE5 engine root directory: '{engineroot}'") - print(Style.RESET_ALL) - exit(1) - -# Establish root of zen tree - -zenroot = __file__ - -while not os.path.exists(os.path.join(zenroot, "deploy_build.bat")): - zenroot = os.path.dirname(zenroot) - -jazz_print("Zen root:", zenroot) -# Build fresh binaries - -try: - subprocess.run(["xmake.exe", "config", "-p", "windows", "-a", "x64", "-m", "release"], check=True) - build_cmd = ["xmake.exe", "build", "--rebuild", "zenserver"] - build_output_dir = r'build\windows\x64\release' - - subprocess.run(build_cmd, check=True) -except: - jazz_fail("Build failed!") - exit(1) - -build_output_binary_path = os.path.join(zenroot, build_output_dir, "zenserver.exe") -build_output_binary_pdb_path = os.path.join(zenroot, build_output_dir, "zenserver.pdb") - -# Upload symbols etc to Sentry -if upload_symbols: - jazz_print("Uploading symbols", "to Sentry") - subprocess.run(["scripts\sentry-cli.exe", "upload-dif", "--org", "to", "--project", "zen-server", build_output_binary_path, build_output_binary_pdb_path]) - -# Change into root directory to pick up Perforce environment - -jazz_print(f"Determining P4 environment", f"for directory '{engineroot}'") - -os.chdir(engineroot) - -p4info = P4.info().run() - -if p4info is None: - jazz_fail("Unable to query P4 info", "do you have source control connectivity?") - exit(1) - -if not os.path.samefile(p4info.clientRoot, engineroot): - print(f"{Fore.RED}Could not find P4 client for UE5 engine root directory '{engineroot}'") - print(Style.RESET_ALL) - exit(1) - -# check out the binaries - -jazz_print("Reverting", "any previous unsubmitted deploy") - -try: - P4.revert("Engine/Binaries/Win64/zenserver.*").run() - P4.revert("Engine/Binaries/Win64/crashpad_handler.exe").run() -except Exception as e: - # it's not super important to report failure here, it's likely - # due to the user not actually having the file checked out (yet) - pass - -jazz_print("Checking out", "(at head) zenserver executables") - -try: - P4.edit("Engine/Binaries/Win64/zenserver.*").run() - P4.edit("Engine/Binaries/Win64/crashpad_handler.exe").run() -except Exception as e: - jazz_fail("edit failed", str(e)) - exit(1) - -target_bin_dir = os.path.join(engineroot, "Engine\\Binaries\\Win64") - -jazz_print("Placing zenserver", f"executables into tree at '{target_bin_dir}'") - -crashpadtarget = os.path.join(target_bin_dir, "crashpad_handler.exe") - -try: - copy_file(build_output_binary_path, os.path.join(target_bin_dir, "zenserver.exe")) - copy_file(build_output_binary_pdb_path, os.path.join(target_bin_dir, "zenserver.pdb")) - copy_file(os.path.join(zenroot, r'vcpkg_installed\x64-windows-static\tools\sentry-native\crashpad_handler.exe'), crashpadtarget) - P4.add(crashpadtarget).run() -except Exception as e: - print(f"Caught exception while copying: {e.args}") - exit(1) - -jazz_print("SUCCESS", "binaries ready for check-in") diff --git a/scripts/generateprojects.bat b/scripts/generateprojects.bat deleted file mode 100644 index cc6732aaa..000000000 --- a/scripts/generateprojects.bat +++ /dev/null @@ -1 +0,0 @@ -xmake project -k vsxmake2022 -y diff --git a/scripts/installdeps.bat b/scripts/installdeps.bat deleted file mode 100644 index f8ec42608..000000000 --- a/scripts/installdeps.bat +++ /dev/null @@ -1 +0,0 @@ -vcpkg --x-manifest-root=%~dp0.. --overlay-ports=%~dp0..\vcpkg_overlay-ports --triplet=x64-windows-static install
\ No newline at end of file diff --git a/scripts/p4utils.py b/scripts/p4utils.py deleted file mode 100644 index d2a9933e7..000000000 --- a/scripts/p4utils.py +++ /dev/null @@ -1,356 +0,0 @@ -import os -import re -import time -import flow.cmd -import threading -from peafour import P4 -import subprocess as sp - -#------------------------------------------------------------------------------- -p4_set_result = None -def get_p4_set(prop_name): - if ret := os.getenv(prop_name): - return ret - - global p4_set_result - if not p4_set_result: - p4_set_result = {} - try: proc = sp.Popen(("p4", "set", "-q"), stdout=sp.PIPE) - except: return - - for line in iter(proc.stdout.readline, b""): - try: key, value = line.split(b"=", 1) - except ValueError: continue - p4_set_result[key.decode()] = value.strip().decode() - - proc.wait() - proc.stdout.close() - - return p4_set_result.get(prop_name, None) - -#------------------------------------------------------------------------------- -def login(): - try: - detail = P4.login(s=True).run() - except P4.Error: - raise EnvironmentError("No valid Perforce session found. Run 'p4 login' to authenticate.") - return getattr(detail, "User", None) - -#------------------------------------------------------------------------------- -def get_p4config_name(): - return os.path.basename(get_p4_set("P4CONFIG") or ".p4config.txt") - -#------------------------------------------------------------------------------- -def has_p4config(start_dir): - from pathlib import Path - p4config = get_p4config_name() - for dir in (Path(start_dir) / "x").parents: - candidate = dir / p4config - if os.path.isfile(candidate): - return True, candidate - return False, p4config - -#------------------------------------------------------------------------------- -def create_p4config(p4config_path, client, username, port=None): - with open(p4config_path, "wt") as out: - print_args = { "sep" : "", "file" : out } - print("P4CLIENT=", client, **print_args) - print("P4USER=", username, **print_args) - if port: - print("P4PORT", port, **print_args) - -#------------------------------------------------------------------------------- -def ensure_p4config(start_dir=None): - start_dir = start_dir or os.getcwd() - found, p4config_name = has_p4config(start_dir) - if found: - return p4config_name, False - - username = login() - - # Get the client for 'start_dir' - client = get_client_from_dir(start_dir, username) - if not client: - return - client, root_dir = client - - # Now we know where to locate a p4config file - p4config_path = f"{root_dir}/{p4config_name}" - create_p4config(p4config_path, client, username) - return p4config_path, True - -#------------------------------------------------------------------------------- -def get_client_from_dir(root_dir, username): - import socket - host_name = socket.gethostname().lower() - - root_dir = os.path.normpath(root_dir).replace("\\", "/").lower() - - clients = (x for x in P4.clients(u=username) if x.Host.lower() == host_name) - for client in clients: - if client.Root.replace("\\", "/").lower() in root_dir: - client_host = client.Host.lower() - if not client_host or client_host == host_name: - return client.client, client.Root - -#------------------------------------------------------------------------------- -def get_branch_root(depot_path): - def fstat_paths(): - limit = 5 # ...two of which are always required - query_path = "//" - for piece in depot_path[2:].split("/")[:limit]: - query_path += piece + "/" - yield query_path + "GenerateProjectFiles.bat" - - print("Probing for well-known file:") - for x in fstat_paths(): - print(" ", x) - - fstat = P4.fstat(fstat_paths(), T="depotFile") - root_path = fstat.run(on_error=False) - if root_path: - return "/".join(root_path.depotFile.split("/")[:-1]) + "/" - - raise ValueError("Unable to establish branch root") - - - -#------------------------------------------------------------------------------- -class TempBranchSpec(object): - def __init__(self, use, username, from_path, to_path, ignore_streams=False): - import hashlib - id = hashlib.md5() - id.update(from_path.encode()) - id.update(to_path.encode()) - id = id.hexdigest()[:6] - self._name = f"{username}-ushell.{use}-{id}" - - # To map between streams we need to extract the internal branchspec that - # Perforce builds. If from/to aren't related streams it will fail so we - # fallback to a conventional trivial branchspec. - try: - if ignore_streams: - raise P4.Error("") - - branch = P4.branch(self._name, o=True, S=from_path[:-1], P=to_path[:-1]) - result = branch.run() - spec = result.as_dict() - except P4.Error: - spec = { - "Branch" : self._name, - "View0" : f"{from_path}... {to_path}...", - } - - P4.branch(i=True).run(input_data=spec, on_error=False) - - def __del__(self): - P4.branch(self._name, d=True).run() - - def __str__(self): - return self._name - - - -#------------------------------------------------------------------------------- -def _kb_string(value): - return format(value // 1024, ",") + "KB" - -#------------------------------------------------------------------------------- -class _SyncRota(object): - class _Worker(object): - def __init__(self, id): - self.id = id - self.work_items = [] - self.burden = 0 - self.done_size = 0 - self.done_items = 0 - self.error = False - - def __init__(self, changelist, worker_count): - self._workers = [_SyncRota._Worker(x) for x in range(worker_count)] - self.changelist = str(changelist) - - def add_work(self, item, rev, cost): - worker = min(self._workers, key=lambda x: x.burden) - worker.work_items.append((item, rev, cost)) - worker.burden += cost - - def sort(self): - direction = 1 - for worker in self._workers: - worker.work_items.sort(key=lambda x: x[2] * direction) - direction *= -1 - - def read_workers(self): - yield from (x for x in self._workers if x.work_items) - -#------------------------------------------------------------------------------- -class Syncer(object): - def __init__(self): - self._paths = [] - self._excluded_views = set() - - def _read_sync_specs(self, include_excluded=True): - cl_suffix = "@" + self._rota.changelist - cl_suffix = "#0" if cl_suffix == "@0" else cl_suffix - for depot_path in self._paths: - yield depot_path + cl_suffix - - if include_excluded: - # Using "@0" results in slow queries it seems - yield from (x + "#0" for x in self._excluded_views) - - def _is_excluded(self, path): - return next((True for x in self._re_excludes if x.match(path)), False) - - def _build_exclude_re(self): - re_excludes = [] - for view in self._excluded_views: - view = view.replace("...", "@") - view = view.replace(".", "\\.") - view = view.replace("*", "[^/]*") - view = view.replace("@", ".*") - re_excludes.append(view) - - if re_excludes: - # The expression isn't escaped so hopefully it's not complicated... - try: re_excludes = [re.compile(x, re.IGNORECASE) for x in re_excludes] - except: pass - - self._re_excludes = re_excludes - - def add_path(self, dir): - self._paths.append(dir) - - def add_exclude(self, view): - self._excluded_views.add(view) - - def schedule(self, changelist, worker_count=8): - self._build_exclude_re() - - # P4.<cmd> uses p4's Python-marshalled output (the -G option). However the - # "p4 sync -n" will report open files via a "info" message instead of a - # structured "stat" one. So we explicitly add open files to the rota. - def read_items(): - yield from P4.sync(self._read_sync_specs(), n=True).read(on_error=False) - yield from P4.opened().read(on_error=False) - - self._rota = _SyncRota(changelist, worker_count) - - # Fill the rota - total_size = 0 - count = 0 - for item in read_items(): - depot_path = item.depotFile - rev = int(item.rev) - - if self._is_excluded(depot_path): - if item.action != "deleted": - continue - rev = 0 - - if count % 17 == 0: - print("\r" + str(count), "files", f"({_kb_string(total_size)})", end="") - - size = int(getattr(item, "fileSize", 0)) # deletes have no size attr - self._rota.add_work(depot_path, rev, size) - - total_size += size - count += 1 - self._rota.sort() - print("\r" + str(count), "files", f"({_kb_string(total_size)})") - - def sync(self, *, dryrun=False, echo=False): - # Sum up what we have to do - total_burden = sum(x.burden for x in self._rota.read_workers()) - total_items = sum(len(x.work_items) for x in self._rota.read_workers()) - - print(f"Fetching {_kb_string(total_burden)} in {total_items} files") - - # Launch the worker threads - def sync_thread(worker): - def on_error(p4_error): - if "not enough space" in p4_error.data: - worker.error = "Out of disk space" - raise EOFError() - - try: - # Annecdotally path@cl appears to be the quickest. path#rev is - # appeared 15% slower, and with -L it was 30%. - def read_sync_items(): - cl_prefix = "@" + self._rota.changelist - for path, rev, size in worker.work_items: - yield path + (cl_prefix if rev else "#0") - - sync = P4(b=8192).sync(read_sync_items(), n=dryrun) - for item in sync.read(on_error=on_error): - if echo: - print(item.depotFile) - worker.done_size += int(getattr(item, "fileSize", 0)) + 0.01 - except EOFError: - pass - - def create_thread(worker): - thread = threading.Thread(target=sync_thread, args=(worker,)) - thread.start() - return thread - - threads = [create_thread(x) for x in self._rota.read_workers()] - print(f"Using {len(threads)} workers") - - # While there are active threads, print detail about their progress - total_burden += (0.01 * total_items) - while not echo: - threads = [x for x in threads if x.is_alive()] - if not threads: - break - - done_size = sum(x.done_size for x in self._rota.read_workers()) - progress = ((done_size * 1000) // total_burden) / 10 - print("\r%5.1f%%" % progress, _kb_string(int(done_size)), end=""); - time.sleep(0.3) - else: - for thread in threads: - thread.join() - print("\r...done ") - - # Check for errors from the workers - for worker in (x for x in self._rota.read_workers() if x.error): - print(flow.cmd.text.red("!!" + str(worker.error))) - return False - - # Nothing more to do if this is a dry run as the remaining tasks need a - # sync to operate on. - if dryrun: - return True - - # P4.sync() returns 'stat' type events but "p4 sync" will report files - # with a complex sync scenario only as unstructured 'info' messages. As the - # above won't know about these files we'll do a second sync to catch them. - global sync_errors - sync_errors = False - print("Finalising ", end="") - def read_depot_files(): - def on_error(data): - msg = data.data.strip() - if "up-to-date" in msg: return - if "not in client view" in msg: return - - print("\n", flow.cmd.text.red(msg), end="") - global sync_errors - sync_errors = True - - def on_info(data): - print("\n", flow.cmd.text.light_yellow(data.data), end="") - - sync = P4.sync(self._read_sync_specs(False), n=True) - for item in sync.read(on_error=on_error, on_info=on_info): - if not self._is_excluded(item.depotFile): - yield item.depotFile - - sync = P4.sync(read_depot_files(), q=True) - for i in sync.read(on_error=False): - pass - print() - - return not sync_errors diff --git a/scripts/peafour.py b/scripts/peafour.py deleted file mode 100644 index c9f559a21..000000000 --- a/scripts/peafour.py +++ /dev/null @@ -1,187 +0,0 @@ -import types -import marshal -import threading -import subprocess as sp - -#------------------------------------------------------------------------------- -class _P4Result(object): - def __init__(self, result): - super().__setattr__("_result", result) - - def __str__(self): return str(self._result) - def __contains__(self, key): return (key in self._result) or (key + "0" in self._result) - def as_dict(self): return {k.decode():v.decode() for k,v in self._result.items()} - def __setattr__(self, key, value): self._result[key.encode()] = str(value).encode() - - def __getattr__(self, key): - if (key + "0").encode() in self._result: - def as_list(): - index = 0; - while (key + str(index)).encode() in self._result: - indexed_key = (key + str(index)).encode() - yield self._result[indexed_key].decode() - index += 1 - return as_list() - - ret = self._result.get(str.encode(key)) - if ret == None: raise AttributeError(key) - return ret.decode(errors="replace") - -#------------------------------------------------------------------------------- -class _P4Command(object): - @staticmethod - def _read_args(*args, **kwargs): - for k,v in kwargs.items(): - if isinstance(v, bool): - if v: - yield "-" + k - elif v != None: - yield f"-{k}={v}" if len(k) > 1 else f"-{k}{v}" - - for arg in (x for x in args if isinstance(x, str)): - yield arg - - def __init__(self, **options): - opt_iter = _P4Command._read_args(**options) - self._command = ["p4", "-Qutf8", "-G"] - self._command += (x for x in opt_iter) - - def start(self, command, *args, **kwargs): - self._stdin_args = [] - for arg in (x for x in args if not isinstance(x, str)): - if not hasattr(arg, "__iter__"): - raise TypeError("P4 arguments can be only strings or sequences") - self._stdin_args.append(arg) - - self._proc = None - - arg_iter = _P4Command._read_args(*args, **kwargs) - - if self._stdin_args: - self._command.append("-x-") - self._command.append(command) - self._command += (x for x in arg_iter) - - def __del__(self): self._close_proc() - def __str__(self): return " ".join(self._command) - def __iter__(self): yield from self._iter() - def __getattr__(self, name): return getattr(self.run(), name) - - def run(self, **kwargs): - return next(self._iter(**kwargs), None) - - def read(self, **kwargs): - yield from self._iter(**kwargs) - - def _close_proc(self): - if self._proc: - if self._proc.stdin: - self._proc.stdin.close() - self._proc.stdout.close() - self._proc = None - - def _iter(self, input_data=None, on_error=True, on_info=None, on_text=None): - stdin = None - if input_data != None: - if self._stdin_args: - raise _P4.Error("It is unsupported to have both generator-type arguments and input data") - - if isinstance(input_data, dict): - input_data = {str(k).encode():str(v).encode() for k,v in input_data.items()} - else: - raise _P4.Error("Unsupported input data type; " + type(input_data).__name__) - stdin = sp.PIPE - - if self._stdin_args: - stdin = sp.PIPE - - proc = sp.Popen(self._command, stdout=sp.PIPE, stdin=stdin) - self._proc = proc - - if stdin: - def stdin_thread_entry(): - try: - if input_data: - marshal.dump(input_data, proc.stdin, 0) - for args in self._stdin_args: - for arg in args: - arg = str(arg).encode() + b"\n" - proc.stdin.write(arg) - except (BrokenPipeError, OSError): - pass - finally: - try: proc.stdin.close() - except: pass - - stdin_thread = threading.Thread(target=stdin_thread_entry) - stdin_thread.start() - - while True: - try: result = marshal.load(proc.stdout) - except EOFError: break - - code = result[b"code"] - del result[b"code"] - - if code == b"error": - if isinstance(on_error, bool): - if on_error: - raise _P4.Error(result[b"data"].decode()[:-1]) - continue - - try: - on_error(_P4Result(result)) - except: - proc.terminate() - raise - continue - - if code == b"stat": - yield _P4Result(result) - continue - - if code == b"text" and on_text: - try: - data = result.get(b"data", b"") - on_text(data) - continue - except: - proc.terminate() - raise - - if code == b"info" and on_info: - try: - on_info(_P4Result(result)) - continue - except: - proc.terminate() - raise - - if stdin: - stdin_thread.join() - self._close_proc() - -#------------------------------------------------------------------------------- -class _P4(object): - class Error(Exception): - def __init__(self, msg): - super().__init__("Perforce: " + msg) - - def __init__(self, **options): - self._options = options - - def __getattr__(self, command): - if command == "Error": - return _P4.Error - - def inner(*args, **kwargs): - instance = _P4Command(**self._options) - instance.start(command, *args, **kwargs) - return instance - - return inner - - def __call__(self, **options): - return _P4(**options) - -P4 = _P4() diff --git a/scripts/testsetup.bat b/scripts/testsetup.bat deleted file mode 100644 index 3adabca29..000000000 --- a/scripts/testsetup.bat +++ /dev/null @@ -1,18 +0,0 @@ -@echo off - -rem This script can be used to add the build output folder (Release) to the path for easier -rem ad hoc testing from the command line - -CALL :NORMALIZEPATH "%~dp0..\x64\Release" - -SET _BINPATH=%RETVAL% - -path|find /i "%_BINPATH%" >nul || set PATH=%PATH%;%_BINPATH% - -SET _BINPATH= - -EXIT /B - -:NORMALIZEPATH - SET RETVAL=%~f1 - EXIT /B diff --git a/scripts/upload_syms.bat b/scripts/upload_syms.bat deleted file mode 100644 index 663d64e06..000000000 --- a/scripts/upload_syms.bat +++ /dev/null @@ -1,3 +0,0 @@ -rem This is a temporary hack until everyone has access to Sentry - -scripts\sentry-cli upload-dif --org to --project zen-server \ue5-main\Engine\Binaries\Win64\zenserver.exe \ue5-main\engine\Binaries\Win64\zenserver.pdb diff --git a/scripts/vswhere.py b/scripts/vswhere.py deleted file mode 100644 index 82b0723f9..000000000 --- a/scripts/vswhere.py +++ /dev/null @@ -1,317 +0,0 @@ -r""" -Interface to Microsoft's Visual Studio locator tool, vswhere. - -If Visual Studio 15.2 or later has been installed, this will use the vswhere -binary installed with Visual Studio. Otherwise, it will download the latest -release of vswhere from https://github.com/Microsoft/vswhere the first time a -function is called. -""" - -import json -import os -import shutil -import subprocess - -__version__ = '1.3.0' -__author__ = 'Joel Spadin' -__license__ = 'MIT' - -LATEST_RELEASE_ENDPOINT = 'https://api.github.com/repos/Microsoft/vswhere/releases/latest' -DOWNLOAD_PATH = os.path.join(os.path.dirname(__file__), 'vswhere.exe') - -if 'ProgramFiles(x86)' in os.environ: - DEFAULT_PATH = os.path.join(os.environ['ProgramFiles(x86)'], 'Microsoft Visual Studio', 'Installer', 'vswhere.exe') -else: - DEFAULT_PATH = None - -alternate_path = None -download_mirror_url = None - - -def execute(args): - """ - Call vswhere with the given arguments and return an array of results. - - `args` is a list of command line arguments to pass to vswhere. - - If the argument list contains '-property', this returns an array with the - property value for each result. Otherwise, this returns an array of - dictionaries containing the results. - """ - is_property = '-property' in args - - args = [get_vswhere_path(), '-utf8'] + args - - if not is_property: - args.extend(['-format', 'json']) - - output = subprocess.check_output(args).decode('utf-8') - - if is_property: - return output.splitlines() - else: - return json.loads(output) - - -def find( - find=None, - find_all=False, - latest=False, - legacy=False, - path=None, - prerelease=False, - products=None, - prop=None, - requires=None, - requires_any=False, - sort=False, - version=None, -): - """ - Call vswhere and return an array of the results. - - Selection Options: - find_all: If True, finds all instances even if they are incomplete and - may not launch. - prerelease: If True, also searches prereleases. By default, only - releases are searched. - products: a product ID or list of one or more product IDs to find. - Defaults to Community, Professional, and Enterprise if not specified. - Specify '*' by itself to search all product instances installed. - See https://aka.ms/vs/workloads for a list of product IDs. - requires: a workload component ID or list of one or more IDs required - when finding instances. All specified IDs must be installed unless - `requires_any` is True. See https://aka.ms/vs/workloads for a list - of workload and component IDs. - requires_any: If True, find instances with any one or more workload or - component IDs passed to `requires`. - version: A version range for instances to find. Example: '[15.0,16.0)' - will find versions 15.*. - latest: If True, returns only the newest version and last installed. - legacy: If True, also searches Visual Studio 2015 and older products. - Information is limited. This option cannot be used with either - `products` or `requires`. - path: Gets the instance for the given file path. Not compatible with any - other selection option. - - Output Options: - sort: If True, sorts the instances from newest version and last installed - to oldest. When used with `find`, first instances are sorted, then - files are sorted lexigraphically. - prop: The name of a property to return instead of the full installation - details. Use delimiters '.', '/', or '_' to separate object and - property names. Example: 'properties.nickname' will return the - 'nickname' property under 'properties'. - find: Returns the file paths matching this glob pattern under the - installation path. The following patterns are supported: - ? Matches any one character except "\\" - * Matches zero or more characters except "\\" - ** Searches the current directory and subdirectories for the - remaining search pattern. - """ - args = [] - - if find: - args.append('-find') - args.append(find) - - if find_all: - args.append('-all') - - if latest: - args.append('-latest') - - if legacy: - args.append('-legacy') - - if path: - args.append('-path') - args.append(path) - - if prerelease: - args.append('-prerelease') - - if products: - args.append('-products') - _extend_or_append(args, products) - - if prop: - args.append('-property') - args.append(prop) - - if requires: - args.append('-requires') - _extend_or_append(args, requires) - - if requires_any: - args.append('-requiresAny') - - if sort: - args.append('-sort') - - if version: - args.append('-version') - args.append(version) - - return execute(args) - - -def find_first(**kwargs): - """ - Call vswhere and returns only the first result, or None if there are no results. - - See find() for keyword arguments. - """ - return next(iter(find(**kwargs)), None) - - -def get_latest(legacy=None, **kwargs): - """ - Get the information for the latest installed version of Visual Studio. - - Also supports the same selection options as find(), for example to select - different products. If the `legacy` argument is not set, it defaults to - `True` unless either `products` or `requires` arguments are set. - """ - legacy = _get_legacy_arg(legacy, **kwargs) - return find_first(latest=True, legacy=legacy, **kwargs) - - -def get_latest_path(legacy=None, **kwargs): - """ - Get the file path to the latest installed version of Visual Studio. - - Returns None if no installations could be found. - - Also supports the same selection options as find(), for example to select - different products. If the `legacy` argument is not set, it defaults to - `True` unless either `products` or `requires` arguments are set. - """ - legacy = _get_legacy_arg(legacy, **kwargs) - return find_first(latest=True, legacy=legacy, prop='installationPath', **kwargs) - - -def get_latest_version(legacy=None, **kwargs): - """ - Get the version string of the latest installed version of Visual Studio. - - For Visual Studio 2017 and newer, this is the full version number, for - example: '15.8.28010.2003'. - - For Visual Studio 2015 and older, this only contains the major version, with - the minor version set to 0, for example: '14.0'. - - Returns None if no installations could be found. - - Also supports the same selection options as find(), for example to select - different products. If the `legacy` argument is not set, it defaults to - `True` unless either `products` or `requires` arguments are set. - """ - legacy = _get_legacy_arg(legacy, **kwargs) - return find_first(latest=True, legacy=legacy, prop='installationVersion', **kwargs) - -def get_latest_major_version(**kwargs): - """ - Get the major version of the latest installed version of Visual Studio as an int. - - Returns 0 if no installations could be found. - - Also supports the same selection options as find(), for example to select - different products. If the `legacy` argument is not set, it defaults to - `True` unless either `products` or `requires` arguments are set. - """ - return int(next(iter(get_latest_version(**kwargs).split('.')), '0')) - - -def get_vswhere_path(): - """ - Get the path to vshwere.exe. - - If vswhere is not already installed as part of Visual Studio, and no - alternate path is given using `set_vswhere_path()`, the latest release will - be downloaded and stored alongside this script. - """ - if alternate_path and os.path.exists(alternate_path): - return alternate_path - - if DEFAULT_PATH and os.path.exists(DEFAULT_PATH): - return DEFAULT_PATH - - if os.path.exists(DOWNLOAD_PATH): - return DOWNLOAD_PATH - - _download_vswhere() - return DOWNLOAD_PATH - - -def set_vswhere_path(path): - """ - Set the path to vswhere.exe. - - If this is set, it overrides any version installed as part of Visual Studio. - """ - global alternate_path - alternate_path = path - - -def set_download_mirror(url): - """ - Set a URL from which vswhere.exe should be downloaded if it is not already - installed as part of Visual Studio and no alternate path is given using - `set_vswhere_path()`. - """ - global download_mirror_url - download_mirror_url = url - - -def _extend_or_append(lst, value): - if isinstance(value, str): - lst.append(value) - else: - lst.extend(value) - - -def _get_legacy_arg(legacy, **kwargs): - if legacy is None: - return 'products' not in kwargs and 'requires' not in kwargs - else: - return legacy - - -def _download_vswhere(): - """ - Download vswhere to DOWNLOAD_PATH. - """ - print('downloading from', _get_latest_release_url()) - try: - from urllib.request import urlopen - with urlopen(_get_latest_release_url()) as response, open(DOWNLOAD_PATH, 'wb') as outfile: - shutil.copyfileobj(response, outfile) - except ImportError: - # Python 2 - import urllib - urllib.urlretrieve(_get_latest_release_url(), DOWNLOAD_PATH) - - -def _get_latest_release_url(): - """ - The the URL of the latest release of vswhere. - """ - if download_mirror_url: - return download_mirror_url - - try: - from urllib.request import urlopen - with urlopen(LATEST_RELEASE_ENDPOINT) as response: - release = json.loads(response.read(), encoding=response.headers.get_content_charset() or 'utf-8') - except ImportError: - # Python 2 - import urllib2 - response = urllib2.urlopen(LATEST_RELEASE_ENDPOINT) - release = json.loads(response.read(), encoding=response.headers.getparam('charset') or 'utf-8') - - for asset in release['assets']: - if asset['name'] == 'vswhere.exe': - return asset['browser_download_url'] - - raise Exception('Could not locate the latest release of vswhere.')
\ No newline at end of file |