[0-9]+(?:\.[0-9]+)*) # release segment
+ (?P # pre-release
+ [-_\.]?
+ (?Palpha|a|beta|b|preview|pre|c|rc)
+ [-_\.]?
+ (?P[0-9]+)?
+ )?
+ (?P # post release
+ (?:-(?P[0-9]+))
+ |
+ (?:
+ [-_\.]?
+ (?Ppost|rev|r)
+ [-_\.]?
+ (?P[0-9]+)?
+ )
+ )?
+ (?P # dev release
+ [-_\.]?
+ (?Pdev)
+ [-_\.]?
+ (?P[0-9]+)?
+ )?
+ )
+ (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
+"""
+
+VERSION_PATTERN = _VERSION_PATTERN
+"""
+A string containing the regular expression used to match a valid version.
+
+The pattern is not anchored at either end, and is intended for embedding in larger
+expressions (for example, matching a version number as part of a file name). The
+regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
+flags set.
+
+:meta hide-value:
+"""
+
+
+class Version(_BaseVersion):
+ """This class abstracts handling of a project's versions.
+
+ A :class:`Version` instance is comparison aware and can be compared and
+ sorted using the standard Python interfaces.
+
+ >>> v1 = Version("1.0a5")
+ >>> v2 = Version("1.0")
+ >>> v1
+
+ >>> v2
+
+ >>> v1 < v2
+ True
+ >>> v1 == v2
+ False
+ >>> v1 > v2
+ False
+ >>> v1 >= v2
+ False
+ >>> v1 <= v2
+ True
+ """
+
+ _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
+ _key: CmpKey
+
+ def __init__(self, version: str) -> None:
+ """Initialize a Version object.
+
+ :param version:
+ The string representation of a version which will be parsed and normalized
+ before use.
+ :raises InvalidVersion:
+ If the ``version`` does not conform to PEP 440 in any way then this
+ exception will be raised.
+ """
+
+ # Validate the version and parse it into pieces
+ match = self._regex.search(version)
+ if not match:
+ raise InvalidVersion(f"Invalid version: '{version}'")
+
+ # Store the parsed out pieces of the version
+ self._version = _Version(
+ epoch=int(match.group("epoch")) if match.group("epoch") else 0,
+ release=tuple(int(i) for i in match.group("release").split(".")),
+ pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
+ post=_parse_letter_version(
+ match.group("post_l"), match.group("post_n1") or match.group("post_n2")
+ ),
+ dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
+ local=_parse_local_version(match.group("local")),
+ )
+
+ # Generate a key which will be used for sorting
+ self._key = _cmpkey(
+ self._version.epoch,
+ self._version.release,
+ self._version.pre,
+ self._version.post,
+ self._version.dev,
+ self._version.local,
+ )
+
+ def __repr__(self) -> str:
+ """A representation of the Version that shows all internal state.
+
+ >>> Version('1.0.0')
+
+ """
+ return f""
+
+ def __str__(self) -> str:
+ """A string representation of the version that can be rounded-tripped.
+
+ >>> str(Version("1.0a5"))
+ '1.0a5'
+ """
+ parts = []
+
+ # Epoch
+ if self.epoch != 0:
+ parts.append(f"{self.epoch}!")
+
+ # Release segment
+ parts.append(".".join(str(x) for x in self.release))
+
+ # Pre-release
+ if self.pre is not None:
+ parts.append("".join(str(x) for x in self.pre))
+
+ # Post-release
+ if self.post is not None:
+ parts.append(f".post{self.post}")
+
+ # Development release
+ if self.dev is not None:
+ parts.append(f".dev{self.dev}")
+
+ # Local version segment
+ if self.local is not None:
+ parts.append(f"+{self.local}")
+
+ return "".join(parts)
+
+ @property
+ def epoch(self) -> int:
+ """The epoch of the version.
+
+ >>> Version("2.0.0").epoch
+ 0
+ >>> Version("1!2.0.0").epoch
+ 1
+ """
+ return self._version.epoch
+
+ @property
+ def release(self) -> Tuple[int, ...]:
+ """The components of the "release" segment of the version.
+
+ >>> Version("1.2.3").release
+ (1, 2, 3)
+ >>> Version("2.0.0").release
+ (2, 0, 0)
+ >>> Version("1!2.0.0.post0").release
+ (2, 0, 0)
+
+ Includes trailing zeroes but not the epoch or any pre-release / development /
+ post-release suffixes.
+ """
+ return self._version.release
+
+ @property
+ def pre(self) -> Optional[Tuple[str, int]]:
+ """The pre-release segment of the version.
+
+ >>> print(Version("1.2.3").pre)
+ None
+ >>> Version("1.2.3a1").pre
+ ('a', 1)
+ >>> Version("1.2.3b1").pre
+ ('b', 1)
+ >>> Version("1.2.3rc1").pre
+ ('rc', 1)
+ """
+ return self._version.pre
+
+ @property
+ def post(self) -> Optional[int]:
+ """The post-release number of the version.
+
+ >>> print(Version("1.2.3").post)
+ None
+ >>> Version("1.2.3.post1").post
+ 1
+ """
+ return self._version.post[1] if self._version.post else None
+
+ @property
+ def dev(self) -> Optional[int]:
+ """The development number of the version.
+
+ >>> print(Version("1.2.3").dev)
+ None
+ >>> Version("1.2.3.dev1").dev
+ 1
+ """
+ return self._version.dev[1] if self._version.dev else None
+
+ @property
+ def local(self) -> Optional[str]:
+ """The local version segment of the version.
+
+ >>> print(Version("1.2.3").local)
+ None
+ >>> Version("1.2.3+abc").local
+ 'abc'
+ """
+ if self._version.local:
+ return ".".join(str(x) for x in self._version.local)
+ else:
+ return None
+
+ @property
+ def public(self) -> str:
+ """The public portion of the version.
+
+ >>> Version("1.2.3").public
+ '1.2.3'
+ >>> Version("1.2.3+abc").public
+ '1.2.3'
+ >>> Version("1.2.3+abc.dev1").public
+ '1.2.3'
+ """
+ return str(self).split("+", 1)[0]
+
+ @property
+ def base_version(self) -> str:
+ """The "base version" of the version.
+
+ >>> Version("1.2.3").base_version
+ '1.2.3'
+ >>> Version("1.2.3+abc").base_version
+ '1.2.3'
+ >>> Version("1!1.2.3+abc.dev1").base_version
+ '1!1.2.3'
+
+ The "base version" is the public version of the project without any pre or post
+ release markers.
+ """
+ parts = []
+
+ # Epoch
+ if self.epoch != 0:
+ parts.append(f"{self.epoch}!")
+
+ # Release segment
+ parts.append(".".join(str(x) for x in self.release))
+
+ return "".join(parts)
+
+ @property
+ def is_prerelease(self) -> bool:
+ """Whether this version is a pre-release.
+
+ >>> Version("1.2.3").is_prerelease
+ False
+ >>> Version("1.2.3a1").is_prerelease
+ True
+ >>> Version("1.2.3b1").is_prerelease
+ True
+ >>> Version("1.2.3rc1").is_prerelease
+ True
+ >>> Version("1.2.3dev1").is_prerelease
+ True
+ """
+ return self.dev is not None or self.pre is not None
+
+ @property
+ def is_postrelease(self) -> bool:
+ """Whether this version is a post-release.
+
+ >>> Version("1.2.3").is_postrelease
+ False
+ >>> Version("1.2.3.post1").is_postrelease
+ True
+ """
+ return self.post is not None
+
+ @property
+ def is_devrelease(self) -> bool:
+ """Whether this version is a development release.
+
+ >>> Version("1.2.3").is_devrelease
+ False
+ >>> Version("1.2.3.dev1").is_devrelease
+ True
+ """
+ return self.dev is not None
+
+ @property
+ def major(self) -> int:
+ """The first item of :attr:`release` or ``0`` if unavailable.
+
+ >>> Version("1.2.3").major
+ 1
+ """
+ return self.release[0] if len(self.release) >= 1 else 0
+
+ @property
+ def minor(self) -> int:
+ """The second item of :attr:`release` or ``0`` if unavailable.
+
+ >>> Version("1.2.3").minor
+ 2
+ >>> Version("1").minor
+ 0
+ """
+ return self.release[1] if len(self.release) >= 2 else 0
+
+ @property
+ def micro(self) -> int:
+ """The third item of :attr:`release` or ``0`` if unavailable.
+
+ >>> Version("1.2.3").micro
+ 3
+ >>> Version("1").micro
+ 0
+ """
+ return self.release[2] if len(self.release) >= 3 else 0
+
+
+def _parse_letter_version(
+ letter: Optional[str], number: Union[str, bytes, SupportsInt, None]
+) -> Optional[Tuple[str, int]]:
+
+ if letter:
+ # We consider there to be an implicit 0 in a pre-release if there is
+ # not a numeral associated with it.
+ if number is None:
+ number = 0
+
+ # We normalize any letters to their lower case form
+ letter = letter.lower()
+
+ # We consider some words to be alternate spellings of other words and
+ # in those cases we want to normalize the spellings to our preferred
+ # spelling.
+ if letter == "alpha":
+ letter = "a"
+ elif letter == "beta":
+ letter = "b"
+ elif letter in ["c", "pre", "preview"]:
+ letter = "rc"
+ elif letter in ["rev", "r"]:
+ letter = "post"
+
+ return letter, int(number)
+ if not letter and number:
+ # We assume if we are given a number, but we are not given a letter
+ # then this is using the implicit post release syntax (e.g. 1.0-1)
+ letter = "post"
+
+ return letter, int(number)
+
+ return None
+
+
+_local_version_separators = re.compile(r"[\._-]")
+
+
+def _parse_local_version(local: Optional[str]) -> Optional[LocalType]:
+ """
+ Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
+ """
+ if local is not None:
+ return tuple(
+ part.lower() if not part.isdigit() else int(part)
+ for part in _local_version_separators.split(local)
+ )
+ return None
+
+
+def _cmpkey(
+ epoch: int,
+ release: Tuple[int, ...],
+ pre: Optional[Tuple[str, int]],
+ post: Optional[Tuple[str, int]],
+ dev: Optional[Tuple[str, int]],
+ local: Optional[LocalType],
+) -> CmpKey:
+
+ # When we compare a release version, we want to compare it with all of the
+ # trailing zeros removed. So we'll use a reverse the list, drop all the now
+ # leading zeros until we come to something non zero, then take the rest
+ # re-reverse it back into the correct order and make it a tuple and use
+ # that for our sorting key.
+ _release = tuple(
+ reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
+ )
+
+ # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
+ # We'll do this by abusing the pre segment, but we _only_ want to do this
+ # if there is not a pre or a post segment. If we have one of those then
+ # the normal sorting rules will handle this case correctly.
+ if pre is None and post is None and dev is not None:
+ _pre: CmpPrePostDevType = NegativeInfinity
+ # Versions without a pre-release (except as noted above) should sort after
+ # those with one.
+ elif pre is None:
+ _pre = Infinity
+ else:
+ _pre = pre
+
+ # Versions without a post segment should sort before those with one.
+ if post is None:
+ _post: CmpPrePostDevType = NegativeInfinity
+
+ else:
+ _post = post
+
+ # Versions without a development segment should sort after those with one.
+ if dev is None:
+ _dev: CmpPrePostDevType = Infinity
+
+ else:
+ _dev = dev
+
+ if local is None:
+ # Versions without a local segment should sort before those with one.
+ _local: CmpLocalType = NegativeInfinity
+ else:
+ # Versions with a local segment need that segment parsed to implement
+ # the sorting rules in PEP440.
+ # - Alpha numeric segments sort before numeric segments
+ # - Alpha numeric segments sort lexicographically
+ # - Numeric segments sort numerically
+ # - Shorter versions sort before longer versions when the prefixes
+ # match exactly
+ _local = tuple(
+ (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
+ )
+
+ return epoch, _release, _pre, _post, _dev, _local
diff --git a/gyp/pyproject.toml b/gyp/pyproject.toml
new file mode 100644
index 0000000000..487cb75002
--- /dev/null
+++ b/gyp/pyproject.toml
@@ -0,0 +1,116 @@
+[build-system]
+requires = ["setuptools>=61.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "gyp-next"
+version = "0.22.2"
+authors = [
+ { name="Node.js contributors", email="ryzokuken@disroot.org" },
+]
+description = "A fork of the GYP build system for use in the Node.js projects"
+readme = "README.md"
+license = "BSD-3-Clause"
+license-files = ["LICENSE"]
+requires-python = ">=3.9"
+dependencies = ["packaging>=24.0", "setuptools>=77.0.3"]
+classifiers = [
+ "Development Status :: 3 - Alpha",
+ "Environment :: Console",
+ "Intended Audience :: Developers",
+ "Natural Language :: English",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
+]
+
+[project.optional-dependencies]
+dev = ["pytest", "ruff"]
+
+[project.scripts]
+gyp = "gyp:script_main"
+
+[project.urls]
+"Homepage" = "https://github.com/nodejs/gyp-next"
+
+[tool.ruff]
+extend-exclude = ["pylib/packaging"]
+line-length = 88
+
+[tool.ruff.lint]
+select = [
+ "C4", # flake8-comprehensions
+ "C90", # McCabe cyclomatic complexity
+ "DTZ", # flake8-datetimez
+ "E", # pycodestyle
+ "F", # Pyflakes
+ "G", # flake8-logging-format
+ "ICN", # flake8-import-conventions
+ "INT", # flake8-gettext
+ "PL", # Pylint
+ "PYI", # flake8-pyi
+ "RSE", # flake8-raise
+ "RUF", # Ruff-specific rules
+ "T10", # flake8-debugger
+ "TCH", # flake8-type-checking
+ "TID", # flake8-tidy-imports
+ "UP", # pyupgrade
+ "W", # pycodestyle
+ "YTT", # flake8-2020
+ # "A", # flake8-builtins
+ # "ANN", # flake8-annotations
+ # "ARG", # flake8-unused-arguments
+ # "B", # flake8-bugbear
+ # "BLE", # flake8-blind-except
+ # "COM", # flake8-commas
+ # "D", # pydocstyle
+ # "DJ", # flake8-django
+ # "EM", # flake8-errmsg
+ # "ERA", # eradicate
+ # "EXE", # flake8-executable
+ # "FBT", # flake8-boolean-trap
+ # "I", # isort
+ # "INP", # flake8-no-pep420
+ # "ISC", # flake8-implicit-str-concat
+ # "N", # pep8-naming
+ # "NPY", # NumPy-specific rules
+ # "PD", # pandas-vet
+ # "PGH", # pygrep-hooks
+ # "PIE", # flake8-pie
+ # "PT", # flake8-pytest-style
+ # "PTH", # flake8-use-pathlib
+ # "Q", # flake8-quotes
+ # "RET", # flake8-return
+ # "S", # flake8-bandit
+ # "SIM", # flake8-simplify
+ # "SLF", # flake8-self
+ # "T20", # flake8-print
+ # "TRY", # tryceratops
+]
+ignore = [
+ "PLR1714",
+ "PLW0603",
+ "PLW2901",
+ "RUF005",
+ "RUF012",
+ "UP031",
+]
+
+[tool.ruff.lint.mccabe]
+max-complexity = 101
+
+[tool.ruff.lint.pylint]
+allow-magic-value-types = ["float", "int", "str"]
+max-args = 11
+max-branches = 108
+max-returns = 10
+max-statements = 286
+
+[tool.setuptools]
+package-dir = {"" = "pylib"}
+packages = ["gyp", "gyp.generator"]
diff --git a/gyp/release-please-config.json b/gyp/release-please-config.json
new file mode 100644
index 0000000000..b6cad32a2d
--- /dev/null
+++ b/gyp/release-please-config.json
@@ -0,0 +1,11 @@
+{
+ "last-release-sha": "78756421b0d7bb335992a9c7d26ba3cc8b619708",
+ "packages": {
+ ".": {
+ "release-type": "python",
+ "package-name": "gyp-next",
+ "bump-minor-pre-major": true,
+ "include-component-in-tag": false
+ }
+ }
+}
diff --git a/gyp/samples/samples b/gyp/samples/samples
deleted file mode 100755
index 804b618998..0000000000
--- a/gyp/samples/samples
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/usr/bin/python
-
-# Copyright (c) 2009 Google Inc. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import os.path
-import shutil
-import sys
-
-
-gyps = [
- 'app/app.gyp',
- 'base/base.gyp',
- 'build/temp_gyp/googleurl.gyp',
- 'build/all.gyp',
- 'build/common.gypi',
- 'build/external_code.gypi',
- 'chrome/test/security_tests/security_tests.gyp',
- 'chrome/third_party/hunspell/hunspell.gyp',
- 'chrome/chrome.gyp',
- 'media/media.gyp',
- 'net/net.gyp',
- 'printing/printing.gyp',
- 'sdch/sdch.gyp',
- 'skia/skia.gyp',
- 'testing/gmock.gyp',
- 'testing/gtest.gyp',
- 'third_party/bzip2/bzip2.gyp',
- 'third_party/icu38/icu38.gyp',
- 'third_party/libevent/libevent.gyp',
- 'third_party/libjpeg/libjpeg.gyp',
- 'third_party/libpng/libpng.gyp',
- 'third_party/libxml/libxml.gyp',
- 'third_party/libxslt/libxslt.gyp',
- 'third_party/lzma_sdk/lzma_sdk.gyp',
- 'third_party/modp_b64/modp_b64.gyp',
- 'third_party/npapi/npapi.gyp',
- 'third_party/sqlite/sqlite.gyp',
- 'third_party/zlib/zlib.gyp',
- 'v8/tools/gyp/v8.gyp',
- 'webkit/activex_shim/activex_shim.gyp',
- 'webkit/activex_shim_dll/activex_shim_dll.gyp',
- 'webkit/build/action_csspropertynames.py',
- 'webkit/build/action_cssvaluekeywords.py',
- 'webkit/build/action_jsconfig.py',
- 'webkit/build/action_makenames.py',
- 'webkit/build/action_maketokenizer.py',
- 'webkit/build/action_useragentstylesheets.py',
- 'webkit/build/rule_binding.py',
- 'webkit/build/rule_bison.py',
- 'webkit/build/rule_gperf.py',
- 'webkit/tools/test_shell/test_shell.gyp',
- 'webkit/webkit.gyp',
-]
-
-
-def Main(argv):
- if len(argv) != 3 or argv[1] not in ['push', 'pull']:
- print 'Usage: %s push/pull PATH_TO_CHROME' % argv[0]
- return 1
-
- path_to_chrome = argv[2]
-
- for g in gyps:
- chrome_file = os.path.join(path_to_chrome, g)
- local_file = os.path.join(os.path.dirname(argv[0]), os.path.split(g)[1])
- if argv[1] == 'push':
- print 'Copying %s to %s' % (local_file, chrome_file)
- shutil.copyfile(local_file, chrome_file)
- elif argv[1] == 'pull':
- print 'Copying %s to %s' % (chrome_file, local_file)
- shutil.copyfile(chrome_file, local_file)
- else:
- assert False
-
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(Main(sys.argv))
diff --git a/gyp/samples/samples.bat b/gyp/samples/samples.bat
deleted file mode 100644
index 778d9c90f0..0000000000
--- a/gyp/samples/samples.bat
+++ /dev/null
@@ -1,5 +0,0 @@
-@rem Copyright (c) 2009 Google Inc. All rights reserved.
-@rem Use of this source code is governed by a BSD-style license that can be
-@rem found in the LICENSE file.
-
-@python %~dp0/samples %*
diff --git a/gyp/setup.py b/gyp/setup.py
deleted file mode 100755
index 75a42558d8..0000000000
--- a/gyp/setup.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (c) 2009 Google Inc. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-from setuptools import setup
-
-setup(
- name='gyp',
- version='0.1',
- description='Generate Your Projects',
- author='Chromium Authors',
- author_email='chromium-dev@googlegroups.com',
- url='http://code.google.com/p/gyp',
- package_dir = {'': 'pylib'},
- packages=['gyp', 'gyp.generator'],
- entry_points = {'console_scripts': ['gyp=gyp:script_main'] }
-)
diff --git a/gyp/test_gyp.py b/gyp/test_gyp.py
new file mode 100755
index 0000000000..70c81ae8ca
--- /dev/null
+++ b/gyp/test_gyp.py
@@ -0,0 +1,260 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""gyptest.py -- test runner for GYP tests."""
+
+import argparse
+import os
+import platform
+import subprocess
+import sys
+import time
+
+
+def is_test_name(f):
+ return f.startswith("gyptest") and f.endswith(".py")
+
+
+def find_all_gyptest_files(directory):
+ result = []
+ for root, dirs, files in os.walk(directory):
+ result.extend([os.path.join(root, f) for f in files if is_test_name(f)])
+ result.sort()
+ return result
+
+
+def main(argv=None):
+ if argv is None:
+ argv = sys.argv
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-a", "--all", action="store_true", help="run all tests")
+ parser.add_argument("-C", "--chdir", action="store", help="change to directory")
+ parser.add_argument(
+ "-f",
+ "--format",
+ action="store",
+ default="",
+ help="run tests with the specified formats",
+ )
+ parser.add_argument(
+ "-G",
+ "--gyp_option",
+ action="append",
+ default=[],
+ help="Add -G options to the gyp command line",
+ )
+ parser.add_argument(
+ "-l", "--list", action="store_true", help="list available tests and exit"
+ )
+ parser.add_argument(
+ "-n",
+ "--no-exec",
+ action="store_true",
+ help="no execute, just print the command line",
+ )
+ parser.add_argument(
+ "--path", action="append", default=[], help="additional $PATH directory"
+ )
+ parser.add_argument(
+ "-q",
+ "--quiet",
+ action="store_true",
+ help="quiet, don't print anything unless there are failures",
+ )
+ parser.add_argument(
+ "-v",
+ "--verbose",
+ action="store_true",
+ help="print configuration info and test results.",
+ )
+ parser.add_argument("tests", nargs="*")
+ args = parser.parse_args(argv[1:])
+
+ if args.chdir:
+ os.chdir(args.chdir)
+
+ if args.path:
+ extra_path = [os.path.abspath(p) for p in args.path]
+ extra_path = os.pathsep.join(extra_path)
+ os.environ["PATH"] = extra_path + os.pathsep + os.environ["PATH"]
+
+ if not args.tests:
+ if not args.all:
+ sys.stderr.write("Specify -a to get all tests.\n")
+ return 1
+ args.tests = ["test"]
+
+ tests = []
+ for arg in args.tests:
+ if os.path.isdir(arg):
+ tests.extend(find_all_gyptest_files(os.path.normpath(arg)))
+ else:
+ if not is_test_name(os.path.basename(arg)):
+ print(arg, "is not a valid gyp test name.", file=sys.stderr)
+ sys.exit(1)
+ tests.append(arg)
+
+ if args.list:
+ for test in tests:
+ print(test)
+ sys.exit(0)
+
+ os.environ["PYTHONPATH"] = os.path.abspath("test/lib")
+
+ if args.verbose:
+ print_configuration_info()
+
+ if args.gyp_option and not args.quiet:
+ print("Extra Gyp options: %s\n" % args.gyp_option)
+
+ if args.format:
+ format_list = args.format.split(",")
+ else:
+ format_list = {
+ "aix5": ["make"],
+ "os400": ["make"],
+ "freebsd7": ["make"],
+ "freebsd8": ["make"],
+ "openbsd5": ["make"],
+ "cygwin": ["msvs"],
+ "win32": ["msvs", "ninja"],
+ "linux": ["make", "ninja"],
+ "linux2": ["make", "ninja"],
+ "linux3": ["make", "ninja"],
+ # TODO: Re-enable xcode-ninja.
+ # https://bugs.chromium.org/p/gyp/issues/detail?id=530
+ # 'darwin': ['make', 'ninja', 'xcode', 'xcode-ninja'],
+ "darwin": ["make", "ninja", "xcode"],
+ }[sys.platform]
+
+ gyp_options = []
+ for option in args.gyp_option:
+ gyp_options += ["-G", option]
+
+ runner = Runner(format_list, tests, gyp_options, args.verbose)
+ runner.run()
+
+ if not args.quiet:
+ runner.print_results()
+
+ return 1 if runner.failures else 0
+
+
+def print_configuration_info():
+ print("Test configuration:")
+ if sys.platform == "darwin":
+ sys.path.append(os.path.abspath("test/lib"))
+ import TestMac # noqa: PLC0415
+
+ print(f" Mac {platform.mac_ver()[0]} {platform.mac_ver()[2]}")
+ print(f" Xcode {TestMac.Xcode.Version()}")
+ elif sys.platform == "win32":
+ sys.path.append(os.path.abspath("pylib"))
+ import gyp.MSVSVersion # noqa: PLC0415
+
+ print(" Win %s %s\n" % platform.win32_ver()[0:2])
+ print(" MSVS %s" % gyp.MSVSVersion.SelectVisualStudioVersion().Description())
+ elif sys.platform in ("linux", "linux2"):
+ print(" Linux %s" % " ".join(platform.linux_distribution()))
+ print(f" Python {platform.python_version()}")
+ print(f" PYTHONPATH={os.environ['PYTHONPATH']}")
+ print()
+
+
+class Runner:
+ def __init__(self, formats, tests, gyp_options, verbose):
+ self.formats = formats
+ self.tests = tests
+ self.verbose = verbose
+ self.gyp_options = gyp_options
+ self.failures = []
+ self.num_tests = len(formats) * len(tests)
+ num_digits = len(str(self.num_tests))
+ self.fmt_str = "[%%%dd/%%%dd] (%%s) %%s" % (num_digits, num_digits)
+ self.isatty = sys.stdout.isatty() and not self.verbose
+ self.env = os.environ.copy()
+ self.hpos = 0
+
+ def run(self):
+ run_start = time.time()
+
+ i = 1
+ for fmt in self.formats:
+ for test in self.tests:
+ self.run_test(test, fmt, i)
+ i += 1
+
+ if self.isatty:
+ self.erase_current_line()
+
+ self.took = time.time() - run_start
+
+ def run_test(self, test, fmt, i):
+ if self.isatty:
+ self.erase_current_line()
+
+ msg = self.fmt_str % (i, self.num_tests, fmt, test)
+ self.print_(msg)
+
+ start = time.time()
+ cmd = [sys.executable, test] + self.gyp_options
+ self.env["TESTGYP_FORMAT"] = fmt
+ proc = subprocess.Popen(
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=self.env
+ )
+ proc.wait()
+ took = time.time() - start
+
+ stdout = proc.stdout.read().decode("utf8")
+ if proc.returncode == 2:
+ res = "skipped"
+ elif proc.returncode:
+ res = "failed"
+ self.failures.append(f"({test}) {fmt}")
+ else:
+ res = "passed"
+ res_msg = f" {res} {took:.3f}s"
+ self.print_(res_msg)
+
+ if stdout and not stdout.endswith(("PASSED\n", "NO RESULT\n")):
+ print()
+ print("\n".join(f" {line}" for line in stdout.splitlines()))
+ elif not self.isatty:
+ print()
+
+ def print_(self, msg):
+ print(msg, end="")
+ index = msg.rfind("\n")
+ if index == -1:
+ self.hpos += len(msg)
+ else:
+ self.hpos = len(msg) - index
+ sys.stdout.flush()
+
+ def erase_current_line(self):
+ print("\b" * self.hpos + " " * self.hpos + "\b" * self.hpos, end="")
+ sys.stdout.flush()
+ self.hpos = 0
+
+ def print_results(self):
+ num_failures = len(self.failures)
+ if num_failures:
+ print()
+ if num_failures == 1:
+ print("Failed the following test:")
+ else:
+ print("Failed the following %d tests:" % num_failures)
+ print("\t" + "\n\t".join(sorted(self.failures)))
+ print()
+ print(
+ "Ran %d tests in %.3fs, %d failed."
+ % (self.num_tests, self.took, num_failures)
+ )
+ print()
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/gyp/tools/README b/gyp/tools/README
index 712e4efbb7..84a73d1521 100644
--- a/gyp/tools/README
+++ b/gyp/tools/README
@@ -5,7 +5,7 @@ pretty_vcproj:
For example, if I want to diff the base.vcproj project:
- pretty_vcproj.py z:\dev\src-chrome\src\base\build\base.vcproj "$(SolutionDir)=z:\dev\src-chrome\src\chrome\\" "$(CHROMIUM_BUILD)=" "$(CHROME_BUILD_TYPE)=" > orignal.txt
+ pretty_vcproj.py z:\dev\src-chrome\src\base\build\base.vcproj "$(SolutionDir)=z:\dev\src-chrome\src\chrome\\" "$(CHROMIUM_BUILD)=" "$(CHROME_BUILD_TYPE)=" > original.txt
pretty_vcproj.py z:\dev\src-chrome\src\base\base_gyp.vcproj "$(SolutionDir)=z:\dev\src-chrome\src\chrome\\" "$(CHROMIUM_BUILD)=" "$(CHROME_BUILD_TYPE)=" > gyp.txt
And you can use your favorite diff tool to see the changes.
diff --git a/gyp/tools/emacs/gyp-tests.el b/gyp/tools/emacs/gyp-tests.el
index 11b8497886..07afc58a93 100644
--- a/gyp/tools/emacs/gyp-tests.el
+++ b/gyp/tools/emacs/gyp-tests.el
@@ -30,7 +30,7 @@
"For the purposes of face comparison, we're not interested in the
differences between certain faces. For example, the difference between
font-lock-comment-delimiter and font-lock-comment-face."
- (case face
+ (cl-case face
((font-lock-comment-delimiter-face) font-lock-comment-face)
(t face)))
diff --git a/gyp/tools/emacs/gyp.el b/gyp/tools/emacs/gyp.el
index b98b155ced..042ff3a925 100644
--- a/gyp/tools/emacs/gyp.el
+++ b/gyp/tools/emacs/gyp.el
@@ -213,7 +213,7 @@
string-start)
(setq string-start (gyp-parse-to limit))
(if string-start
- (setq group (case (gyp-section-at-point)
+ (setq group (cl-case (gyp-section-at-point)
('dependencies 1)
('variables 2)
('conditions 2)
diff --git a/gyp/tools/graphviz.py b/gyp/tools/graphviz.py
index 326ae221cf..65f78011b5 100755
--- a/gyp/tools/graphviz.py
+++ b/gyp/tools/graphviz.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright (c) 2011 Google Inc. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
@@ -14,87 +14,86 @@
def ParseTarget(target):
- target, _, suffix = target.partition('#')
- filename, _, target = target.partition(':')
- return filename, target, suffix
+ target, _, suffix = target.partition("#")
+ filename, _, target = target.partition(":")
+ return filename, target, suffix
def LoadEdges(filename, targets):
- """Load the edges map from the dump file, and filter it to only
- show targets in |targets| and their depedendents."""
+ """Load the edges map from the dump file, and filter it to only
+ show targets in |targets| and their depedendents."""
- file = open('dump.json')
- edges = json.load(file)
- file.close()
+ file = open("dump.json")
+ edges = json.load(file)
+ file.close()
- # Copy out only the edges we're interested in from the full edge list.
- target_edges = {}
- to_visit = targets[:]
- while to_visit:
- src = to_visit.pop()
- if src in target_edges:
- continue
- target_edges[src] = edges[src]
- to_visit.extend(edges[src])
+ # Copy out only the edges we're interested in from the full edge list.
+ target_edges = {}
+ to_visit = targets[:]
+ while to_visit:
+ src = to_visit.pop()
+ if src in target_edges:
+ continue
+ target_edges[src] = edges[src]
+ to_visit.extend(edges[src])
- return target_edges
+ return target_edges
def WriteGraph(edges):
- """Print a graphviz graph to stdout.
- |edges| is a map of target to a list of other targets it depends on."""
-
- # Bucket targets by file.
- files = collections.defaultdict(list)
- for src, dst in edges.items():
- build_file, target_name, toolset = ParseTarget(src)
- files[build_file].append(src)
-
- print 'digraph D {'
- print ' fontsize=8' # Used by subgraphs.
- print ' node [fontsize=8]'
-
- # Output nodes by file. We must first write out each node within
- # its file grouping before writing out any edges that may refer
- # to those nodes.
- for filename, targets in files.items():
- if len(targets) == 1:
- # If there's only one node for this file, simplify
- # the display by making it a box without an internal node.
- target = targets[0]
- build_file, target_name, toolset = ParseTarget(target)
- print ' "%s" [shape=box, label="%s\\n%s"]' % (target, filename,
- target_name)
- else:
- # Group multiple nodes together in a subgraph.
- print ' subgraph "cluster_%s" {' % filename
- print ' label = "%s"' % filename
- for target in targets:
- build_file, target_name, toolset = ParseTarget(target)
- print ' "%s" [label="%s"]' % (target, target_name)
- print ' }'
-
- # Now that we've placed all the nodes within subgraphs, output all
- # the edges between nodes.
- for src, dsts in edges.items():
- for dst in dsts:
- print ' "%s" -> "%s"' % (src, dst)
-
- print '}'
+ """Print a graphviz graph to stdout.
+ |edges| is a map of target to a list of other targets it depends on."""
+
+ # Bucket targets by file.
+ files = collections.defaultdict(list)
+ for src, dst in edges.items():
+ build_file, target_name, _toolset = ParseTarget(src)
+ files[build_file].append(src)
+
+ print("digraph D {")
+ print(" fontsize=8") # Used by subgraphs.
+ print(" node [fontsize=8]")
+
+ # Output nodes by file. We must first write out each node within
+ # its file grouping before writing out any edges that may refer
+ # to those nodes.
+ for filename, targets in files.items():
+ if len(targets) == 1:
+ # If there's only one node for this file, simplify
+ # the display by making it a box without an internal node.
+ target = targets[0]
+ build_file, target_name, _toolset = ParseTarget(target)
+ print(f' "{target}" [shape=box, label="{filename}\\n{target_name}"]')
+ else:
+ # Group multiple nodes together in a subgraph.
+ print(' subgraph "cluster_%s" {' % filename)
+ print(' label = "%s"' % filename)
+ for target in targets:
+ build_file, target_name, _toolset = ParseTarget(target)
+ print(f' "{target}" [label="{target_name}"]')
+ print(" }")
+
+ # Now that we've placed all the nodes within subgraphs, output all
+ # the edges between nodes.
+ for src, dsts in edges.items():
+ for dst in dsts:
+ print(f' "{src}" -> "{dst}"')
+
+ print("}")
def main():
- if len(sys.argv) < 2:
- print >>sys.stderr, __doc__
- print >>sys.stderr
- print >>sys.stderr, 'usage: %s target1 target2...' % (sys.argv[0])
- return 1
+ if len(sys.argv) < 2:
+ print(__doc__, file=sys.stderr)
+ print(file=sys.stderr)
+ print("usage: %s target1 target2..." % (sys.argv[0]), file=sys.stderr)
+ return 1
- edges = LoadEdges('dump.json', sys.argv[1:])
+ edges = LoadEdges("dump.json", sys.argv[1:])
- WriteGraph(edges)
- return 0
+ WriteGraph(edges)
+ return 0
-if __name__ == '__main__':
- sys.exit(main())
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/gyp/tools/pretty_gyp.py b/gyp/tools/pretty_gyp.py
index c51d35872c..562a73ee67 100755
--- a/gyp/tools/pretty_gyp.py
+++ b/gyp/tools/pretty_gyp.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright (c) 2012 Google Inc. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
@@ -6,150 +6,149 @@
"""Pretty-prints the contents of a GYP file."""
-import sys
import re
-
+import sys
# Regex to remove comments when we're counting braces.
-COMMENT_RE = re.compile(r'\s*#.*')
+COMMENT_RE = re.compile(r"\s*#.*")
# Regex to remove quoted strings when we're counting braces.
# It takes into account quoted quotes, and makes sure that the quotes match.
# NOTE: It does not handle quotes that span more than one line, or
-# cases where an escaped quote is preceeded by an escaped backslash.
+# cases where an escaped quote is preceded by an escaped backslash.
QUOTE_RE_STR = r'(?P[\'"])(.*?)(? 0:
- after = True
-
- # This catches the special case of a closing brace having something
- # other than just whitespace ahead of it -- we don't want to
- # unindent that until after this line is printed so it stays with
- # the previous indentation level.
- if cnt < 0 and closing_prefix_re.match(stripline):
- after = True
- return (cnt, after)
+ """keeps track of the number of braces on a given line and returns the result.
+
+ It starts at zero and subtracts for closed braces, and adds for open braces.
+ """
+ open_braces = ["[", "(", "{"]
+ close_braces = ["]", ")", "}"]
+ closing_prefix_re = re.compile(r"[^\s\]\}\)]\s*[\]\}\)]+,?\s*$")
+ cnt = 0
+ stripline = COMMENT_RE.sub(r"", line)
+ stripline = QUOTE_RE.sub(r"''", stripline)
+ for char in stripline:
+ for brace in open_braces:
+ if char == brace:
+ cnt += 1
+ for brace in close_braces:
+ if char == brace:
+ cnt -= 1
+
+ after = False
+ if cnt > 0:
+ after = True
+
+ # This catches the special case of a closing brace having something
+ # other than just whitespace ahead of it -- we don't want to
+ # unindent that until after this line is printed so it stays with
+ # the previous indentation level.
+ if cnt < 0 and closing_prefix_re.match(stripline):
+ after = True
+ return (cnt, after)
def prettyprint_input(lines):
- """Does the main work of indenting the input based on the brace counts."""
- indent = 0
- basic_offset = 2
- last_line = ""
- for line in lines:
- if COMMENT_RE.match(line):
- print line
- else:
- line = line.strip('\r\n\t ') # Otherwise doesn't strip \r on Unix.
- if len(line) > 0:
- (brace_diff, after) = count_braces(line)
- if brace_diff != 0:
- if after:
- print " " * (basic_offset * indent) + line
- indent += brace_diff
- else:
- indent += brace_diff
- print " " * (basic_offset * indent) + line
+ """Does the main work of indenting the input based on the brace counts."""
+ indent = 0
+ basic_offset = 2
+ for line in lines:
+ if COMMENT_RE.match(line):
+ print(line)
else:
- print " " * (basic_offset * indent) + line
- else:
- print ""
- last_line = line
+ line = line.strip("\r\n\t ") # Otherwise doesn't strip \r on Unix.
+ if len(line) > 0:
+ (brace_diff, after) = count_braces(line)
+ if brace_diff != 0:
+ if after:
+ print(" " * (basic_offset * indent) + line)
+ indent += brace_diff
+ else:
+ indent += brace_diff
+ print(" " * (basic_offset * indent) + line)
+ else:
+ print(" " * (basic_offset * indent) + line)
+ else:
+ print()
def main():
- if len(sys.argv) > 1:
- data = open(sys.argv[1]).read().splitlines()
- else:
- data = sys.stdin.read().splitlines()
- # Split up the double braces.
- lines = split_double_braces(data)
+ if len(sys.argv) > 1:
+ data = open(sys.argv[1]).read().splitlines()
+ else:
+ data = sys.stdin.read().splitlines()
+ # Split up the double braces.
+ lines = split_double_braces(data)
- # Indent and print the output.
- prettyprint_input(lines)
- return 0
+ # Indent and print the output.
+ prettyprint_input(lines)
+ return 0
-if __name__ == '__main__':
- sys.exit(main())
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/gyp/tools/pretty_sln.py b/gyp/tools/pretty_sln.py
index ca8cf4ad3f..70c91aefad 100755
--- a/gyp/tools/pretty_sln.py
+++ b/gyp/tools/pretty_sln.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright (c) 2012 Google Inc. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
@@ -6,164 +6,175 @@
"""Prints the information in a sln file in a diffable way.
- It first outputs each projects in alphabetical order with their
- dependencies.
+It first outputs each projects in alphabetical order with their
+dependencies.
- Then it outputs a possible build order.
+Then it outputs a possible build order.
"""
-__author__ = 'nsylvain (Nicolas Sylvain)'
-
import os
import re
import sys
+
import pretty_vcproj
+__author__ = "nsylvain (Nicolas Sylvain)"
+
+
def BuildProject(project, built, projects, deps):
- # if all dependencies are done, we can build it, otherwise we try to build the
- # dependency.
- # This is not infinite-recursion proof.
- for dep in deps[project]:
- if dep not in built:
- BuildProject(dep, built, projects, deps)
- print project
- built.append(project)
+ # if all dependencies are done, we can build it, otherwise we try to build the
+ # dependency.
+ # This is not infinite-recursion proof.
+ for dep in deps[project]:
+ if dep not in built:
+ BuildProject(dep, built, projects, deps)
+ print(project)
+ built.append(project)
+
def ParseSolution(solution_file):
- # All projects, their clsid and paths.
- projects = dict()
-
- # A list of dependencies associated with a project.
- dependencies = dict()
-
- # Regular expressions that matches the SLN format.
- # The first line of a project definition.
- begin_project = re.compile(r'^Project\("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942'
- r'}"\) = "(.*)", "(.*)", "(.*)"$')
- # The last line of a project definition.
- end_project = re.compile('^EndProject$')
- # The first line of a dependency list.
- begin_dep = re.compile(
- r'ProjectSection\(ProjectDependencies\) = postProject$')
- # The last line of a dependency list.
- end_dep = re.compile('EndProjectSection$')
- # A line describing a dependency.
- dep_line = re.compile(' *({.*}) = ({.*})$')
-
- in_deps = False
- solution = open(solution_file)
- for line in solution:
- results = begin_project.search(line)
- if results:
- # Hack to remove icu because the diff is too different.
- if results.group(1).find('icu') != -1:
- continue
- # We remove "_gyp" from the names because it helps to diff them.
- current_project = results.group(1).replace('_gyp', '')
- projects[current_project] = [results.group(2).replace('_gyp', ''),
- results.group(3),
- results.group(2)]
- dependencies[current_project] = []
- continue
-
- results = end_project.search(line)
- if results:
- current_project = None
- continue
-
- results = begin_dep.search(line)
- if results:
- in_deps = True
- continue
-
- results = end_dep.search(line)
- if results:
- in_deps = False
- continue
-
- results = dep_line.search(line)
- if results and in_deps and current_project:
- dependencies[current_project].append(results.group(1))
- continue
-
- # Change all dependencies clsid to name instead.
- for project in dependencies:
- # For each dependencies in this project
- new_dep_array = []
- for dep in dependencies[project]:
- # Look for the project name matching this cldis
- for project_info in projects:
- if projects[project_info][1] == dep:
- new_dep_array.append(project_info)
- dependencies[project] = sorted(new_dep_array)
-
- return (projects, dependencies)
+ # All projects, their clsid and paths.
+ projects = {}
+
+ # A list of dependencies associated with a project.
+ dependencies = {}
+
+ # Regular expressions that matches the SLN format.
+ # The first line of a project definition.
+ begin_project = re.compile(
+ r'^Project\("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942'
+ r'}"\) = "(.*)", "(.*)", "(.*)"$'
+ )
+ # The last line of a project definition.
+ end_project = re.compile("^EndProject$")
+ # The first line of a dependency list.
+ begin_dep = re.compile(r"ProjectSection\(ProjectDependencies\) = postProject$")
+ # The last line of a dependency list.
+ end_dep = re.compile("EndProjectSection$")
+ # A line describing a dependency.
+ dep_line = re.compile(" *({.*}) = ({.*})$")
+
+ in_deps = False
+ solution = open(solution_file)
+ for line in solution:
+ results = begin_project.search(line)
+ if results:
+ # Hack to remove icu because the diff is too different.
+ if results.group(1).find("icu") != -1:
+ continue
+ # We remove "_gyp" from the names because it helps to diff them.
+ current_project = results.group(1).replace("_gyp", "")
+ projects[current_project] = [
+ results.group(2).replace("_gyp", ""),
+ results.group(3),
+ results.group(2),
+ ]
+ dependencies[current_project] = []
+ continue
+
+ results = end_project.search(line)
+ if results:
+ current_project = None
+ continue
+
+ results = begin_dep.search(line)
+ if results:
+ in_deps = True
+ continue
+
+ results = end_dep.search(line)
+ if results:
+ in_deps = False
+ continue
+
+ results = dep_line.search(line)
+ if results and in_deps and current_project:
+ dependencies[current_project].append(results.group(1))
+ continue
+
+ # Change all dependencies clsid to name instead.
+ for project, deps in dependencies.items():
+ # For each dependencies in this project
+ new_dep_array = []
+ for dep in deps:
+ # Look for the project name matching this cldis
+ for project_info in projects:
+ if projects[project_info][1] == dep:
+ new_dep_array.append(project_info)
+ dependencies[project] = sorted(new_dep_array)
+
+ return (projects, dependencies)
+
def PrintDependencies(projects, deps):
- print "---------------------------------------"
- print "Dependencies for all projects"
- print "---------------------------------------"
- print "-- --"
+ print("---------------------------------------")
+ print("Dependencies for all projects")
+ print("---------------------------------------")
+ print("-- --")
- for (project, dep_list) in sorted(deps.items()):
- print "Project : %s" % project
- print "Path : %s" % projects[project][0]
- if dep_list:
- for dep in dep_list:
- print " - %s" % dep
- print ""
+ for project, dep_list in sorted(deps.items()):
+ print("Project : %s" % project)
+ print("Path : %s" % projects[project][0])
+ if dep_list:
+ for dep in dep_list:
+ print(" - %s" % dep)
+ print()
+
+ print("-- --")
- print "-- --"
def PrintBuildOrder(projects, deps):
- print "---------------------------------------"
- print "Build order "
- print "---------------------------------------"
- print "-- --"
+ print("---------------------------------------")
+ print("Build order ")
+ print("---------------------------------------")
+ print("-- --")
+
+ built = []
+ for project, _ in sorted(deps.items()):
+ if project not in built:
+ BuildProject(project, built, projects, deps)
- built = []
- for (project, _) in sorted(deps.items()):
- if project not in built:
- BuildProject(project, built, projects, deps)
+ print("-- --")
- print "-- --"
def PrintVCProj(projects):
+ for project in projects:
+ print("-------------------------------------")
+ print("-------------------------------------")
+ print(project)
+ print(project)
+ print(project)
+ print("-------------------------------------")
+ print("-------------------------------------")
+
+ project_path = os.path.abspath(
+ os.path.join(os.path.dirname(sys.argv[1]), projects[project][2])
+ )
+
+ pretty = pretty_vcproj
+ argv = [
+ "",
+ project_path,
+ "$(SolutionDir)=%s\\" % os.path.dirname(sys.argv[1]),
+ ]
+ argv.extend(sys.argv[3:])
+ pretty.main(argv)
- for project in projects:
- print "-------------------------------------"
- print "-------------------------------------"
- print project
- print project
- print project
- print "-------------------------------------"
- print "-------------------------------------"
-
- project_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[1]),
- projects[project][2]))
-
- pretty = pretty_vcproj
- argv = [ '',
- project_path,
- '$(SolutionDir)=%s\\' % os.path.dirname(sys.argv[1]),
- ]
- argv.extend(sys.argv[3:])
- pretty.main(argv)
def main():
- # check if we have exactly 1 parameter.
- if len(sys.argv) < 2:
- print 'Usage: %s "c:\\path\\to\\project.sln"' % sys.argv[0]
- return 1
+ # check if we have exactly 1 parameter.
+ if len(sys.argv) < 2:
+ print('Usage: %s "c:\\path\\to\\project.sln"' % sys.argv[0])
+ return 1
- (projects, deps) = ParseSolution(sys.argv[1])
- PrintDependencies(projects, deps)
- PrintBuildOrder(projects, deps)
+ (projects, deps) = ParseSolution(sys.argv[1])
+ PrintDependencies(projects, deps)
+ PrintBuildOrder(projects, deps)
- if '--recursive' in sys.argv:
- PrintVCProj(projects)
- return 0
+ if "--recursive" in sys.argv:
+ PrintVCProj(projects)
+ return 0
-if __name__ == '__main__':
- sys.exit(main())
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/gyp/tools/pretty_vcproj.py b/gyp/tools/pretty_vcproj.py
index 6099bd7cc4..82d47a0bdd 100755
--- a/gyp/tools/pretty_vcproj.py
+++ b/gyp/tools/pretty_vcproj.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright (c) 2012 Google Inc. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
@@ -6,324 +6,331 @@
"""Make the format of a vcproj really pretty.
- This script normalize and sort an xml. It also fetches all the properties
- inside linked vsprops and include them explicitly in the vcproj.
+This script normalize and sort an xml. It also fetches all the properties
+inside linked vsprops and include them explicitly in the vcproj.
- It outputs the resulting xml to stdout.
+It outputs the resulting xml to stdout.
"""
-__author__ = 'nsylvain (Nicolas Sylvain)'
-
import os
import sys
+from xml.dom.minidom import Node, parse
-from xml.dom.minidom import parse
-from xml.dom.minidom import Node
-
-REPLACEMENTS = dict()
+__author__ = "nsylvain (Nicolas Sylvain)"
ARGUMENTS = None
+REPLACEMENTS = {}
-class CmpTuple(object):
- """Compare function between 2 tuple."""
- def __call__(self, x, y):
- return cmp(x[0], y[0])
-
+def cmp(x, y):
+ return (x > y) - (x < y)
-class CmpNode(object):
- """Compare function between 2 xml nodes."""
- def __call__(self, x, y):
- def get_string(node):
- node_string = "node"
- node_string += node.nodeName
- if node.nodeValue:
- node_string += node.nodeValue
+class CmpTuple:
+ """Compare function between 2 tuple."""
- if node.attributes:
- # We first sort by name, if present.
- node_string += node.getAttribute("Name")
+ def __call__(self, x, y):
+ return cmp(x[0], y[0])
- all_nodes = []
- for (name, value) in node.attributes.items():
- all_nodes.append((name, value))
- all_nodes.sort(CmpTuple())
- for (name, value) in all_nodes:
- node_string += name
- node_string += value
+class CmpNode:
+ """Compare function between 2 xml nodes."""
- return node_string
+ def __call__(self, x, y):
+ def get_string(node):
+ node_string = "node"
+ node_string += node.nodeName
+ if node.nodeValue:
+ node_string += node.nodeValue
- return cmp(get_string(x), get_string(y))
+ if node.attributes:
+ # We first sort by name, if present.
+ node_string += node.getAttribute("Name")
+ all_nodes = []
+ for name, value in node.attributes.items():
+ all_nodes.append((name, value))
-def PrettyPrintNode(node, indent=0):
- if node.nodeType == Node.TEXT_NODE:
- if node.data.strip():
- print '%s%s' % (' '*indent, node.data.strip())
- return
-
- if node.childNodes:
- node.normalize()
- # Get the number of attributes
- attr_count = 0
- if node.attributes:
- attr_count = node.attributes.length
-
- # Print the main tag
- if attr_count == 0:
- print '%s<%s>' % (' '*indent, node.nodeName)
- else:
- print '%s<%s' % (' '*indent, node.nodeName)
-
- all_attributes = []
- for (name, value) in node.attributes.items():
- all_attributes.append((name, value))
- all_attributes.sort(CmpTuple())
- for (name, value) in all_attributes:
- print '%s %s="%s"' % (' '*indent, name, value)
- print '%s>' % (' '*indent)
- if node.nodeValue:
- print '%s %s' % (' '*indent, node.nodeValue)
-
- for sub_node in node.childNodes:
- PrettyPrintNode(sub_node, indent=indent+2)
- print '%s%s>' % (' '*indent, node.nodeName)
+ all_nodes.sort(CmpTuple())
+ for name, value in all_nodes:
+ node_string += name
+ node_string += value
+ return node_string
-def FlattenFilter(node):
- """Returns a list of all the node and sub nodes."""
- node_list = []
+ return cmp(get_string(x), get_string(y))
- if (node.attributes and
- node.getAttribute('Name') == '_excluded_files'):
- # We don't add the "_excluded_files" filter.
- return []
- for current in node.childNodes:
- if current.nodeName == 'Filter':
- node_list.extend(FlattenFilter(current))
+def PrettyPrintNode(node, indent=0):
+ if node.nodeType == Node.TEXT_NODE:
+ if node.data.strip():
+ print("{}{}".format(" " * indent, node.data.strip()))
+ return
+
+ if node.childNodes:
+ node.normalize()
+ # Get the number of attributes
+ attr_count = 0
+ if node.attributes:
+ attr_count = node.attributes.length
+
+ # Print the main tag
+ if attr_count == 0:
+ print("{}<{}>".format(" " * indent, node.nodeName))
else:
- node_list.append(current)
+ print("{}<{}".format(" " * indent, node.nodeName))
- return node_list
+ all_attributes = []
+ for name, value in node.attributes.items():
+ all_attributes.append((name, value))
+ all_attributes.sort(CmpTuple())
+ for name, value in all_attributes:
+ print('{} {}="{}"'.format(" " * indent, name, value))
+ print("%s>" % (" " * indent))
+ if node.nodeValue:
+ print("{} {}".format(" " * indent, node.nodeValue))
+ for sub_node in node.childNodes:
+ PrettyPrintNode(sub_node, indent=indent + 2)
+ print("{}{}>".format(" " * indent, node.nodeName))
-def FixFilenames(filenames, current_directory):
- new_list = []
- for filename in filenames:
- if filename:
- for key in REPLACEMENTS:
- filename = filename.replace(key, REPLACEMENTS[key])
- os.chdir(current_directory)
- filename = filename.strip('"\' ')
- if filename.startswith('$'):
- new_list.append(filename)
- else:
- new_list.append(os.path.abspath(filename))
- return new_list
+def FlattenFilter(node):
+ """Returns a list of all the node and sub nodes."""
+ node_list = []
-def AbsoluteNode(node):
- """Makes all the properties we know about in this node absolute."""
- if node.attributes:
- for (name, value) in node.attributes.items():
- if name in ['InheritedPropertySheets', 'RelativePath',
- 'AdditionalIncludeDirectories',
- 'IntermediateDirectory', 'OutputDirectory',
- 'AdditionalLibraryDirectories']:
- # We want to fix up these paths
- path_list = value.split(';')
- new_list = FixFilenames(path_list, os.path.dirname(ARGUMENTS[1]))
- node.setAttribute(name, ';'.join(new_list))
- if not value:
- node.removeAttribute(name)
+ if node.attributes and node.getAttribute("Name") == "_excluded_files":
+ # We don't add the "_excluded_files" filter.
+ return []
+ for current in node.childNodes:
+ if current.nodeName == "Filter":
+ node_list.extend(FlattenFilter(current))
+ else:
+ node_list.append(current)
-def CleanupVcproj(node):
- """For each sub node, we call recursively this function."""
- for sub_node in node.childNodes:
- AbsoluteNode(sub_node)
- CleanupVcproj(sub_node)
-
- # Normalize the node, and remove all extranous whitespaces.
- for sub_node in node.childNodes:
- if sub_node.nodeType == Node.TEXT_NODE:
- sub_node.data = sub_node.data.replace("\r", "")
- sub_node.data = sub_node.data.replace("\n", "")
- sub_node.data = sub_node.data.rstrip()
-
- # Fix all the semicolon separated attributes to be sorted, and we also
- # remove the dups.
- if node.attributes:
- for (name, value) in node.attributes.items():
- sorted_list = sorted(value.split(';'))
- unique_list = []
- for i in sorted_list:
- if not unique_list.count(i):
- unique_list.append(i)
- node.setAttribute(name, ';'.join(unique_list))
- if not value:
- node.removeAttribute(name)
-
- if node.childNodes:
- node.normalize()
-
- # For each node, take a copy, and remove it from the list.
- node_array = []
- while node.childNodes and node.childNodes[0]:
- # Take a copy of the node and remove it from the list.
- current = node.childNodes[0]
- node.removeChild(current)
-
- # If the child is a filter, we want to append all its children
- # to this same list.
- if current.nodeName == 'Filter':
- node_array.extend(FlattenFilter(current))
- else:
- node_array.append(current)
+ return node_list
- # Sort the list.
- node_array.sort(CmpNode())
+def FixFilenames(filenames, current_directory):
+ new_list = []
+ for filename in filenames:
+ if filename:
+ for key, value in REPLACEMENTS.items():
+ filename = filename.replace(key, value)
+ os.chdir(current_directory)
+ filename = filename.strip("\"' ")
+ if filename.startswith("$"):
+ new_list.append(filename)
+ else:
+ new_list.append(os.path.abspath(filename))
+ return new_list
- # Insert the nodes in the correct order.
- for new_node in node_array:
- # But don't append empty tool node.
- if new_node.nodeName == 'Tool':
- if new_node.attributes and new_node.attributes.length == 1:
- # This one was empty.
- continue
- if new_node.nodeName == 'UserMacro':
- continue
- node.appendChild(new_node)
+def AbsoluteNode(node):
+ """Makes all the properties we know about in this node absolute."""
+ if node.attributes:
+ for name, value in node.attributes.items():
+ if name in [
+ "InheritedPropertySheets",
+ "RelativePath",
+ "AdditionalIncludeDirectories",
+ "IntermediateDirectory",
+ "OutputDirectory",
+ "AdditionalLibraryDirectories",
+ ]:
+ # We want to fix up these paths
+ path_list = value.split(";")
+ new_list = FixFilenames(path_list, os.path.dirname(ARGUMENTS[1]))
+ node.setAttribute(name, ";".join(new_list))
+ if not value:
+ node.removeAttribute(name)
-def GetConfiguationNodes(vcproj):
- #TODO(nsylvain): Find a better way to navigate the xml.
- nodes = []
- for node in vcproj.childNodes:
- if node.nodeName == "Configurations":
- for sub_node in node.childNodes:
- if sub_node.nodeName == "Configuration":
- nodes.append(sub_node)
- return nodes
+def CleanupVcproj(node):
+ """For each sub node, we call recursively this function."""
+ for sub_node in node.childNodes:
+ AbsoluteNode(sub_node)
+ CleanupVcproj(sub_node)
+
+ # Normalize the node, and remove all extraneous whitespaces.
+ for sub_node in node.childNodes:
+ if sub_node.nodeType == Node.TEXT_NODE:
+ sub_node.data = sub_node.data.replace("\r", "")
+ sub_node.data = sub_node.data.replace("\n", "")
+ sub_node.data = sub_node.data.rstrip()
+
+ # Fix all the semicolon separated attributes to be sorted, and we also
+ # remove the dups.
+ if node.attributes:
+ for name, value in node.attributes.items():
+ sorted_list = sorted(value.split(";"))
+ unique_list = []
+ for i in sorted_list:
+ if not unique_list.count(i):
+ unique_list.append(i)
+ node.setAttribute(name, ";".join(unique_list))
+ if not value:
+ node.removeAttribute(name)
+
+ if node.childNodes:
+ node.normalize()
+
+ # For each node, take a copy, and remove it from the list.
+ node_array = []
+ while node.childNodes and node.childNodes[0]:
+ # Take a copy of the node and remove it from the list.
+ current = node.childNodes[0]
+ node.removeChild(current)
+
+ # If the child is a filter, we want to append all its children
+ # to this same list.
+ if current.nodeName == "Filter":
+ node_array.extend(FlattenFilter(current))
+ else:
+ node_array.append(current)
+
+ # Sort the list.
+ node_array.sort(CmpNode())
+
+ # Insert the nodes in the correct order.
+ for new_node in node_array:
+ # But don't append empty tool node.
+ if new_node.nodeName == "Tool":
+ if new_node.attributes and new_node.attributes.length == 1:
+ # This one was empty.
+ continue
+ if new_node.nodeName == "UserMacro":
+ continue
+ node.appendChild(new_node)
+
+
+def GetConfigurationNodes(vcproj):
+ # TODO(nsylvain): Find a better way to navigate the xml.
+ nodes = []
+ for node in vcproj.childNodes:
+ if node.nodeName == "Configurations":
+ for sub_node in node.childNodes:
+ if sub_node.nodeName == "Configuration":
+ nodes.append(sub_node)
+
+ return nodes
def GetChildrenVsprops(filename):
- dom = parse(filename)
- if dom.documentElement.attributes:
- vsprops = dom.documentElement.getAttribute('InheritedPropertySheets')
- return FixFilenames(vsprops.split(';'), os.path.dirname(filename))
- return []
+ dom = parse(filename)
+ if dom.documentElement.attributes:
+ vsprops = dom.documentElement.getAttribute("InheritedPropertySheets")
+ return FixFilenames(vsprops.split(";"), os.path.dirname(filename))
+ return []
-def SeekToNode(node1, child2):
- # A text node does not have properties.
- if child2.nodeType == Node.TEXT_NODE:
- return None
- # Get the name of the current node.
- current_name = child2.getAttribute("Name")
- if not current_name:
- # There is no name. We don't know how to merge.
+def SeekToNode(node1, child2):
+ # A text node does not have properties.
+ if child2.nodeType == Node.TEXT_NODE:
+ return None
+
+ # Get the name of the current node.
+ current_name = child2.getAttribute("Name")
+ if not current_name:
+ # There is no name. We don't know how to merge.
+ return None
+
+ # Look through all the nodes to find a match.
+ for sub_node in node1.childNodes:
+ if sub_node.nodeName == child2.nodeName:
+ name = sub_node.getAttribute("Name")
+ if name == current_name:
+ return sub_node
+
+ # No match. We give up.
return None
- # Look through all the nodes to find a match.
- for sub_node in node1.childNodes:
- if sub_node.nodeName == child2.nodeName:
- name = sub_node.getAttribute("Name")
- if name == current_name:
- return sub_node
-
- # No match. We give up.
- return None
-
def MergeAttributes(node1, node2):
- # No attributes to merge?
- if not node2.attributes:
- return
-
- for (name, value2) in node2.attributes.items():
- # Don't merge the 'Name' attribute.
- if name == 'Name':
- continue
- value1 = node1.getAttribute(name)
- if value1:
- # The attribute exist in the main node. If it's equal, we leave it
- # untouched, otherwise we concatenate it.
- if value1 != value2:
- node1.setAttribute(name, ';'.join([value1, value2]))
- else:
- # The attribute does nto exist in the main node. We append this one.
- node1.setAttribute(name, value2)
-
- # If the attribute was a property sheet attributes, we remove it, since
- # they are useless.
- if name == 'InheritedPropertySheets':
- node1.removeAttribute(name)
+ # No attributes to merge?
+ if not node2.attributes:
+ return
+
+ for name, value2 in node2.attributes.items():
+ # Don't merge the 'Name' attribute.
+ if name == "Name":
+ continue
+ value1 = node1.getAttribute(name)
+ if value1:
+ # The attribute exist in the main node. If it's equal, we leave it
+ # untouched, otherwise we concatenate it.
+ if value1 != value2:
+ node1.setAttribute(name, ";".join([value1, value2]))
+ else:
+ # The attribute does not exist in the main node. We append this one.
+ node1.setAttribute(name, value2)
+
+ # If the attribute was a property sheet attributes, we remove it, since
+ # they are useless.
+ if name == "InheritedPropertySheets":
+ node1.removeAttribute(name)
def MergeProperties(node1, node2):
- MergeAttributes(node1, node2)
- for child2 in node2.childNodes:
- child1 = SeekToNode(node1, child2)
- if child1:
- MergeProperties(child1, child2)
- else:
- node1.appendChild(child2.cloneNode(True))
+ MergeAttributes(node1, node2)
+ for child2 in node2.childNodes:
+ child1 = SeekToNode(node1, child2)
+ if child1:
+ MergeProperties(child1, child2)
+ else:
+ node1.appendChild(child2.cloneNode(True))
def main(argv):
- """Main function of this vcproj prettifier."""
- global ARGUMENTS
- ARGUMENTS = argv
-
- # check if we have exactly 1 parameter.
- if len(argv) < 2:
- print ('Usage: %s "c:\\path\\to\\vcproj.vcproj" [key1=value1] '
- '[key2=value2]' % argv[0])
- return 1
-
- # Parse the keys
- for i in range(2, len(argv)):
- (key, value) = argv[i].split('=')
- REPLACEMENTS[key] = value
-
- # Open the vcproj and parse the xml.
- dom = parse(argv[1])
-
- # First thing we need to do is find the Configuration Node and merge them
- # with the vsprops they include.
- for configuration_node in GetConfiguationNodes(dom.documentElement):
- # Get the property sheets associated with this configuration.
- vsprops = configuration_node.getAttribute('InheritedPropertySheets')
-
- # Fix the filenames to be absolute.
- vsprops_list = FixFilenames(vsprops.strip().split(';'),
- os.path.dirname(argv[1]))
-
- # Extend the list of vsprops with all vsprops contained in the current
- # vsprops.
- for current_vsprops in vsprops_list:
- vsprops_list.extend(GetChildrenVsprops(current_vsprops))
-
- # Now that we have all the vsprops, we need to merge them.
- for current_vsprops in vsprops_list:
- MergeProperties(configuration_node,
- parse(current_vsprops).documentElement)
-
- # Now that everything is merged, we need to cleanup the xml.
- CleanupVcproj(dom.documentElement)
-
- # Finally, we use the prett xml function to print the vcproj back to the
- # user.
- #print dom.toprettyxml(newl="\n")
- PrettyPrintNode(dom.documentElement)
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(main(sys.argv))
+ """Main function of this vcproj prettifier."""
+ global ARGUMENTS
+ ARGUMENTS = argv
+
+ # check if we have exactly 1 parameter.
+ if len(argv) < 2:
+ print(
+ 'Usage: %s "c:\\path\\to\\vcproj.vcproj" [key1=value1] '
+ "[key2=value2]" % argv[0]
+ )
+ return 1
+
+ # Parse the keys
+ for i in range(2, len(argv)):
+ (key, value) = argv[i].split("=")
+ REPLACEMENTS[key] = value
+
+ # Open the vcproj and parse the xml.
+ dom = parse(argv[1])
+
+ # First thing we need to do is find the Configuration Node and merge them
+ # with the vsprops they include.
+ for configuration_node in GetConfigurationNodes(dom.documentElement):
+ # Get the property sheets associated with this configuration.
+ vsprops = configuration_node.getAttribute("InheritedPropertySheets")
+
+ # Fix the filenames to be absolute.
+ vsprops_list = FixFilenames(
+ vsprops.strip().split(";"), os.path.dirname(argv[1])
+ )
+
+ # Extend the list of vsprops with all vsprops contained in the current
+ # vsprops.
+ for current_vsprops in vsprops_list:
+ vsprops_list.extend(GetChildrenVsprops(current_vsprops))
+
+ # Now that we have all the vsprops, we need to merge them.
+ for current_vsprops in vsprops_list:
+ MergeProperties(configuration_node, parse(current_vsprops).documentElement)
+
+ # Now that everything is merged, we need to cleanup the xml.
+ CleanupVcproj(dom.documentElement)
+
+ # Finally, we use the prett xml function to print the vcproj back to the
+ # user.
+ # print dom.toprettyxml(newl="\n")
+ PrettyPrintNode(dom.documentElement)
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
diff --git a/lib/Find-VS2017.cs b/lib/Find-VisualStudio.cs
similarity index 65%
rename from lib/Find-VS2017.cs
rename to lib/Find-VisualStudio.cs
index 6e7429b771..d2e45a7627 100644
--- a/lib/Find-VS2017.cs
+++ b/lib/Find-VisualStudio.cs
@@ -3,10 +3,13 @@
// See accompanying file LICENSE at https://github.com/node4good/windows-autoconf
// Usage:
-// powershell -ExecutionPolicy Unrestricted -Version "2.0" -Command "&{Add-Type -Path Find-VS2017.cs; [VisualStudioConfiguration.Main]::Query()}"
+// powershell -ExecutionPolicy Unrestricted -Command "Add-Type -Path Find-VisualStudio.cs; [VisualStudioConfiguration.Main]::PrintJson()"
+// This script needs to be compatible with PowerShell v2 to run on Windows 2008R2 and Windows 7.
+
using System;
using System.Text;
using System.Runtime.InteropServices;
+using System.Collections.Generic;
namespace VisualStudioConfiguration
{
@@ -184,7 +187,7 @@ public class SetupConfigurationClass
public static class Main
{
- public static void Query()
+ public static void PrintJson()
{
ISetupConfiguration query = new SetupConfiguration();
ISetupConfiguration2 query2 = (ISetupConfiguration2)query;
@@ -192,82 +195,56 @@ public static void Query()
int pceltFetched;
ISetupInstance2[] rgelt = new ISetupInstance2[1];
- StringBuilder log = new StringBuilder();
+ List instances = new List();
while (true)
{
e.Next(1, rgelt, out pceltFetched);
if (pceltFetched <= 0)
{
- Console.WriteLine(String.Format("{{\"log\":\"{0}\"}}", log.ToString()));
+ Console.WriteLine(String.Format("[{0}]", string.Join(",", instances.ToArray())));
return;
}
- if (CheckInstance(rgelt[0], ref log))
- return;
+
+ try
+ {
+ instances.Add(InstanceJson(rgelt[0]));
+ }
+ catch (COMException)
+ {
+ // Ignore instances that can't be queried.
+ }
}
}
- private static bool CheckInstance(ISetupInstance2 setupInstance2, ref StringBuilder log)
+ private static string JsonString(string s)
{
- // Visual Studio Community 2017 component directory:
- // https://www.visualstudio.com/en-us/productinfo/vs2017-install-product-Community.workloads
+ return "\"" + s.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
+ }
- string path = setupInstance2.GetInstallationPath().Replace("\\", "\\\\");
- log.Append(String.Format("Found installation at: {0}\\n", path));
+ private static string InstanceJson(ISetupInstance2 setupInstance2)
+ {
+ // Visual Studio component directory:
+ // https://docs.microsoft.com/en-us/visualstudio/install/workload-and-component-ids
- bool hasMSBuild = false;
- bool hasVCTools = false;
- uint Win10SDKVer = 0;
- bool hasWin8SDK = false;
+ StringBuilder json = new StringBuilder();
+ json.Append("{");
- foreach (ISetupPackageReference package in setupInstance2.GetPackages())
- {
- const string Win10SDKPrefix = "Microsoft.VisualStudio.Component.Windows10SDK.";
-
- string id = package.GetId();
- if (id == "Microsoft.VisualStudio.VC.MSBuild.Base")
- hasMSBuild = true;
- else if (id == "Microsoft.VisualStudio.Component.VC.Tools.x86.x64")
- hasVCTools = true;
- else if (id.StartsWith(Win10SDKPrefix)) {
- string[] parts = id.Substring(Win10SDKPrefix.Length).Split('.');
- if (parts.Length > 1 && parts[1] != "Desktop")
- continue;
- uint foundSdkVer;
- if (UInt32.TryParse(parts[0], out foundSdkVer))
- Win10SDKVer = Math.Max(Win10SDKVer, foundSdkVer);
- } else if (id == "Microsoft.VisualStudio.Component.Windows81SDK")
- hasWin8SDK = true;
- else
- continue;
-
- log.Append(String.Format(" - Found {0}\\n", id));
- }
+ string path = JsonString(setupInstance2.GetInstallationPath());
+ json.Append(String.Format("\"path\":{0},", path));
- if (!hasMSBuild)
- log.Append(" - Missing Visual Studio C++ core features (Microsoft.VisualStudio.VC.MSBuild.Base)\\n");
- if (!hasVCTools)
- log.Append(" - Missing VC++ 2017 v141 toolset (x86,x64) (Microsoft.VisualStudio.Component.VC.Tools.x86.x64)\\n");
- if ((Win10SDKVer == 0) && (!hasWin8SDK))
- log.Append(" - Missing a Windows SDK (Microsoft.VisualStudio.Component.Windows10SDK.* or Microsoft.VisualStudio.Component.Windows81SDK)\\n");
+ string version = JsonString(setupInstance2.GetInstallationVersion());
+ json.Append(String.Format("\"version\":{0},", version));
- if (hasMSBuild && hasVCTools)
+ List packages = new List();
+ foreach (ISetupPackageReference package in setupInstance2.GetPackages())
{
- if (Win10SDKVer > 0)
- {
- log.Append(" - Using this installation with Windows 10 SDK"/*\\n*/);
- Console.WriteLine(String.Format("{{\"log\":\"{0}\",\"path\":\"{1}\",\"sdk\":\"10.0.{2}.0\"}}", log.ToString(), path, Win10SDKVer));
- return true;
- }
- else if (hasWin8SDK)
- {
- log.Append(" - Using this installation with Windows 8.1 SDK"/*\\n*/);
- Console.WriteLine(String.Format("{{\"log\":\"{0}\",\"path\":\"{1}\",\"sdk\":\"8.1\"}}", log.ToString(), path));
- return true;
- }
+ string id = JsonString(package.GetId());
+ packages.Add(id);
}
+ json.Append(String.Format("\"packages\":[{0}]", string.Join(",", packages.ToArray())));
- log.Append(" - Some required components are missing, not using this installation\\n");
- return false;
+ json.Append("}");
+ return json.ToString();
}
}
}
diff --git a/lib/build.js b/lib/build.js
index 2f8e14c374..00a0abe691 100644
--- a/lib/build.js
+++ b/lib/build.js
@@ -1,225 +1,169 @@
-
-module.exports = exports = build
-
-/**
- * Module dependencies.
- */
-
-var fs = require('graceful-fs')
- , rm = require('rimraf')
- , path = require('path')
- , glob = require('glob')
- , log = require('npmlog')
- , which = require('which')
- , exec = require('child_process').exec
- , processRelease = require('./process-release')
- , win = process.platform === 'win32'
-
-exports.usage = 'Invokes `' + (win ? 'msbuild' : 'make') + '` and builds the module'
-
-function build (gyp, argv, callback) {
- var platformMake = 'make'
+'use strict'
+
+const gracefulFs = require('graceful-fs')
+const fs = gracefulFs.promises
+const path = require('path')
+const { glob } = require('tinyglobby')
+const log = require('./log')
+const which = require('which')
+const win = process.platform === 'win32'
+
+async function build (gyp, argv) {
+ let platformMake = 'make'
if (process.platform === 'aix') {
platformMake = 'gmake'
+ } else if (process.platform === 'os400') {
+ platformMake = 'gmake'
} else if (process.platform.indexOf('bsd') !== -1) {
platformMake = 'gmake'
} else if (win && argv.length > 0) {
- argv = argv.map(function(target) {
+ argv = argv.map(function (target) {
return '/t:' + target
})
}
- var release = processRelease(argv, gyp, process.version, process.release)
- , makeCommand = gyp.opts.make || process.env.MAKE || platformMake
- , command = win ? 'msbuild' : makeCommand
- , buildDir = path.resolve('build')
- , configPath = path.resolve(buildDir, 'config.gypi')
- , jobs = gyp.opts.jobs || process.env.JOBS
- , buildType
- , config
- , arch
- , nodeDir
+ const makeCommand = gyp.opts.make || process.env.MAKE || platformMake
+ let command = win ? 'msbuild' : makeCommand
+ const jobs = gyp.opts.jobs || process.env.JOBS
+ let buildType
+ let config
+ let arch
+ let nodeDir
+ let guessedSolution
+ let python
+ let buildBinsDir
- loadConfigGypi()
+ await loadConfigGypi()
/**
* Load the "config.gypi" file that was generated during "configure".
*/
- function loadConfigGypi () {
- fs.readFile(configPath, 'utf8', function (err, data) {
- if (err) {
- if (err.code == 'ENOENT') {
- callback(new Error('You must run `node-gyp configure` first!'))
- } else {
- callback(err)
- }
- return
+ async function loadConfigGypi () {
+ let data
+ try {
+ const configPath = path.resolve('build', 'config.gypi')
+ data = await fs.readFile(configPath, 'utf8')
+ } catch (err) {
+ if (err.code === 'ENOENT') {
+ throw new Error('You must run `node-gyp configure` first!')
+ } else {
+ throw err
}
- config = JSON.parse(data.replace(/\#.+\n/, ''))
+ }
- // get the 'arch', 'buildType', and 'nodeDir' vars from the config
- buildType = config.target_defaults.default_configuration
- arch = config.variables.target_arch
- nodeDir = config.variables.nodedir
+ config = JSON.parse(data.replace(/#.+\n/, ''))
- if ('debug' in gyp.opts) {
- buildType = gyp.opts.debug ? 'Debug' : 'Release'
- }
- if (!buildType) {
- buildType = 'Release'
- }
+ // get the 'arch', 'buildType', and 'nodeDir' vars from the config
+ buildType = config.target_defaults.default_configuration
+ arch = config.variables.target_arch
+ nodeDir = config.variables.nodedir
+ python = config.variables.python
- log.verbose('build type', buildType)
- log.verbose('architecture', arch)
- log.verbose('node dev dir', nodeDir)
+ if ('debug' in gyp.opts) {
+ buildType = gyp.opts.debug ? 'Debug' : 'Release'
+ }
+ if (!buildType) {
+ buildType = 'Release'
+ }
- if (win) {
- findSolutionFile()
- } else {
- doWhich()
- }
- })
+ log.verbose('build type', buildType)
+ log.verbose('architecture', arch)
+ log.verbose('node dev dir', nodeDir)
+ log.verbose('python', python)
+
+ if (win) {
+ await findSolutionFile()
+ } else {
+ await doWhich()
+ }
}
/**
* On Windows, find the first build/*.sln file.
*/
- function findSolutionFile () {
- glob('build/*.sln', function (err, files) {
- if (err) return callback(err)
- if (files.length === 0) {
- return callback(new Error('Could not find *.sln file. Did you run "configure"?'))
+ async function findSolutionFile () {
+ const files = await glob('build/*.sln', { expandDirectories: false })
+ if (files.length === 0) {
+ if (gracefulFs.existsSync('build/Makefile') ||
+ (await glob('build/*.mk', { expandDirectories: false })).length !== 0) {
+ command = makeCommand
+ await doWhich(false)
+ return
+ } else {
+ throw new Error('Could not find *.sln file or Makefile. Did you run "configure"?')
}
- guessedSolution = files[0]
- log.verbose('found first Solution file', guessedSolution)
- doWhich()
- })
+ }
+ guessedSolution = files[0]
+ log.verbose('found first Solution file', guessedSolution)
+ await doWhich(true)
}
/**
* Uses node-which to locate the msbuild / make executable.
*/
- function doWhich () {
- // First make sure we have the build command in the PATH
- which(command, function (err, execPath) {
- if (err) {
- if (win && /not found/.test(err.message)) {
- // On windows and no 'msbuild' found. Let's guess where it is
- findMsbuild()
- } else {
- // Some other error or 'make' not found on Unix, report that to the user
- callback(err)
- }
- return
+ async function doWhich (msvs) {
+ // On Windows use msbuild provided by node-gyp configure
+ if (msvs) {
+ if (!config.variables.msbuild_path) {
+ throw new Error('MSBuild is not set, please run `node-gyp configure`.')
}
- log.verbose('`which` succeeded for `' + command + '`', execPath)
- doBuild()
- })
- }
-
- /**
- * Search for the location of "msbuild.exe" file on Windows.
- */
-
- function findMsbuild () {
- if (config.variables.msbuild_path) {
command = config.variables.msbuild_path
log.verbose('using MSBuild:', command)
- doBuild()
+ await doBuild(msvs)
return
}
- log.verbose('could not find "msbuild.exe" in PATH - finding location in registry')
- var notfoundErr = 'Can\'t find "msbuild.exe". Do you have Microsoft Visual Studio C++ 2008+ installed?'
- var cmd = 'reg query "HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions" /s'
- if (process.arch !== 'ia32')
- cmd += ' /reg:32'
- exec(cmd, function (err, stdout, stderr) {
- if (err) {
- return callback(new Error(err.message + '\n' + notfoundErr))
- }
- var reVers = /ToolsVersions\\([^\\]+)$/i
- , rePath = /\r\n[ \t]+MSBuildToolsPath[ \t]+REG_SZ[ \t]+([^\r]+)/i
- , msbuilds = []
- , r
- , msbuildPath
- stdout.split('\r\n\r\n').forEach(function(l) {
- if (!l) return
- l = l.trim()
- if (r = reVers.exec(l.substring(0, l.indexOf('\r\n')))) {
- var ver = parseFloat(r[1], 10)
- if (ver >= 3.5) {
- if (r = rePath.exec(l)) {
- msbuilds.push({
- version: ver,
- path: r[1]
- })
- }
- }
- }
- })
- msbuilds.sort(function (x, y) {
- return (x.version < y.version ? -1 : 1)
- })
- ;(function verifyMsbuild () {
- if (!msbuilds.length) return callback(new Error(notfoundErr))
- msbuildPath = path.resolve(msbuilds.pop().path, 'msbuild.exe')
- fs.stat(msbuildPath, function (err, stat) {
- if (err) {
- if (err.code == 'ENOENT') {
- if (msbuilds.length) {
- return verifyMsbuild()
- } else {
- callback(new Error(notfoundErr))
- }
- } else {
- callback(err)
- }
- return
- }
- command = msbuildPath
- doBuild()
- })
- })()
- })
+ // First make sure we have the build command in the PATH
+ const execPath = await which(command)
+ log.verbose('`which` succeeded for `' + command + '`', execPath)
+ await doBuild(msvs)
}
-
/**
* Actually spawn the process and compile the module.
*/
- function doBuild () {
-
+ async function doBuild (msvs) {
// Enable Verbose build
- var verbose = log.levels[log.level] <= log.levels.verbose
- if (!win && verbose) {
+ const verbose = log.logger.isVisible('verbose')
+ let j
+
+ if (!msvs && verbose) {
argv.push('V=1')
}
- if (win && !verbose) {
+
+ if (msvs && !verbose) {
argv.push('/clp:Verbosity=minimal')
}
- if (win) {
+ if (msvs) {
// Turn off the Microsoft logo on Windows
argv.push('/nologo')
+ // No lingering msbuild processes and open file handles
+ argv.push('/nodeReuse:false')
}
// Specify the build type, Release by default
- if (win) {
- var archLower = arch.toLowerCase()
- var p = archLower === 'x64' ? 'x64' :
- (archLower === 'arm' ? 'ARM' : 'Win32')
+ if (msvs) {
+ // Convert .gypi config target_arch to MSBuild /Platform
+ // Since there are many ways to state '32-bit Intel', default to it.
+ // N.B. msbuild's Condition string equality tests are case-insensitive.
+ const archLower = arch.toLowerCase()
+ const p = archLower === 'x64'
+ ? 'x64'
+ : (archLower === 'arm'
+ ? 'ARM'
+ : (archLower === 'arm64' ? 'ARM64' : 'Win32'))
argv.push('/p:Configuration=' + buildType + ';Platform=' + p)
if (jobs) {
- var j = parseInt(jobs, 10)
+ j = parseInt(jobs, 10)
if (!isNaN(j) && j > 0) {
argv.push('/m:' + j)
} else if (jobs.toUpperCase() === 'MAX') {
- argv.push('/m:' + require('os').cpus().length)
+ argv.push('/m:' + require('os').availableParallelism())
}
}
} else {
@@ -228,43 +172,59 @@ function build (gyp, argv, callback) {
argv.push('-C')
argv.push('build')
if (jobs) {
- var j = parseInt(jobs, 10)
+ j = parseInt(jobs, 10)
if (!isNaN(j) && j > 0) {
argv.push('--jobs')
argv.push(j)
} else if (jobs.toUpperCase() === 'MAX') {
argv.push('--jobs')
- argv.push(require('os').cpus().length)
+ argv.push(require('os').availableParallelism())
}
}
}
- if (win) {
+ if (msvs) {
// did the user specify their own .sln file?
- var hasSln = argv.some(function (arg) {
- return path.extname(arg) == '.sln'
+ const hasSln = argv.some(function (arg) {
+ return path.extname(arg) === '.sln'
})
if (!hasSln) {
argv.unshift(gyp.opts.solution || guessedSolution)
}
}
- var proc = gyp.spawn(command, argv)
- proc.on('exit', onExit)
- }
+ if (!win) {
+ // Add build-time dependency symlinks (such as Python) to PATH
+ buildBinsDir = path.resolve('build', 'node_gyp_bins')
+ process.env.PATH = `${buildBinsDir}:${process.env.PATH}`
+ await fs.mkdir(buildBinsDir, { recursive: true })
+ const symlinkDestination = path.join(buildBinsDir, 'python3')
+ try {
+ await fs.unlink(symlinkDestination)
+ } catch (err) {
+ if (err.code !== 'ENOENT') throw err
+ }
+ await fs.symlink(python, symlinkDestination)
+ log.verbose('bin symlinks', `created symlink to "${python}" in "${buildBinsDir}" and added to PATH`)
+ }
- /**
- * Invoked after the make/msbuild command exits.
- */
+ const proc = gyp.spawn(command, argv)
+ await new Promise((resolve, reject) => proc.on('exit', async (code, signal) => {
+ if (buildBinsDir) {
+ // Clean up the build-time dependency symlinks:
+ await fs.rm(buildBinsDir, { recursive: true, maxRetries: 3 })
+ }
- function onExit (code, signal) {
- if (code !== 0) {
- return callback(new Error('`' + command + '` failed with exit code: ' + code))
- }
- if (signal) {
- return callback(new Error('`' + command + '` got signal: ' + signal))
- }
- callback()
+ if (code !== 0) {
+ return reject(new Error('`' + command + '` failed with exit code: ' + code))
+ }
+ if (signal) {
+ return reject(new Error('`' + command + '` got signal: ' + signal))
+ }
+ resolve()
+ }))
}
-
}
+
+module.exports = build
+module.exports.usage = 'Invokes `' + (win ? 'msbuild' : 'make') + '` and builds the module'
diff --git a/lib/clean.js b/lib/clean.js
index e69164d45a..479c374f10 100644
--- a/lib/clean.js
+++ b/lib/clean.js
@@ -1,22 +1,15 @@
+'use strict'
-module.exports = exports = clean
-
-exports.usage = 'Removes any generated build files and the "out" dir'
-
-/**
- * Module dependencies.
- */
-
-var rm = require('rimraf')
-var log = require('npmlog')
-
-
-function clean (gyp, argv, callback) {
+const fs = require('graceful-fs').promises
+const log = require('./log')
+async function clean (gyp, argv) {
// Remove the 'build' dir
- var buildDir = 'build'
+ const buildDir = 'build'
log.verbose('clean', 'removing "%s" directory', buildDir)
- rm(buildDir, callback)
-
+ await fs.rm(buildDir, { recursive: true, force: true, maxRetries: 3 })
}
+
+module.exports = clean
+module.exports.usage = 'Removes any generated build files and the "out" dir'
diff --git a/lib/configure.js b/lib/configure.js
index a42abe5972..ee672cfbf2 100644
--- a/lib/configure.js
+++ b/lib/configure.js
@@ -1,62 +1,62 @@
-module.exports = exports = configure
-module.exports.test = {
- PythonFinder: PythonFinder,
- findAccessibleSync: findAccessibleSync,
- findPython: findPython,
-}
-
-/**
- * Module dependencies.
- */
-
-var fs = require('graceful-fs')
- , path = require('path')
- , log = require('npmlog')
- , osenv = require('osenv')
- , which = require('which')
- , semver = require('semver')
- , mkdirp = require('mkdirp')
- , cp = require('child_process')
- , extend = require('util')._extend
- , processRelease = require('./process-release')
- , win = process.platform === 'win32'
- , findNodeDirectory = require('./find-node-directory')
- , msgFormat = require('util').format
-if (win)
- var findVS2017 = require('./find-vs2017')
-
-exports.usage = 'Generates ' + (win ? 'MSVC project files' : 'a Makefile') + ' for the current module'
-
-function configure (gyp, argv, callback) {
-
- var python = gyp.opts.python || process.env.PYTHON || 'python2'
- , buildDir = path.resolve('build')
- , configNames = [ 'config.gypi', 'common.gypi' ]
- , configs = []
- , nodeDir
- , release = processRelease(argv, gyp, process.version, process.release)
-
- findPython(python, function (err, found) {
- if (err) {
- callback(err)
- } else {
- python = found
- getNodeDir()
- }
- })
-
- function getNodeDir () {
-
+'use strict'
+
+const { promises: fs, readFileSync } = require('graceful-fs')
+const path = require('path')
+const log = require('./log')
+const os = require('os')
+const processRelease = require('./process-release')
+const win = process.platform === 'win32'
+const findNodeDirectory = require('./find-node-directory')
+const { createConfigGypi } = require('./create-config-gypi')
+const { format: msgFormat } = require('util')
+const { findAccessibleSync } = require('./util')
+const { findPython } = require('./find-python')
+const { findVisualStudio } = win ? require('./find-visualstudio') : {}
+
+const majorRe = /^#define NODE_MAJOR_VERSION (\d+)/m
+const minorRe = /^#define NODE_MINOR_VERSION (\d+)/m
+const patchRe = /^#define NODE_PATCH_VERSION (\d+)/m
+
+async function configure (gyp, argv) {
+ const buildDir = path.resolve('build')
+ const configNames = ['config.gypi', 'common.gypi']
+ const configs = []
+ let nodeDir
+ const release = processRelease(argv, gyp, process.version, process.release)
+
+ const python = await findPython(gyp.opts.python)
+ return getNodeDir()
+
+ async function getNodeDir () {
// 'python' should be set by now
process.env.PYTHON = python
+ if (!gyp.opts.nodedir &&
+ process.config.variables.use_prefix_to_find_headers) {
+ // check if the headers can be found using the prefix specified
+ // at build time. Use them if they match the version expected
+ const prefix = process.config.variables.node_prefix
+ let availVersion
+ try {
+ const nodeVersionH = readFileSync(path.join(prefix,
+ 'include', 'node', 'node_version.h'), { encoding: 'utf8' })
+ const major = nodeVersionH.match(majorRe)[1]
+ const minor = nodeVersionH.match(minorRe)[1]
+ const patch = nodeVersionH.match(patchRe)[1]
+ availVersion = major + '.' + minor + '.' + patch
+ } catch {}
+ if (availVersion === release.version) {
+ // ok version matches, use the headers
+ gyp.opts.nodedir = prefix
+ log.verbose('using local node headers based on prefix',
+ 'setting nodedir to ' + gyp.opts.nodedir)
+ }
+ }
+
if (gyp.opts.nodedir) {
// --nodedir was specified. use that for the dev files
- nodeDir = gyp.opts.nodedir.replace(/^~/, osenv.home())
-
+ nodeDir = gyp.opts.nodedir.replace(/^~/, os.homedir())
log.verbose('get node dir', 'compiling against specified --nodedir dev files: %s', nodeDir)
- createBuildDir()
-
} else {
// if no --nodedir specified, ensure node dependencies are installed
if ('v' + release.version !== process.version) {
@@ -69,150 +69,86 @@ function configure (gyp, argv, callback) {
if (!release.semver) {
// could not parse the version string with semver
- return callback(new Error('Invalid version number: ' + release.version))
+ throw new Error('Invalid version number: ' + release.version)
}
// If the tarball option is set, always remove and reinstall the headers
// into devdir. Otherwise only install if they're not already there.
- gyp.opts.ensure = gyp.opts.tarball ? false : true
+ gyp.opts.ensure = !gyp.opts.tarball
- gyp.commands.install([ release.version ], function (err, version) {
- if (err) return callback(err)
- log.verbose('get node dir', 'target node version installed:', release.versionDir)
- nodeDir = path.resolve(gyp.devDir, release.versionDir)
- createBuildDir()
- })
+ await gyp.commands.install([release.version])
+
+ log.verbose('get node dir', 'target node version installed:', release.versionDir)
+ nodeDir = path.resolve(gyp.devDir, release.versionDir)
}
+
+ return createBuildDir()
}
- function createBuildDir () {
+ async function createBuildDir () {
log.verbose('build dir', 'attempting to create "build" dir: %s', buildDir)
- mkdirp(buildDir, function (err, isNew) {
- if (err) return callback(err)
- log.verbose('build dir', '"build" dir needed to be created?', isNew)
- if (win && (!gyp.opts.msvs_version || gyp.opts.msvs_version === '2017')) {
- findVS2017(function (err, vsSetup) {
- if (err) {
- log.verbose('Not using VS2017:', err.message)
- createConfigFile()
- } else {
- createConfigFile(null, vsSetup)
+
+ const isNew = await fs.mkdir(buildDir, { recursive: true })
+ log.verbose(
+ 'build dir', '"build" dir needed to be created?', isNew ? 'Yes' : 'No'
+ )
+ if (win) {
+ let usingMakeGenerator = false
+ for (let i = argv.length - 1; i >= 0; --i) {
+ const arg = argv[i]
+ if (arg === '-f' || arg === '--format') {
+ const format = argv[i + 1]
+ if (typeof format === 'string' && format.startsWith('make')) {
+ usingMakeGenerator = true
+ break
}
- })
- } else {
- createConfigFile()
+ } else if (arg.startsWith('--format=make')) {
+ usingMakeGenerator = true
+ break
+ }
}
- })
- }
-
- function createConfigFile (err, vsSetup) {
- if (err) return callback(err)
-
- var configFilename = 'config.gypi'
- var configPath = path.resolve(buildDir, configFilename)
-
- log.verbose('build/' + configFilename, 'creating config file')
-
- var config = process.config || {}
- , defaults = config.target_defaults
- , variables = config.variables
-
- // default "config.variables"
- if (!variables) variables = config.variables = {}
-
- // default "config.defaults"
- if (!defaults) defaults = config.target_defaults = {}
-
- // don't inherit the "defaults" from node's `process.config` object.
- // doing so could cause problems in cases where the `node` executable was
- // compiled on a different machine (with different lib/include paths) than
- // the machine where the addon is being built to
- defaults.cflags = []
- defaults.defines = []
- defaults.include_dirs = []
- defaults.libraries = []
-
- // set the default_configuration prop
- if ('debug' in gyp.opts) {
- defaults.default_configuration = gyp.opts.debug ? 'Debug' : 'Release'
- }
- if (!defaults.default_configuration) {
- defaults.default_configuration = 'Release'
- }
-
- // set the target_arch variable
- variables.target_arch = gyp.opts.arch || process.arch || 'ia32'
-
- // set the node development directory
- variables.nodedir = nodeDir
-
- // disable -T "thin" static archives by default
- variables.standalone_static_library = gyp.opts.thin ? 0 : 1
-
- if (vsSetup) {
- // GYP doesn't (yet) have support for VS2017, so we force it to VS2015
- // to avoid pulling a floating patch that has not landed upstream.
- // Ref: https://chromium-review.googlesource.com/#/c/433540/
- gyp.opts.msvs_version = '2015'
- process.env['GYP_MSVS_VERSION'] = 2015
- process.env['GYP_MSVS_OVERRIDE_PATH'] = vsSetup.path
- defaults['msbuild_toolset'] = 'v141'
- defaults['msvs_windows_target_platform_version'] = vsSetup.sdk
- variables['msbuild_path'] = path.join(vsSetup.path, 'MSBuild', '15.0',
- 'Bin', 'MSBuild.exe')
+ let vsInfo = {}
+ if (!usingMakeGenerator) {
+ vsInfo = await findVisualStudio(release.semver, gyp.opts['msvs-version'])
+ }
+ return createConfigFile(vsInfo)
}
+ return createConfigFile(null)
+ }
- // loop through the rest of the opts and add the unknown ones as variables.
- // this allows for module-specific configure flags like:
- //
- // $ node-gyp configure --shared-libxml2
- Object.keys(gyp.opts).forEach(function (opt) {
- if (opt === 'argv') return
- if (opt in gyp.configDefs) return
- variables[opt.replace(/-/g, '_')] = gyp.opts[opt]
- })
-
- // ensures that any boolean values from `process.config` get stringified
- function boolsToString (k, v) {
- if (typeof v === 'boolean')
- return String(v)
- return v
+ async function createConfigFile (vsInfo) {
+ if (win) {
+ process.env.GYP_MSVS_VERSION = Math.min(vsInfo.versionYear, 2015)
+ process.env.GYP_MSVS_OVERRIDE_PATH = vsInfo.path
}
-
- log.silly('build/' + configFilename, config)
-
- // now write out the config.gypi file to the build/ dir
- var prefix = '# Do not edit. File was generated by node-gyp\'s "configure" step'
- , json = JSON.stringify(config, boolsToString, 2)
- log.verbose('build/' + configFilename, 'writing out config file: %s', configPath)
+ const configPath = await createConfigGypi({ gyp, buildDir, nodeDir, vsInfo, python })
configs.push(configPath)
- fs.writeFile(configPath, [prefix, json, ''].join('\n'), findConfigs)
+ return findConfigs()
}
- function findConfigs (err) {
- if (err) return callback(err)
- var name = configNames.shift()
- if (!name) return runGyp()
- var fullPath = path.resolve(name)
+ async function findConfigs () {
+ const name = configNames.shift()
+ if (!name) {
+ return runGyp()
+ }
+
+ const fullPath = path.resolve(name)
log.verbose(name, 'checking for gypi file: %s', fullPath)
- fs.stat(fullPath, function (err, stat) {
- if (err) {
- if (err.code == 'ENOENT') {
- findConfigs() // check next gypi filename
- } else {
- callback(err)
- }
- } else {
- log.verbose(name, 'found gypi file')
- configs.push(fullPath)
- findConfigs()
+ try {
+ await fs.stat(fullPath)
+ log.verbose(name, 'found gypi file')
+ configs.push(fullPath)
+ } catch (err) {
+ // ENOENT will check next gypi filename
+ if (err.code !== 'ENOENT') {
+ throw err
}
- })
- }
+ }
- function runGyp (err) {
- if (err) return callback(err)
+ return findConfigs()
+ }
+ async function runGyp () {
if (!~argv.indexOf('-f') && !~argv.indexOf('--format')) {
if (win) {
log.verbose('gyp', 'gyp format was not specified; forcing "msvs"')
@@ -225,300 +161,168 @@ function configure (gyp, argv, callback) {
}
}
- function hasMsvsVersion () {
- return argv.some(function (arg) {
- return arg.indexOf('msvs_version') === 0
- })
- }
-
- if (win && !hasMsvsVersion()) {
- if ('msvs_version' in gyp.opts) {
- argv.push('-G', 'msvs_version=' + gyp.opts.msvs_version)
- } else {
- argv.push('-G', 'msvs_version=auto')
- }
- }
-
// include all the ".gypi" files that were found
configs.forEach(function (config) {
argv.push('-I', config)
})
// For AIX and z/OS we need to set up the path to the exports file
- // which contains the symbols needed for linking.
- var node_exp_file = undefined
- if (process.platform === 'aix' || process.platform === 'os390') {
- var ext = process.platform === 'aix' ? 'exp' : 'x'
- var node_root_dir = findNodeDirectory()
- var candidates = undefined
- if (process.platform === 'aix') {
- candidates = ['include/node/node',
- 'out/Release/node',
- 'out/Debug/node',
- 'node'
- ].map(function(file) {
- return file + '.' + ext
- })
+ // which contains the symbols needed for linking.
+ let nodeExpFile
+ let nodeRootDir
+ let candidates
+ let logprefix = 'find exports file'
+ if (process.platform === 'aix' || process.platform === 'os390' || process.platform === 'os400') {
+ const ext = process.platform === 'os390' ? 'x' : 'exp'
+ nodeRootDir = findNodeDirectory()
+
+ if (process.platform === 'aix' || process.platform === 'os400') {
+ candidates = [
+ 'include/node/node',
+ 'out/Release/node',
+ 'out/Debug/node',
+ 'node'
+ ].map(function (file) {
+ return file + '.' + ext
+ })
} else {
- candidates = ['out/Release/obj.target/libnode',
- 'out/Debug/obj.target/libnode',
- 'lib/libnode'
- ].map(function(file) {
- return file + '.' + ext
- })
+ candidates = [
+ 'out/Release/lib.target/libnode',
+ 'out/Debug/lib.target/libnode',
+ 'out/Release/obj.target/libnode',
+ 'out/Debug/obj.target/libnode',
+ 'lib/libnode'
+ ].map(function (file) {
+ return file + '.' + ext
+ })
}
- var logprefix = 'find exports file'
- node_exp_file = findAccessibleSync(logprefix, node_root_dir, candidates)
- if (node_exp_file !== undefined) {
- log.verbose(logprefix, 'Found exports file: %s', node_exp_file)
+
+ nodeExpFile = findAccessibleSync(logprefix, nodeRootDir, candidates)
+ if (nodeExpFile !== undefined) {
+ log.verbose(logprefix, 'Found exports file: %s', nodeExpFile)
} else {
- var msg = msgFormat('Could not find node.%s file in %s', ext, node_root_dir)
+ const msg = msgFormat('Could not find node.%s file in %s', ext, nodeRootDir)
log.error(logprefix, 'Could not find exports file')
- return callback(new Error(msg))
+ throw new Error(msg)
}
}
- // this logic ported from the old `gyp_addon` python file
- var gyp_script = path.resolve(__dirname, '..', 'gyp', 'gyp_main.py')
- var addon_gypi = path.resolve(__dirname, '..', 'addon.gypi')
- var common_gypi = path.resolve(nodeDir, 'include/node/common.gypi')
- fs.stat(common_gypi, function (err, stat) {
- if (err)
- common_gypi = path.resolve(nodeDir, 'common.gypi')
-
- var output_dir = 'build'
- if (win) {
- // Windows expects an absolute path
- output_dir = buildDir
+ // For z/OS we need to set up the path to zoslib include directory,
+ // which contains headers included in v8config.h.
+ let zoslibIncDir
+ if (process.platform === 'os390') {
+ logprefix = "find zoslib's zos-base.h:"
+ let msg
+ let zoslibIncPath = process.env.ZOSLIB_INCLUDES
+ if (zoslibIncPath) {
+ zoslibIncPath = findAccessibleSync(logprefix, zoslibIncPath, ['zos-base.h'])
+ if (zoslibIncPath === undefined) {
+ msg = msgFormat('Could not find zos-base.h file in the directory set ' +
+ 'in ZOSLIB_INCLUDES environment variable: %s; set it ' +
+ 'to the correct path, or unset it to search %s', process.env.ZOSLIB_INCLUDES, nodeRootDir)
+ }
+ } else {
+ candidates = [
+ 'include/node/zoslib/zos-base.h',
+ 'include/zoslib/zos-base.h',
+ 'zoslib/include/zos-base.h',
+ 'install/include/node/zoslib/zos-base.h'
+ ]
+ zoslibIncPath = findAccessibleSync(logprefix, nodeRootDir, candidates)
+ if (zoslibIncPath === undefined) {
+ msg = msgFormat('Could not find any of %s in directory %s; set ' +
+ 'environmant variable ZOSLIB_INCLUDES to the path ' +
+ 'that contains zos-base.h', candidates.toString(), nodeRootDir)
+ }
}
- var nodeGypDir = path.resolve(__dirname, '..')
- var nodeLibFile = path.join(nodeDir,
- !gyp.opts.nodedir ? '<(target_arch)' : '$(Configuration)',
- release.name + '.lib')
-
- argv.push('-I', addon_gypi)
- argv.push('-I', common_gypi)
- argv.push('-Dlibrary=shared_library')
- argv.push('-Dvisibility=default')
- argv.push('-Dnode_root_dir=' + nodeDir)
- if (process.platform === 'aix' || process.platform === 'os390') {
- argv.push('-Dnode_exp_file=' + node_exp_file)
+ if (zoslibIncPath !== undefined) {
+ zoslibIncDir = path.dirname(zoslibIncPath)
+ log.verbose(logprefix, "Found zoslib's zos-base.h in: %s", zoslibIncDir)
+ } else if (release.version.split('.')[0] >= 16) {
+ // zoslib is only shipped in Node v16 and above.
+ log.error(logprefix, msg)
+ throw new Error(msg)
}
- argv.push('-Dnode_gyp_dir=' + nodeGypDir)
- argv.push('-Dnode_lib_file=' + nodeLibFile)
- argv.push('-Dmodule_root_dir=' + process.cwd())
- argv.push('-Dnode_engine=' +
- (gyp.opts.node_engine || process.jsEngine || 'v8'))
- argv.push('--depth=.')
- argv.push('--no-parallel')
-
- // tell gyp to write the Makefile/Solution files into output_dir
- argv.push('--generator-output', output_dir)
-
- // tell make to write its output into the same dir
- argv.push('-Goutput_dir=.')
-
- // enforce use of the "binding.gyp" file
- argv.unshift('binding.gyp')
+ }
- // execute `gyp` from the current target nodedir
- argv.unshift(gyp_script)
+ // this logic ported from the old `gyp_addon` python file
+ const gypScript = path.resolve(__dirname, '..', 'gyp', 'gyp_main.py')
+ const addonGypi = path.resolve(__dirname, '..', 'addon.gypi')
+ let commonGypi = path.resolve(nodeDir, 'include/node/common.gypi')
+ try {
+ await fs.stat(commonGypi)
+ } catch (err) {
+ commonGypi = path.resolve(nodeDir, 'common.gypi')
+ }
- // make sure python uses files that came with this particular node package
- var pypath = [path.join(__dirname, '..', 'gyp', 'pylib')]
- if (process.env.PYTHONPATH) {
- pypath.push(process.env.PYTHONPATH)
+ let outputDir = 'build'
+ if (win) {
+ // Windows expects an absolute path
+ outputDir = buildDir
+ }
+ const nodeGypDir = path.resolve(__dirname, '..')
+
+ let nodeLibFile = path.join(nodeDir,
+ !gyp.opts.nodedir ? '<(target_arch)' : '$(Configuration)',
+ release.name + '.lib')
+
+ argv.push('-I', addonGypi)
+ argv.push('-I', commonGypi)
+ argv.push('-Dlibrary=shared_library')
+ argv.push('-Dvisibility=default')
+ argv.push('-Dnode_root_dir=' + nodeDir)
+ if (process.platform === 'aix' || process.platform === 'os390' || process.platform === 'os400') {
+ argv.push('-Dnode_exp_file=' + nodeExpFile)
+ if (process.platform === 'os390' && zoslibIncDir) {
+ argv.push('-Dzoslib_include_dir=' + zoslibIncDir)
}
- process.env.PYTHONPATH = pypath.join(win ? ';' : ':')
-
- var cp = gyp.spawn(python, argv)
- cp.on('exit', onCpExit)
- })
- }
-
- /**
- * Called when the `gyp` child process exits.
- */
+ }
+ argv.push('-Dnode_gyp_dir=' + nodeGypDir)
- function onCpExit (code, signal) {
- if (code !== 0) {
- callback(new Error('`gyp` failed with exit code: ' + code))
- } else {
- // we're done
- callback()
+ // Do this to keep Cygwin environments happy, else the unescaped '\' gets eaten up,
+ // resulting in bad paths, Ex c:parentFolderfolderanotherFolder instead of c:\parentFolder\folder\anotherFolder
+ if (win) {
+ nodeLibFile = nodeLibFile.replace(/\\/g, '\\\\')
}
- }
+ argv.push('-Dnode_lib_file=' + nodeLibFile)
+ argv.push('-Dmodule_root_dir=' + process.cwd())
+ argv.push('-Dnode_engine=' +
+ (gyp.opts.node_engine || process.jsEngine || 'v8'))
+ argv.push('--depth=.')
+ argv.push('--no-parallel')
-}
+ // tell gyp to write the Makefile/Solution files into output_dir
+ argv.push('--generator-output', outputDir)
-/**
- * Returns the first file or directory from an array of candidates that is
- * readable by the current user, or undefined if none of the candidates are
- * readable.
- */
-function findAccessibleSync (logprefix, dir, candidates) {
- for (var next = 0; next < candidates.length; next++) {
- var candidate = path.resolve(dir, candidates[next])
- try {
- var fd = fs.openSync(candidate, 'r')
- } catch (e) {
- // this candidate was not found or not readable, do nothing
- log.silly(logprefix, 'Could not open %s: %s', candidate, e.message)
- continue
- }
- fs.closeSync(fd)
- log.silly(logprefix, 'Found readable %s', candidate)
- return candidate
- }
+ // tell make to write its output into the same dir
+ argv.push('-Goutput_dir=.')
- return undefined
-}
+ // enforce use of the "binding.gyp" file
+ argv.unshift('binding.gyp')
-function PythonFinder(python, callback) {
- this.callback = callback
- this.python = python
-}
+ // execute `gyp` from the current target nodedir
+ argv.unshift(gypScript)
-PythonFinder.prototype = {
- checkPythonLauncherDepth: 0,
- env: process.env,
- execFile: cp.execFile,
- log: log,
- resolve: path.win32 && path.win32.resolve || path.resolve,
- stat: fs.stat,
- which: which,
- win: win,
-
- checkPython: function checkPython () {
- this.log.verbose('check python',
- 'checking for Python executable "%s" in the PATH',
- this.python)
- this.which(this.python, function (err, execPath) {
- if (err) {
- this.log.verbose('`which` failed', this.python, err)
- if (this.python === 'python2') {
- this.python = 'python'
- return this.checkPython()
- }
- if (this.win) {
- this.checkPythonLauncher()
- } else {
- this.failNoPython()
- }
- } else {
- this.log.verbose('`which` succeeded', this.python, execPath)
- // Found the `python` executable, and from now on we use it explicitly.
- // This solves #667 and #750 (`execFile` won't run batch files
- // (*.cmd, and *.bat))
- this.python = execPath
- this.checkPythonVersion()
- }
- }.bind(this))
- },
-
- // Distributions of Python on Windows by default install with the "py.exe"
- // Python launcher which is more likely to exist than the Python executable
- // being in the $PATH.
- // Because the Python launcher supports all versions of Python, we have to
- // explicitly request a Python 2 version. This is done by supplying "-2" as
- // the first command line argument. Since "py.exe -2" would be an invalid
- // executable for "execFile", we have to use the launcher to figure out
- // where the actual "python.exe" executable is located.
- checkPythonLauncher: function checkPythonLauncher () {
- this.checkPythonLauncherDepth += 1
-
- this.log.verbose(
- 'could not find "' + this.python + '". checking python launcher')
- var env = extend({}, this.env)
- env.TERM = 'dumb'
-
- var launcherArgs = ['-2', '-c', 'import sys; print sys.executable']
- this.execFile('py.exe', launcherArgs, { env: env }, function (err, stdout) {
- if (err) {
- this.guessPython()
- } else {
- this.python = stdout.trim()
- this.log.verbose('check python launcher',
- 'python executable found: %j',
- this.python)
- this.checkPythonVersion()
- }
- this.checkPythonLauncherDepth -= 1
- }.bind(this))
- },
-
- checkPythonVersion: function checkPythonVersion () {
- var args = ['-c', 'import sys; print("%s.%s.%s" % sys.version_info[:3]);']
- var env = extend({}, this.env)
- env.TERM = 'dumb'
-
- this.execFile(this.python, args, { env: env }, function (err, stdout) {
- if (err) {
- return this.callback(err)
- }
- this.log.verbose('check python version',
- '`%s -c "' + args[1] + '"` returned: %j',
- this.python, stdout)
- var version = stdout.trim()
- var range = semver.Range('>=2.5.0 <3.0.0')
- var valid = false
- try {
- valid = range.test(version)
- } catch (e) {
- this.log.silly('range.test() error', e)
- }
- if (valid) {
- this.callback(null, this.python)
- } else if (this.win && this.checkPythonLauncherDepth === 0) {
- this.checkPythonLauncher()
- } else {
- this.failPythonVersion(version)
- }
- }.bind(this))
- },
-
- failNoPython: function failNoPython () {
- var errmsg =
- 'Can\'t find Python executable "' + this.python +
- '", you can set the PYTHON env variable.'
- this.callback(new Error(errmsg))
- },
-
- failPythonVersion: function failPythonVersion (badVersion) {
- var errmsg =
- 'Python executable "' + this.python +
- '" is v' + badVersion + ', which is not supported by gyp.\n' +
- 'You can pass the --python switch to point to ' +
- 'Python >= v2.5.0 & < 3.0.0.'
- this.callback(new Error(errmsg))
- },
-
- // Called on Windows when "python" isn't available in the current $PATH.
- // We are going to check if "%SystemDrive%\python27\python.exe" exists.
- guessPython: function guessPython () {
- this.log.verbose('could not find "' + this.python + '". guessing location')
- var rootDir = this.env.SystemDrive || 'C:\\'
- if (rootDir[rootDir.length - 1] !== '\\') {
- rootDir += '\\'
+ // make sure python uses files that came with this particular node package
+ const pypath = [path.join(__dirname, '..', 'gyp', 'pylib')]
+ if (process.env.PYTHONPATH) {
+ pypath.push(process.env.PYTHONPATH)
}
- var pythonPath = this.resolve(rootDir, 'Python27', 'python.exe')
- this.log.verbose('ensuring that file exists:', pythonPath)
- this.stat(pythonPath, function (err, stat) {
- if (err) {
- if (err.code == 'ENOENT') {
- this.failNoPython()
+ process.env.PYTHONPATH = pypath.join(win ? ';' : ':')
+
+ await new Promise((resolve, reject) => {
+ const cp = gyp.spawn(python, argv)
+ cp.on('exit', (code) => {
+ if (code !== 0) {
+ reject(new Error('`gyp` failed with exit code: ' + code))
} else {
- this.callback(err)
+ // we're done
+ resolve()
}
- return
- }
- this.python = pythonPath
- this.checkPythonVersion()
- }.bind(this))
- },
+ })
+ })
+ }
}
-function findPython (python, callback) {
- var finder = new PythonFinder(python, callback)
- finder.checkPython()
-}
+module.exports = configure
+module.exports.usage = 'Generates ' + (win ? 'MSVC project files' : 'a Makefile') + ' for the current module'
diff --git a/lib/create-config-gypi.js b/lib/create-config-gypi.js
new file mode 100644
index 0000000000..d471da5169
--- /dev/null
+++ b/lib/create-config-gypi.js
@@ -0,0 +1,158 @@
+'use strict'
+
+const fs = require('graceful-fs').promises
+const log = require('./log')
+const path = require('path')
+
+function parseConfigGypi (config) {
+ // translated from tools/js2c.py of Node.js
+ // 1. string comments
+ config = config.replace(/#.*/g, '')
+ // 2. join multiline strings
+ config = config.replace(/'$\s+'/mg, '')
+ // 3. normalize string literals from ' into "
+ config = config.replace(/'/g, '"')
+ return JSON.parse(config)
+}
+
+async function getBaseConfigGypi ({ gyp, nodeDir }) {
+ // try reading $nodeDir/include/node/config.gypi first when:
+ // 1. --dist-url or --nodedir is specified
+ // 2. and --force-process-config is not specified
+ const useCustomHeaders = gyp.opts.nodedir || gyp.opts.disturl || gyp.opts['dist-url']
+ const shouldReadConfigGypi = useCustomHeaders && !gyp.opts['force-process-config']
+ if (shouldReadConfigGypi && nodeDir) {
+ try {
+ const baseConfigGypiPath = path.resolve(nodeDir, 'include/node/config.gypi')
+ const baseConfigGypi = await fs.readFile(baseConfigGypiPath)
+ return parseConfigGypi(baseConfigGypi.toString())
+ } catch (err) {
+ log.warn('read config.gypi', err.message)
+ }
+ }
+
+ // fallback to process.config if it is invalid
+ return JSON.parse(JSON.stringify(process.config))
+}
+
+async function getCurrentConfigGypi ({ gyp, nodeDir, vsInfo, python }) {
+ const config = await getBaseConfigGypi({ gyp, nodeDir })
+ if (!config.target_defaults) {
+ config.target_defaults = {}
+ }
+ if (!config.variables) {
+ config.variables = {}
+ }
+
+ const defaults = config.target_defaults
+ const variables = config.variables
+
+ // don't inherit the "defaults" from the base config.gypi.
+ // doing so could cause problems in cases where the `node` executable was
+ // compiled on a different machine (with different lib/include paths) than
+ // the machine where the addon is being built to
+ defaults.cflags = []
+ defaults.defines = []
+ defaults.include_dirs = []
+ defaults.libraries = []
+
+ // set the default_configuration prop
+ if ('debug' in gyp.opts) {
+ defaults.default_configuration = gyp.opts.debug ? 'Debug' : 'Release'
+ }
+
+ if (!defaults.default_configuration) {
+ defaults.default_configuration = 'Release'
+ }
+
+ // set the target_arch variable
+ variables.target_arch = gyp.opts.arch || process.arch || 'ia32'
+ if (variables.target_arch === 'arm64') {
+ defaults.msvs_configuration_platform = 'ARM64'
+ defaults.xcode_configuration_platform = 'arm64'
+ }
+
+ // set the node development directory
+ variables.nodedir = nodeDir
+
+ // set the configured Python path
+ variables.python = python
+
+ // disable -T "thin" static archives by default
+ variables.standalone_static_library = gyp.opts.thin ? 0 : 1
+
+ if (process.platform === 'win32') {
+ defaults.msbuild_toolset = vsInfo.toolset
+ if (vsInfo.sdk) {
+ defaults.msvs_windows_target_platform_version = vsInfo.sdk
+ }
+ if (variables.target_arch === 'arm64') {
+ if (vsInfo.versionMajor > 15 ||
+ (vsInfo.versionMajor === 15 && vsInfo.versionMajor >= 9)) {
+ defaults.msvs_enable_marmasm = 1
+ } else {
+ log.warn('Compiling ARM64 assembly is only available in\n' +
+ 'Visual Studio 2017 version 15.9 and above')
+ }
+ }
+ variables.msbuild_path = vsInfo.msBuild
+ if (config.variables.clang === 1) {
+ config.variables.clang = 0
+ }
+ // disable LTO for addon builds. node release builds may enable (thin) LTO,
+ // which leaks clang/lld-only flags like -flto=thin and /opt:lldltojobs into
+ // addons built with the default MSVC toolchain, which rejects those flags
+ variables.enable_lto = 'false'
+ variables.enable_thin_lto = 'false'
+ }
+
+ // loop through the rest of the opts and add the unknown ones as variables.
+ // this allows for module-specific configure flags like:
+ //
+ // $ node-gyp configure --shared-libxml2
+ Object.keys(gyp.opts).forEach(function (opt) {
+ if (opt === 'argv') {
+ return
+ }
+ if (opt in gyp.configDefs) {
+ return
+ }
+ variables[opt.replace(/-/g, '_')] = gyp.opts[opt]
+ })
+
+ return config
+}
+
+async function createConfigGypi ({ gyp, buildDir, nodeDir, vsInfo, python }) {
+ const configFilename = 'config.gypi'
+ const configPath = path.resolve(buildDir, configFilename)
+
+ log.verbose('build/' + configFilename, 'creating config file')
+
+ const config = await getCurrentConfigGypi({ gyp, nodeDir, vsInfo, python })
+
+ // ensures that any boolean values in config.gypi get stringified
+ function boolsToString (k, v) {
+ if (typeof v === 'boolean') {
+ return String(v)
+ }
+ return v
+ }
+
+ log.silly('build/' + configFilename, config)
+
+ // now write out the config.gypi file to the build/ dir
+ const prefix = '# Do not edit. File was generated by node-gyp\'s "configure" step'
+
+ const json = JSON.stringify(config, boolsToString, 2)
+ log.verbose('build/' + configFilename, 'writing out config file: %s', configPath)
+ await fs.writeFile(configPath, [prefix, json, ''].join('\n'))
+
+ return configPath
+}
+
+module.exports = {
+ createConfigGypi,
+ parseConfigGypi,
+ getCurrentConfigGypi
+}
diff --git a/lib/download.js b/lib/download.js
new file mode 100644
index 0000000000..dfaca798cf
--- /dev/null
+++ b/lib/download.js
@@ -0,0 +1,86 @@
+const { Readable } = require('stream')
+const { Agent, EnvHttpProxyAgent, RetryAgent, fetch } = require('undici')
+const { promises: fs } = require('graceful-fs')
+const log = require('./log')
+
+async function download (gyp, url) {
+ log.http('GET', url)
+
+ const requestOpts = {
+ headers: {
+ 'User-Agent': `node-gyp v${gyp.version} (node ${process.version})`,
+ Connection: 'keep-alive'
+ },
+ dispatcher: await createDispatcher(gyp)
+ }
+
+ let res
+ try {
+ res = await fetch(url, requestOpts)
+ } catch (err) {
+ // Built-in fetch wraps low-level errors in "TypeError: fetch failed" with
+ // the underlying error on .cause. Callers inspect .code (e.g. ENOTFOUND).
+ if (err.cause) {
+ throw err.cause
+ }
+ throw err
+ }
+
+ log.http(res.status, res.url)
+
+ const body = res.body ? Readable.fromWeb(res.body) : Readable.from([])
+ return {
+ status: res.status,
+ url: res.url,
+ body,
+ text: async () => {
+ let data = ''
+ body.setEncoding('utf8')
+ for await (const chunk of body) {
+ data += chunk
+ }
+ return data
+ }
+ }
+}
+
+async function createDispatcher (gyp) {
+ const env = process.env
+ const hasProxyEnv = env.http_proxy || env.HTTP_PROXY || env.https_proxy || env.HTTPS_PROXY
+ if (!gyp.opts.proxy && !gyp.opts.cafile && !hasProxyEnv) {
+ return new RetryAgent(new Agent(), { maxRetries: 3 })
+ }
+
+ const opts = {}
+ if (gyp.opts.cafile) {
+ const ca = await readCAFile(gyp.opts.cafile)
+ // EnvHttpProxyAgent forwards opts to both its internal Agent (direct) and
+ // ProxyAgent (proxied). Agent reads TLS config from `connect`; ProxyAgent
+ // reads it from `requestTls` (origin) / `proxyTls` (proxy). Set all three
+ // so the custom CA is applied regardless of which path a request takes.
+ opts.connect = { ca }
+ opts.requestTls = { ca }
+ opts.proxyTls = { ca }
+ }
+ if (gyp.opts.proxy) {
+ opts.httpProxy = gyp.opts.proxy
+ opts.httpsProxy = gyp.opts.proxy
+ }
+ if (gyp.opts.noproxy) {
+ opts.noProxy = gyp.opts.noproxy
+ }
+ return new RetryAgent(new EnvHttpProxyAgent(opts), { maxRetries: 3 })
+}
+
+async function readCAFile (filename) {
+ // The CA file can contain multiple certificates so split on certificate
+ // boundaries. [\S\s]*? is used to match everything including newlines.
+ const ca = await fs.readFile(filename, 'utf8')
+ const re = /(-----BEGIN CERTIFICATE-----[\S\s]*?-----END CERTIFICATE-----)/g
+ return ca.match(re)
+}
+
+module.exports = {
+ download,
+ readCAFile
+}
diff --git a/lib/find-node-directory.js b/lib/find-node-directory.js
index 3aee8a109a..8838b81d33 100644
--- a/lib/find-node-directory.js
+++ b/lib/find-node-directory.js
@@ -1,7 +1,9 @@
-var path = require('path')
- , log = require('npmlog')
+'use strict'
-function findNodeDirectory(scriptLocation, processObj) {
+const path = require('path')
+const log = require('./log')
+
+function findNodeDirectory (scriptLocation, processObj) {
// set dirname and process if not passed in
// this facilitates regression tests
if (scriptLocation === undefined) {
@@ -12,50 +14,50 @@ function findNodeDirectory(scriptLocation, processObj) {
}
// Have a look to see what is above us, to try and work out where we are
- npm_parent_directory = path.join(scriptLocation, '../../../..')
- log.verbose('node-gyp root', 'npm_parent_directory is '
- + path.basename(npm_parent_directory))
- node_root_dir = ""
+ const npmParentDirectory = path.join(scriptLocation, '../../../..')
+ log.verbose('node-gyp root', 'npm_parent_directory is ' +
+ path.basename(npmParentDirectory))
+ let nodeRootDir = ''
log.verbose('node-gyp root', 'Finding node root directory')
- if (path.basename(npm_parent_directory) === 'deps') {
+ if (path.basename(npmParentDirectory) === 'deps') {
// We are in a build directory where this script lives in
// deps/npm/node_modules/node-gyp/lib
- node_root_dir = path.join(npm_parent_directory, '..')
- log.verbose('node-gyp root', 'in build directory, root = '
- + node_root_dir)
- } else if (path.basename(npm_parent_directory) === 'node_modules') {
+ nodeRootDir = path.join(npmParentDirectory, '..')
+ log.verbose('node-gyp root', 'in build directory, root = ' +
+ nodeRootDir)
+ } else if (path.basename(npmParentDirectory) === 'node_modules') {
// We are in a node install directory where this script lives in
// lib/node_modules/npm/node_modules/node-gyp/lib or
// node_modules/npm/node_modules/node-gyp/lib depending on the
// platform
if (processObj.platform === 'win32') {
- node_root_dir = path.join(npm_parent_directory, '..')
+ nodeRootDir = path.join(npmParentDirectory, '..')
} else {
- node_root_dir = path.join(npm_parent_directory, '../..')
+ nodeRootDir = path.join(npmParentDirectory, '../..')
}
- log.verbose('node-gyp root', 'in install directory, root = '
- + node_root_dir)
+ log.verbose('node-gyp root', 'in install directory, root = ' +
+ nodeRootDir)
} else {
// We don't know where we are, try working it out from the location
// of the node binary
- var node_dir = path.dirname(processObj.execPath)
- var directory_up = path.basename(node_dir)
- if (directory_up === 'bin') {
- node_root_dir = path.join(node_dir, '..')
- } else if (directory_up === 'Release' || directory_up === 'Debug') {
+ const nodeDir = path.dirname(processObj.execPath)
+ const directoryUp = path.basename(nodeDir)
+ if (directoryUp === 'bin') {
+ nodeRootDir = path.join(nodeDir, '..')
+ } else if (directoryUp === 'Release' || directoryUp === 'Debug') {
// If we are a recently built node, and the directory structure
// is that of a repository. If we are on Windows then we only need
// to go one level up, everything else, two
if (processObj.platform === 'win32') {
- node_root_dir = path.join(node_dir, '..')
+ nodeRootDir = path.join(nodeDir, '..')
} else {
- node_root_dir = path.join(node_dir, '../..')
+ nodeRootDir = path.join(nodeDir, '../..')
}
}
// Else return the default blank, "".
}
- return node_root_dir
+ return nodeRootDir
}
module.exports = findNodeDirectory
diff --git a/lib/find-python.js b/lib/find-python.js
new file mode 100644
index 0000000000..273b5957b4
--- /dev/null
+++ b/lib/find-python.js
@@ -0,0 +1,304 @@
+'use strict'
+
+const log = require('./log')
+const semver = require('semver')
+const { execFile } = require('./util')
+const win = process.platform === 'win32'
+
+function getOsUserInfo () {
+ try {
+ return require('os').userInfo().username
+ } catch {}
+}
+
+const systemDrive = process.env.SystemDrive || 'C:'
+const username = process.env.USERNAME || process.env.USER || getOsUserInfo()
+const localAppData = process.env.LOCALAPPDATA || `${systemDrive}\\${username}\\AppData\\Local`
+const foundLocalAppData = process.env.LOCALAPPDATA || username
+const programFiles = process.env.ProgramW6432 || process.env.ProgramFiles || `${systemDrive}\\Program Files`
+const programFilesX86 = process.env['ProgramFiles(x86)'] || `${programFiles} (x86)`
+
+const winDefaultLocationsArray = []
+for (const majorMinor of ['311', '310', '39', '38']) {
+ if (foundLocalAppData) {
+ winDefaultLocationsArray.push(
+ `${localAppData}\\Programs\\Python\\Python${majorMinor}\\python.exe`,
+ `${programFiles}\\Python${majorMinor}\\python.exe`,
+ `${localAppData}\\Programs\\Python\\Python${majorMinor}-32\\python.exe`,
+ `${programFiles}\\Python${majorMinor}-32\\python.exe`,
+ `${programFilesX86}\\Python${majorMinor}-32\\python.exe`
+ )
+ } else {
+ winDefaultLocationsArray.push(
+ `${programFiles}\\Python${majorMinor}\\python.exe`,
+ `${programFiles}\\Python${majorMinor}-32\\python.exe`,
+ `${programFilesX86}\\Python${majorMinor}-32\\python.exe`
+ )
+ }
+}
+
+class PythonFinder {
+ static findPython = (...args) => new PythonFinder(...args).findPython()
+
+ log = log.withPrefix('find Python')
+ argsExecutable = ['-c', 'import sys; sys.stdout.buffer.write(sys.executable.encode(\'utf-8\'));']
+ argsVersion = ['-c', 'import sys; print("%s.%s.%s" % sys.version_info[:3]);']
+ semverRange = '>=3.6.0'
+
+ // These can be overridden for testing:
+ execFile = execFile
+ env = process.env
+ win = win
+ pyLauncher = 'py.exe'
+ winDefaultLocations = winDefaultLocationsArray
+
+ constructor (configPython) {
+ this.configPython = configPython
+ this.errorLog = []
+ }
+
+ // Logs a message at verbose level, but also saves it to be displayed later
+ // at error level if an error occurs. This should help diagnose the problem.
+ addLog (message) {
+ this.log.verbose(message)
+ this.errorLog.push(message)
+ }
+
+ // Find Python by trying a sequence of possibilities.
+ // Ignore errors, keep trying until Python is found.
+ async findPython () {
+ const SKIP = 0
+ const FAIL = 1
+ const toCheck = (() => {
+ if (this.env.NODE_GYP_FORCE_PYTHON) {
+ return [{
+ before: () => {
+ this.addLog(
+ 'checking Python explicitly set from NODE_GYP_FORCE_PYTHON')
+ this.addLog('- process.env.NODE_GYP_FORCE_PYTHON is ' +
+ `"${this.env.NODE_GYP_FORCE_PYTHON}"`)
+ },
+ check: () => this.checkCommand(this.env.NODE_GYP_FORCE_PYTHON)
+ }]
+ }
+
+ const checks = [
+ {
+ before: () => {
+ if (!this.configPython) {
+ this.addLog('--python was not set on the command line')
+ return SKIP
+ }
+ this.addLog(`--python=${this.configPython} was set on the command line`)
+ },
+ check: () => this.checkCommand(this.configPython)
+ },
+ {
+ before: () => {
+ if (!this.env.PYTHON) {
+ this.addLog('Python is not set from environment variable ' +
+ 'PYTHON')
+ return SKIP
+ }
+ this.addLog('checking Python explicitly set from environment ' +
+ 'variable PYTHON')
+ this.addLog(`- process.env.PYTHON is "${this.env.PYTHON}"`)
+ },
+ check: () => this.checkCommand(this.env.PYTHON)
+ }
+ ]
+
+ if (this.win) {
+ checks.push({
+ before: () => {
+ this.addLog(
+ 'checking if the py launcher can be used to find Python 3')
+ },
+ check: () => this.checkPyLauncher()
+ })
+ }
+
+ checks.push(...[
+ {
+ before: () => { this.addLog('checking if "python3" can be used') },
+ check: () => this.checkCommand('python3')
+ },
+ {
+ before: () => { this.addLog('checking if "python" can be used') },
+ check: () => this.checkCommand('python')
+ }
+ ])
+
+ if (this.win) {
+ for (let i = 0; i < this.winDefaultLocations.length; ++i) {
+ const location = this.winDefaultLocations[i]
+ checks.push({
+ before: () => this.addLog(`checking if Python is ${location}`),
+ check: () => this.checkExecPath(location)
+ })
+ }
+ }
+
+ return checks
+ })()
+
+ for (const check of toCheck) {
+ const before = check.before()
+ if (before === SKIP) {
+ continue
+ }
+ if (before === FAIL) {
+ return this.fail()
+ }
+ try {
+ return await check.check()
+ } catch (err) {
+ this.log.silly('runChecks: err = %j', (err && err.stack) || err)
+ }
+ }
+
+ return this.fail()
+ }
+
+ // Check if command is a valid Python to use.
+ // Will exit the Python finder on success.
+ // If on Windows, run in a CMD shell to support BAT/CMD launchers.
+ async checkCommand (command) {
+ let exec = command
+ let args = this.argsExecutable
+ let shell = false
+ if (this.win) {
+ // Arguments have to be manually quoted
+ exec = `"${exec}"`
+ args = args.map(a => `"${a}"`)
+ shell = true
+ }
+
+ this.log.verbose(`- executing "${command}" to get executable path`)
+ // Possible outcomes:
+ // - Error: not in PATH, not executable or execution fails
+ // - Gibberish: the next command to check version will fail
+ // - Absolute path to executable
+ try {
+ const execPath = await this.run(exec, args, shell)
+ this.addLog(`- executable path is "${execPath}"`)
+ return this.checkExecPath(execPath)
+ } catch (err) {
+ this.addLog(`- "${command}" is not in PATH or produced an error`)
+ throw err
+ }
+ }
+
+ // Check if the py launcher can find a valid Python to use.
+ // Will exit the Python finder on success.
+ // Distributions of Python on Windows by default install with the "py.exe"
+ // Python launcher which is more likely to exist than the Python executable
+ // being in the $PATH.
+ // Because the Python launcher supports Python 2 and Python 3, we should
+ // explicitly request a Python 3 version. This is done by supplying "-3" as
+ // the first command line argument. Since "py.exe -3" would be an invalid
+ // executable for "execFile", we have to use the launcher to figure out
+ // where the actual "python.exe" executable is located.
+ async checkPyLauncher () {
+ this.log.verbose(`- executing "${this.pyLauncher}" to get Python 3 executable path`)
+ // Possible outcomes: same as checkCommand
+ try {
+ const execPath = await this.run(this.pyLauncher, ['-3', ...this.argsExecutable], false)
+ this.addLog(`- executable path is "${execPath}"`)
+ return this.checkExecPath(execPath)
+ } catch (err) {
+ this.addLog(`- "${this.pyLauncher}" is not in PATH or produced an error`)
+ throw err
+ }
+ }
+
+ // Check if a Python executable is the correct version to use.
+ // Will exit the Python finder on success.
+ async checkExecPath (execPath) {
+ this.log.verbose(`- executing "${execPath}" to get version`)
+ // Possible outcomes:
+ // - Error: executable can not be run (likely meaning the command wasn't
+ // a Python executable and the previous command produced gibberish)
+ // - Gibberish: somehow the last command produced an executable path,
+ // this will fail when verifying the version
+ // - Version of the Python executable
+ try {
+ const version = await this.run(execPath, this.argsVersion, false)
+ this.addLog(`- version is "${version}"`)
+
+ const range = new semver.Range(this.semverRange)
+ let valid = false
+ try {
+ valid = range.test(version)
+ } catch (err) {
+ this.log.silly('range.test() threw:\n%s', err.stack)
+ this.addLog(`- "${execPath}" does not have a valid version`)
+ this.addLog('- is it a Python executable?')
+ throw err
+ }
+ if (!valid) {
+ this.addLog(`- version is ${version} - should be ${this.semverRange}`)
+ this.addLog('- THIS VERSION OF PYTHON IS NOT SUPPORTED')
+ throw new Error(`Found unsupported Python version ${version}`)
+ }
+ return this.succeed(execPath, version)
+ } catch (err) {
+ this.addLog(`- "${execPath}" could not be run`)
+ throw err
+ }
+ }
+
+ // Run an executable or shell command, trimming the output.
+ async run (exec, args, shell) {
+ const env = Object.assign({}, this.env)
+ env.TERM = 'dumb'
+ const opts = { env, shell }
+
+ this.log.silly('execFile: exec = %j', exec)
+ this.log.silly('execFile: args = %j', args)
+ this.log.silly('execFile: opts = %j', opts)
+ try {
+ const [err, stdout, stderr] = await this.execFile(exec, args, opts)
+ this.log.silly('execFile result: err = %j', (err && err.stack) || err)
+ this.log.silly('execFile result: stdout = %j', stdout)
+ this.log.silly('execFile result: stderr = %j', stderr)
+ return stdout.trim()
+ } catch (err) {
+ this.log.silly('execFile: threw:\n%s', err.stack)
+ throw err
+ }
+ }
+
+ succeed (execPath, version) {
+ this.log.info(`using Python version ${version} found at "${execPath}"`)
+ return execPath
+ }
+
+ fail () {
+ const errorLog = this.errorLog.join('\n')
+
+ const pathExample = this.win
+ ? 'C:\\Path\\To\\python.exe'
+ : '/path/to/pythonexecutable'
+ // For Windows 80 col console, use up to the column before the one marked
+ // with X (total 79 chars including logger prefix, 58 chars usable here):
+ // X
+ const info = [
+ '**********************************************************',
+ 'You need to install the latest version of Python.',
+ 'Node-gyp should be able to find and use Python. If not,',
+ 'you can try one of the following options:',
+ `- Use the switch --python="${pathExample}"`,
+ ' (accepted by both node-gyp and npm)',
+ '- Set the environment variable PYTHON',
+ 'For more information consult the documentation at:',
+ 'https://github.com/nodejs/node-gyp#installation',
+ '**********************************************************'
+ ].join('\n')
+
+ this.log.error(`\n${errorLog}\n\n${info}\n`)
+ throw new Error('Could not find any Python installation to use')
+ }
+}
+
+module.exports = PythonFinder
diff --git a/lib/find-visualstudio.js b/lib/find-visualstudio.js
new file mode 100644
index 0000000000..efb8b02a59
--- /dev/null
+++ b/lib/find-visualstudio.js
@@ -0,0 +1,606 @@
+'use strict'
+
+const log = require('./log')
+const { existsSync } = require('fs')
+const { win32: path } = require('path')
+const { regSearchKeys, execFile } = require('./util')
+
+class VisualStudioFinder {
+ static findVisualStudio = (...args) => new VisualStudioFinder(...args).findVisualStudio()
+
+ log = log.withPrefix('find VS')
+
+ regSearchKeys = regSearchKeys
+
+ constructor (nodeSemver, configMsvsVersion) {
+ this.nodeSemver = nodeSemver
+ this.configMsvsVersion = configMsvsVersion
+ this.errorLog = []
+ this.validVersions = []
+ }
+
+ // Logs a message at verbose level, but also saves it to be displayed later
+ // at error level if an error occurs. This should help diagnose the problem.
+ addLog (message) {
+ this.log.verbose(message)
+ this.errorLog.push(message)
+ }
+
+ async findVisualStudio () {
+ this.configVersionYear = null
+ this.configPath = null
+ if (this.configMsvsVersion) {
+ this.addLog(`--msvs_version=${this.configMsvsVersion} was set on the command line`)
+ if (this.configMsvsVersion.match(/^\d{4}$/)) {
+ this.configVersionYear = parseInt(this.configMsvsVersion, 10)
+ this.addLog(
+ `- looking for Visual Studio version ${this.configVersionYear}`)
+ } else {
+ this.configPath = path.resolve(this.configMsvsVersion)
+ this.addLog(
+ `- looking for Visual Studio installed in "${this.configPath}"`)
+ }
+ } else {
+ this.addLog('--msvs_version was not set on the command line')
+ }
+
+ if (process.env.VCINSTALLDIR) {
+ this.envVcInstallDir =
+ path.resolve(process.env.VCINSTALLDIR, '..')
+ this.addLog('running in VS Command Prompt, installation path is:\n' +
+ `"${this.envVcInstallDir}"\n- will only use this version`)
+ } else {
+ this.addLog('VCINSTALLDIR not set, not running in VS Command Prompt')
+ }
+
+ const checks = [
+ () => this.findVisualStudio2019OrNewerFromSpecifiedLocation(),
+ () => this.findVisualStudio2019OrNewerUsingSetupModule(),
+ () => this.findVisualStudio2019OrNewer(),
+ () => this.findVisualStudio2017FromSpecifiedLocation(),
+ () => this.findVisualStudio2017UsingSetupModule(),
+ () => this.findVisualStudio2017(),
+ () => this.findVisualStudio2015(),
+ () => this.findVisualStudio2013()
+ ]
+
+ for (const check of checks) {
+ const info = await check()
+ if (info) {
+ return this.succeed(info)
+ }
+ }
+
+ return this.fail()
+ }
+
+ succeed (info) {
+ this.log.info(`using VS${info.versionYear} (${info.version}) found at:` +
+ `\n"${info.path}"` +
+ '\nrun with --verbose for detailed information')
+ return info
+ }
+
+ fail () {
+ if (this.configMsvsVersion && this.envVcInstallDir) {
+ this.errorLog.push(
+ 'msvs_version does not match this VS Command Prompt or the',
+ 'installation cannot be used.')
+ } else if (this.configMsvsVersion) {
+ // If msvs_version was specified but finding VS failed, print what would
+ // have been accepted
+ this.errorLog.push('')
+ if (this.validVersions) {
+ this.errorLog.push('valid versions for msvs_version:')
+ this.validVersions.forEach((version) => {
+ this.errorLog.push(`- "${version}"`)
+ })
+ } else {
+ this.errorLog.push('no valid versions for msvs_version were found')
+ }
+ }
+
+ const errorLog = this.errorLog.join('\n')
+
+ // For Windows 80 col console, use up to the column before the one marked
+ // with X (total 79 chars including logger prefix, 62 chars usable here):
+ // X
+ const infoLog = [
+ '**************************************************************',
+ 'You need to install the latest version of Visual Studio',
+ 'including the "Desktop development with C++" workload.',
+ 'For more information consult the documentation at:',
+ 'https://github.com/nodejs/node-gyp#on-windows',
+ '**************************************************************'
+ ].join('\n')
+
+ this.log.error(`\n${errorLog}\n\n${infoLog}\n`)
+ throw new Error('Could not find any Visual Studio installation to use')
+ }
+
+ async findVisualStudio2019OrNewerFromSpecifiedLocation () {
+ return this.findVSFromSpecifiedLocation([2019, 2022, 2026])
+ }
+
+ async findVisualStudio2017FromSpecifiedLocation () {
+ if (this.nodeSemver.major >= 22) {
+ this.addLog(
+ 'not looking for VS2017 as it is only supported up to Node.js 21')
+ return null
+ }
+ return this.findVSFromSpecifiedLocation([2017])
+ }
+
+ async findVSFromSpecifiedLocation (supportedYears) {
+ if (!this.envVcInstallDir) {
+ return null
+ }
+ const info = {
+ path: path.resolve(this.envVcInstallDir),
+ // Assume the version specified by the user is correct.
+ // Since Visual Studio 2015, the Developer Command Prompt sets the
+ // VSCMD_VER environment variable which contains the version information
+ // for Visual Studio.
+ // https://learn.microsoft.com/en-us/visualstudio/ide/reference/command-prompt-powershell?view=vs-2022
+ version: process.env.VSCMD_VER,
+ packages: [
+ 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
+ 'Microsoft.VisualStudio.Component.VC.Tools.ARM64',
+ // Assume MSBuild exists. It will be checked in processing.
+ 'Microsoft.VisualStudio.VC.MSBuild.Base'
+ ]
+ }
+
+ // Is there a better way to get SDK information?
+ const envWindowsSDKVersion = process.env.WindowsSDKVersion
+ const sdkVersionMatched = envWindowsSDKVersion?.match(/^(\d+)\.(\d+)\.(\d+)\..*/)
+ if (sdkVersionMatched) {
+ info.packages.push(`Microsoft.VisualStudio.Component.Windows10SDK.${sdkVersionMatched[3]}.Desktop`)
+ }
+ // pass for further processing
+ return this.processData([info], supportedYears)
+ }
+
+ async findVisualStudio2019OrNewerUsingSetupModule () {
+ return this.findNewVSUsingSetupModule([2019, 2022, 2026])
+ }
+
+ async findVisualStudio2017UsingSetupModule () {
+ if (this.nodeSemver.major >= 22) {
+ this.addLog(
+ 'not looking for VS2017 as it is only supported up to Node.js 21')
+ return null
+ }
+ return this.findNewVSUsingSetupModule([2017])
+ }
+
+ async findNewVSUsingSetupModule (supportedYears) {
+ const ps = path.join(process.env.SystemRoot, 'System32',
+ 'WindowsPowerShell', 'v1.0', 'powershell.exe')
+ const vcInstallDir = this.envVcInstallDir
+
+ const checkModuleArgs = [
+ '-NoProfile',
+ '-Command',
+ '&{@(Get-Module -ListAvailable -Name VSSetup).Version.ToString()}'
+ ]
+ this.log.silly('Running', ps, checkModuleArgs)
+ const [cErr] = await this.execFile(ps, checkModuleArgs)
+ if (cErr) {
+ this.addLog('VSSetup module doesn\'t seem to exist. You can install it via: "Install-Module VSSetup -Scope CurrentUser"')
+ this.log.silly('VSSetup error = %j', cErr && (cErr.stack || cErr))
+ return null
+ }
+ const filterArg = vcInstallDir !== undefined ? `| where {$_.InstallationPath -eq '${vcInstallDir}' }` : ''
+ const psArgs = [
+ '-NoProfile',
+ '-Command',
+ `&{Get-VSSetupInstance ${filterArg} | ConvertTo-Json -Depth 3}`
+ ]
+
+ this.log.silly('Running', ps, psArgs)
+ const [err, stdout, stderr] = await this.execFile(ps, psArgs)
+ let parsedData = this.parseData(err, stdout, stderr)
+ if (parsedData === null) {
+ return null
+ }
+ this.log.silly('Parsed data', parsedData)
+ if (!Array.isArray(parsedData)) {
+ // if there are only 1 result, then Powershell will output non-array
+ parsedData = [parsedData]
+ }
+ // normalize output
+ parsedData = parsedData.map((info) => {
+ info.path = info.InstallationPath
+ info.version = `${info.InstallationVersion.Major}.${info.InstallationVersion.Minor}.${info.InstallationVersion.Build}.${info.InstallationVersion.Revision}`
+ info.packages = info.Packages.map((p) => p.Id)
+ return info
+ })
+ // pass for further processing
+ return this.processData(parsedData, supportedYears)
+ }
+
+ // Invoke the PowerShell script to get information about Visual Studio 2019
+ // or newer installations
+ async findVisualStudio2019OrNewer () {
+ return this.findNewVS([2019, 2022, 2026])
+ }
+
+ // Invoke the PowerShell script to get information about Visual Studio 2017
+ async findVisualStudio2017 () {
+ if (this.nodeSemver.major >= 22) {
+ this.addLog(
+ 'not looking for VS2017 as it is only supported up to Node.js 21')
+ return null
+ }
+ return this.findNewVS([2017])
+ }
+
+ // Invoke the PowerShell script to get information about Visual Studio 2017
+ // or newer installations
+ async findNewVS (supportedYears) {
+ const ps = path.join(process.env.SystemRoot, 'System32',
+ 'WindowsPowerShell', 'v1.0', 'powershell.exe')
+ const csFile = path.join(__dirname, 'Find-VisualStudio.cs')
+ const psArgs = [
+ '-ExecutionPolicy',
+ 'Unrestricted',
+ '-NoProfile',
+ '-Command',
+ '&{Add-Type -IgnoreWarnings -Path \'' + csFile + '\';' + '[VisualStudioConfiguration.Main]::PrintJson()}'
+ ]
+
+ this.log.silly('Running', ps, psArgs)
+ const [err, stdout, stderr] = await this.execFile(ps, psArgs)
+ const parsedData = this.parseData(err, stdout, stderr, { checkIsArray: true })
+ if (parsedData === null) {
+ return null
+ }
+ return this.processData(parsedData, supportedYears)
+ }
+
+ // Parse the output of the PowerShell script, make sanity checks
+ parseData (err, stdout, stderr, sanityCheckOptions) {
+ const defaultOptions = {
+ checkIsArray: false
+ }
+
+ // Merging provided options with the default options
+ const sanityOptions = { ...defaultOptions, ...sanityCheckOptions }
+
+ this.log.silly('PS stderr = %j', stderr)
+
+ const failPowershell = (failureDetails) => {
+ this.addLog(
+ `could not use PowerShell to find Visual Studio 2017 or newer, try re-running with '--loglevel silly' for more details. \n
+ Failure details: ${failureDetails}`)
+ return null
+ }
+
+ if (err) {
+ this.log.silly('PS err = %j', err && (err.stack || err))
+ return failPowershell(`${err}`.substring(0, 40))
+ }
+
+ let vsInfo
+ try {
+ vsInfo = JSON.parse(stdout)
+ } catch (e) {
+ this.log.silly('PS stdout = %j', stdout)
+ this.log.silly(e)
+ return failPowershell()
+ }
+
+ if (sanityOptions.checkIsArray && !Array.isArray(vsInfo)) {
+ this.log.silly('PS stdout = %j', stdout)
+ return failPowershell('Expected array as output of the PS script')
+ }
+ return vsInfo
+ }
+
+ // Process parsed data containing information about VS installations
+ // Look for the required parts, extract and output them back
+ processData (vsInfo, supportedYears) {
+ vsInfo = vsInfo.map((info) => {
+ this.log.silly(`processing installation: "${info.path}"`)
+ info.path = path.resolve(info.path)
+ const ret = this.getVersionInfo(info)
+ ret.path = info.path
+ ret.msBuild = this.getMSBuild(info, ret.versionYear)
+ ret.toolset = this.getToolset(info, ret.versionYear)
+ ret.sdk = this.getSDK(info)
+ return ret
+ })
+ this.log.silly('vsInfo:', vsInfo)
+
+ // Remove future versions or errors parsing version number
+ // Also remove any unsupported versions
+ vsInfo = vsInfo.filter((info) => {
+ if (info.versionYear && supportedYears.indexOf(info.versionYear) !== -1) {
+ return true
+ }
+ this.addLog(`${info.versionYear ? 'unsupported' : 'unknown'} version "${info.version}" found at "${info.path}"`)
+ return false
+ })
+
+ // Sort to place newer versions first
+ vsInfo.sort((a, b) => b.versionYear - a.versionYear)
+
+ for (let i = 0; i < vsInfo.length; ++i) {
+ const info = vsInfo[i]
+ this.addLog(`checking VS${info.versionYear} (${info.version}) found ` +
+ `at:\n"${info.path}"`)
+
+ if (info.msBuild) {
+ this.addLog('- found "Visual Studio C++ core features"')
+ } else {
+ this.addLog('- "Visual Studio C++ core features" missing')
+ continue
+ }
+
+ if (info.toolset) {
+ this.addLog(`- found VC++ toolset: ${info.toolset}`)
+ } else {
+ this.addLog('- missing any VC++ toolset')
+ continue
+ }
+
+ if (info.sdk) {
+ this.addLog(`- found Windows SDK: ${info.sdk}`)
+ } else {
+ this.addLog('- missing any Windows SDK')
+ continue
+ }
+
+ if (!this.checkConfigVersion(info.versionYear, info.path)) {
+ continue
+ }
+
+ return info
+ }
+
+ this.addLog(
+ 'could not find a version of Visual Studio 2017 or newer to use')
+ return null
+ }
+
+ // Helper - process version information
+ getVersionInfo (info) {
+ const match = /^(\d+)\.(\d+)(?:\..*)?/.exec(info.version)
+ if (!match) {
+ this.log.silly('- failed to parse version:', info.version)
+ return {}
+ }
+ this.log.silly('- version match = %j', match)
+ const ret = {
+ version: info.version,
+ versionMajor: parseInt(match[1], 10),
+ versionMinor: parseInt(match[2], 10)
+ }
+ if (ret.versionMajor === 15) {
+ ret.versionYear = 2017
+ return ret
+ }
+ if (ret.versionMajor === 16) {
+ ret.versionYear = 2019
+ return ret
+ }
+ if (ret.versionMajor === 17) {
+ ret.versionYear = 2022
+ return ret
+ }
+ if (ret.versionMajor === 18) {
+ ret.versionYear = 2026
+ return ret
+ }
+ this.log.silly('- unsupported version:', ret.versionMajor)
+ return {}
+ }
+
+ msBuildPathExists (path) {
+ return existsSync(path)
+ }
+
+ // Helper - process MSBuild information
+ getMSBuild (info, versionYear) {
+ const pkg = 'Microsoft.VisualStudio.VC.MSBuild.Base'
+ const msbuildPath = path.join(info.path, 'MSBuild', 'Current', 'Bin', 'MSBuild.exe')
+ const msbuildPathArm64 = path.join(info.path, 'MSBuild', 'Current', 'Bin', 'arm64', 'MSBuild.exe')
+ if (info.packages.indexOf(pkg) !== -1) {
+ this.log.silly('- found VC.MSBuild.Base')
+ if (versionYear === 2017) {
+ return path.join(info.path, 'MSBuild', '15.0', 'Bin', 'MSBuild.exe')
+ }
+ if (versionYear === 2019) {
+ if (process.arch === 'arm64' && this.msBuildPathExists(msbuildPathArm64)) {
+ return msbuildPathArm64
+ } else {
+ return msbuildPath
+ }
+ }
+ }
+ /**
+ * Visual Studio 2022 doesn't have the MSBuild package.
+ * Support for compiling _on_ ARM64 was added in MSVC 14.32.31326,
+ * so let's leverage it if the user has an ARM64 device.
+ */
+ if (process.arch === 'arm64' && this.msBuildPathExists(msbuildPathArm64)) {
+ return msbuildPathArm64
+ } else if (this.msBuildPathExists(msbuildPath)) {
+ return msbuildPath
+ }
+ return null
+ }
+
+ // Helper - process toolset information
+ getToolset (info, versionYear) {
+ const vcToolsArm64 = 'VC.Tools.ARM64'
+ const pkgArm64 = `Microsoft.VisualStudio.Component.${vcToolsArm64}`
+ const vcToolsX64 = 'VC.Tools.x86.x64'
+ const pkgX64 = `Microsoft.VisualStudio.Component.${vcToolsX64}`
+ const express = 'Microsoft.VisualStudio.WDExpress'
+
+ if (process.arch === 'arm64' && info.packages.includes(pkgArm64)) {
+ this.log.silly(`- found ${vcToolsArm64}`)
+ } else if (info.packages.includes(pkgX64)) {
+ if (process.arch === 'arm64') {
+ this.addLog(`- found ${vcToolsX64} on ARM64 platform. Expect less performance and/or link failure with ARM64 binary.`)
+ } else {
+ this.log.silly(`- found ${vcToolsX64}`)
+ }
+ } else if (info.packages.includes(express)) {
+ this.log.silly('- found Visual Studio Express (looking for toolset)')
+ } else {
+ return null
+ }
+
+ if (versionYear === 2017) {
+ return 'v141'
+ } else if (versionYear === 2019) {
+ return 'v142'
+ } else if (versionYear === 2022) {
+ return 'v143'
+ } else if (versionYear === 2026) {
+ return 'v145'
+ }
+ this.log.silly('- invalid versionYear:', versionYear)
+ return null
+ }
+
+ // Helper - process Windows SDK information
+ getSDK (info) {
+ const win8SDK = 'Microsoft.VisualStudio.Component.Windows81SDK'
+ const win10SDKPrefix = 'Microsoft.VisualStudio.Component.Windows10SDK.'
+ const win11SDKPrefix = 'Microsoft.VisualStudio.Component.Windows11SDK.'
+
+ let Win10or11SDKVer = 0
+ info.packages.forEach((pkg) => {
+ if (!pkg.startsWith(win10SDKPrefix) && !pkg.startsWith(win11SDKPrefix)) {
+ return
+ }
+ const parts = pkg.split('.')
+ if (parts.length > 5 && parts[5] !== 'Desktop') {
+ this.log.silly('- ignoring non-Desktop Win10/11SDK:', pkg)
+ return
+ }
+ const foundSdkVer = parseInt(parts[4], 10)
+ if (isNaN(foundSdkVer)) {
+ // Microsoft.VisualStudio.Component.Windows10SDK.IpOverUsb
+ this.log.silly('- failed to parse Win10/11SDK number:', pkg)
+ return
+ }
+ this.log.silly('- found Win10/11SDK:', foundSdkVer)
+ Win10or11SDKVer = Math.max(Win10or11SDKVer, foundSdkVer)
+ })
+
+ if (Win10or11SDKVer !== 0) {
+ return `10.0.${Win10or11SDKVer}.0`
+ } else if (info.packages.indexOf(win8SDK) !== -1) {
+ this.log.silly('- found Win8SDK')
+ return '8.1'
+ }
+ return null
+ }
+
+ // Find an installation of Visual Studio 2015 to use
+ async findVisualStudio2015 () {
+ if (this.nodeSemver.major >= 19) {
+ this.addLog(
+ 'not looking for VS2015 as it is only supported up to Node.js 18')
+ return null
+ }
+ return this.findOldVS({
+ version: '14.0',
+ versionMajor: 14,
+ versionMinor: 0,
+ versionYear: 2015,
+ toolset: 'v140'
+ })
+ }
+
+ // Find an installation of Visual Studio 2013 to use
+ async findVisualStudio2013 () {
+ if (this.nodeSemver.major >= 9) {
+ this.addLog(
+ 'not looking for VS2013 as it is only supported up to Node.js 8')
+ return null
+ }
+ return this.findOldVS({
+ version: '12.0',
+ versionMajor: 12,
+ versionMinor: 0,
+ versionYear: 2013,
+ toolset: 'v120'
+ })
+ }
+
+ // Helper - common code for VS2013 and VS2015
+ async findOldVS (info) {
+ const regVC7 = ['HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7',
+ 'HKLM\\Software\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VC7']
+ const regMSBuild = 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions'
+
+ this.addLog(`looking for Visual Studio ${info.versionYear}`)
+ try {
+ let res = await this.regSearchKeys(regVC7, info.version, [])
+ const vsPath = path.resolve(res, '..')
+ this.addLog(`- found in "${vsPath}"`)
+ const msBuildRegOpts = process.arch === 'ia32' ? [] : ['/reg:32']
+
+ try {
+ res = await this.regSearchKeys([`${regMSBuild}\\${info.version}`], 'MSBuildToolsPath', msBuildRegOpts)
+ } catch (err) {
+ this.addLog('- could not find MSBuild in registry for this version')
+ return null
+ }
+
+ const msBuild = path.join(res, 'MSBuild.exe')
+ this.addLog(`- MSBuild in "${msBuild}"`)
+
+ if (!this.checkConfigVersion(info.versionYear, vsPath)) {
+ return null
+ }
+
+ info.path = vsPath
+ info.msBuild = msBuild
+ info.sdk = null
+ return info
+ } catch (err) {
+ this.addLog('- not found')
+ return null
+ }
+ }
+
+ // After finding a usable version of Visual Studio:
+ // - add it to validVersions to be displayed at the end if a specific
+ // version was requested and not found;
+ // - check if this is the version that was requested.
+ // - check if this matches the Visual Studio Command Prompt
+ checkConfigVersion (versionYear, vsPath) {
+ this.validVersions.push(versionYear)
+ this.validVersions.push(vsPath)
+
+ if (this.configVersionYear && this.configVersionYear !== versionYear) {
+ this.addLog('- msvs_version does not match this version')
+ return false
+ }
+ if (this.configPath &&
+ path.relative(this.configPath, vsPath) !== '') {
+ this.addLog('- msvs_version does not point to this installation')
+ return false
+ }
+ if (this.envVcInstallDir &&
+ path.relative(this.envVcInstallDir, vsPath) !== '') {
+ this.addLog('- does not match this Visual Studio Command Prompt')
+ return false
+ }
+
+ return true
+ }
+
+ async execFile (exec, args) {
+ return await execFile(exec, args, { encoding: 'utf8' })
+ }
+}
+
+module.exports = VisualStudioFinder
diff --git a/lib/find-vs2017.js b/lib/find-vs2017.js
deleted file mode 100644
index ad46ceaf88..0000000000
--- a/lib/find-vs2017.js
+++ /dev/null
@@ -1,46 +0,0 @@
-var log = require('npmlog')
- , execFile = require('child_process').execFile
- , path = require('path')
-
-function findVS2017(callback) {
- var ps = path.join(process.env.SystemRoot, 'System32', 'WindowsPowerShell',
- 'v1.0', 'powershell.exe')
- var csFile = path.join(__dirname, 'Find-VS2017.cs')
- var psArgs = ['-ExecutionPolicy', 'Unrestricted', '-NoProfile',
- '-Command', '&{Add-Type -Path \'' + csFile + '\';' +
- '[VisualStudioConfiguration.Main]::Query()}']
-
- log.silly('find vs2017', 'Running', ps, psArgs)
- var child = execFile(ps, psArgs, { encoding: 'utf8' },
- function (err, stdout, stderr) {
- log.silly('find vs2017', 'PS err:', err)
- log.silly('find vs2017', 'PS stdout:', stdout)
- log.silly('find vs2017', 'PS stderr:', stderr)
-
- if (err)
- return callback(new Error('Could not use PowerShell to find VS2017'))
-
- var vsSetup
- try {
- vsSetup = JSON.parse(stdout)
- } catch (e) {
- log.silly('find vs2017', e)
- return callback(new Error('Could not use PowerShell to find VS2017'))
- }
- log.silly('find vs2017', 'vsSetup:', vsSetup)
-
- if (vsSetup && vsSetup.log)
- log.verbose('find vs2017', vsSetup.log.trimRight())
-
- if (!vsSetup || !vsSetup.path || !vsSetup.sdk) {
- return callback(new Error('No usable installation of VS2017 found'))
- }
-
- log.verbose('find vs2017', 'using installation:', vsSetup.path)
- callback(null, { "path": vsSetup.path, "sdk": vsSetup.sdk })
- })
-
- child.stdin.end()
-}
-
-module.exports = findVS2017
diff --git a/lib/install.js b/lib/install.js
index 8a615dfb38..3580cdd003 100644
--- a/lib/install.js
+++ b/lib/install.js
@@ -1,72 +1,46 @@
-module.exports = exports = function (gyp, argv, callback) {
- return install(fs, gyp, argv, callback)
-}
-
-module.exports.test = {
- download: download,
- install: install,
- readCAFile: readCAFile,
-}
-
-exports.usage = 'Install node development files for the specified node version.'
-
-/**
- * Module dependencies.
- */
-
-var fs = require('graceful-fs')
- , osenv = require('osenv')
- , tar = require('tar')
- , rm = require('rimraf')
- , path = require('path')
- , crypto = require('crypto')
- , log = require('npmlog')
- , semver = require('semver')
- , request = require('request')
- , mkdir = require('mkdirp')
- , processRelease = require('./process-release')
- , win = process.platform == 'win32'
-
-function install (fs, gyp, argv, callback) {
-
- var release = processRelease(argv, gyp, process.version, process.release)
-
- // ensure no double-callbacks happen
- function cb (err) {
- if (cb.done) return
- cb.done = true
- if (err) {
- log.warn('install', 'got an error, rolling back install')
- // roll-back the install if anything went wrong
- gyp.commands.remove([ release.versionDir ], function (err2) {
- callback(err)
- })
- } else {
- callback(null, release.version)
- }
- }
+'use strict'
+
+const { createWriteStream, promises: fs } = require('graceful-fs')
+const os = require('os')
+const { backOff } = require('exponential-backoff')
+const tar = require('tar')
+const path = require('path')
+const { Transform, promises: { pipeline } } = require('stream')
+const crypto = require('crypto')
+const log = require('./log')
+const semver = require('semver')
+const { download } = require('./download')
+const processRelease = require('./process-release')
+
+const win = process.platform === 'win32'
+
+async function install (gyp, argv) {
+ log.stdout()
+ const release = processRelease(argv, gyp, process.version, process.release)
+ // Detecting target_arch based on logic from create-cnfig-gyp.js. Used on Windows only.
+ const arch = win ? (gyp.opts.target_arch || gyp.opts.arch || process.arch || 'ia32') : ''
+ // Used to prevent downloading tarball if only new node.lib is required on Windows.
+ let shouldDownloadTarball = true
// Determine which node dev files version we are installing
log.verbose('install', 'input version string %j', release.version)
if (!release.semver) {
// could not parse the version string with semver
- return callback(new Error('Invalid version number: ' + release.version))
+ throw new Error('Invalid version number: ' + release.version)
}
if (semver.lt(release.version, '0.8.0')) {
- return callback(new Error('Minimum target version is `0.8.0` or greater. Got: ' + release.version))
+ throw new Error('Minimum target version is `0.8.0` or greater. Got: ' + release.version)
}
// 0.x.y-pre versions are not published yet and cannot be installed. Bail.
if (release.semver.prerelease[0] === 'pre') {
log.verbose('detected "pre" node version', release.version)
- if (gyp.opts.nodedir) {
- log.verbose('--nodedir flag was passed; skipping install', gyp.opts.nodedir)
- callback()
- } else {
- callback(new Error('"pre" versions of node cannot be installed, use the --nodedir flag instead'))
+ if (!gyp.opts.nodedir) {
+ throw new Error('"pre" versions of node cannot be installed, use the --nodedir flag instead')
}
+ log.verbose('--nodedir flag was passed; skipping install', gyp.opts.nodedir)
return
}
@@ -74,318 +48,297 @@ function install (fs, gyp, argv, callback) {
log.verbose('install', 'installing version: %s', release.versionDir)
// the directory where the dev files will be installed
- var devDir = path.resolve(gyp.devDir, release.versionDir)
+ const devDir = path.resolve(gyp.devDir, release.versionDir)
// If '--ensure' was passed, then don't *always* install the version;
// check if it is already installed, and only install when needed
if (gyp.opts.ensure) {
log.verbose('install', '--ensure was passed, so won\'t reinstall if already installed')
- fs.stat(devDir, function (err, stat) {
- if (err) {
- if (err.code == 'ENOENT') {
- log.verbose('install', 'version not already installed, continuing with install', release.version)
- go()
- } else if (err.code == 'EACCES') {
- eaccesFallback(err)
- } else {
- cb(err)
+ try {
+ await fs.stat(devDir)
+ } catch (err) {
+ if (err.code === 'ENOENT') {
+ log.verbose('install', 'version not already installed, continuing with install', release.version)
+ try {
+ return await go()
+ } catch (err) {
+ return rollback(err)
}
- return
+ } else if (err.code === 'EACCES') {
+ return eaccesFallback(err)
}
- log.verbose('install', 'version is already installed, need to check "installVersion"')
- var installVersionFile = path.resolve(devDir, 'installVersion')
- fs.readFile(installVersionFile, 'ascii', function (err, ver) {
- if (err && err.code != 'ENOENT') {
- return cb(err)
- }
- var installVersion = parseInt(ver, 10) || 0
- log.verbose('got "installVersion"', installVersion)
- log.verbose('needs "installVersion"', gyp.package.installVersion)
- if (installVersion < gyp.package.installVersion) {
- log.verbose('install', 'version is no good; reinstalling')
- go()
- } else {
- log.verbose('install', 'version is good')
- cb()
+ throw err
+ }
+ log.verbose('install', 'version is already installed, need to check "installVersion"')
+ const installVersionFile = path.resolve(devDir, 'installVersion')
+ let installVersion = 0
+ try {
+ const ver = await fs.readFile(installVersionFile, 'ascii')
+ installVersion = parseInt(ver, 10) || 0
+ } catch (err) {
+ if (err.code !== 'ENOENT') {
+ throw err
+ }
+ }
+ log.verbose('got "installVersion"', installVersion)
+ log.verbose('needs "installVersion"', gyp.package.installVersion)
+ if (installVersion < gyp.package.installVersion) {
+ log.verbose('install', 'version is no good; reinstalling')
+ try {
+ return await go()
+ } catch (err) {
+ return rollback(err)
+ }
+ }
+ log.verbose('install', 'version is good')
+ if (win) {
+ log.verbose('on Windows; need to check node.lib')
+ const nodeLibPath = path.resolve(devDir, arch, 'node.lib')
+ try {
+ await fs.stat(nodeLibPath)
+ } catch (err) {
+ if (err.code === 'ENOENT') {
+ log.verbose('install', `version not already installed for ${arch}, continuing with install`, release.version)
+ try {
+ shouldDownloadTarball = false
+ return await go()
+ } catch (err) {
+ return rollback(err)
+ }
+ } else if (err.code === 'EACCES') {
+ return eaccesFallback(err)
}
- })
- })
+ throw err
+ }
+ }
} else {
- go()
+ try {
+ return await go()
+ } catch (err) {
+ return rollback(err)
+ }
}
- function getContentSha(res, callback) {
- var shasum = crypto.createHash('sha256')
- res.on('data', function (chunk) {
- shasum.update(chunk)
- }).on('end', function () {
- callback(null, shasum.digest('hex'))
- })
+ async function copyDirectory (src, dest) {
+ try {
+ await fs.stat(src)
+ } catch {
+ throw new Error(`Missing source directory for copy: ${src}`)
+ }
+ await fs.mkdir(dest, { recursive: true })
+ const entries = await fs.readdir(src, { withFileTypes: true })
+ for (const entry of entries) {
+ if (entry.isDirectory()) {
+ await copyDirectory(path.join(src, entry.name), path.join(dest, entry.name))
+ } else if (entry.isFile()) {
+ // with parallel installs, copying files may cause file errors on
+ // Windows so use an exponential backoff to resolve collisions
+ await backOff(async () => {
+ try {
+ await fs.copyFile(path.join(src, entry.name), path.join(dest, entry.name))
+ } catch (err) {
+ // if ensure, check if file already exists and that's good enough
+ if (gyp.opts.ensure && err.code === 'EBUSY') {
+ try {
+ await fs.stat(path.join(dest, entry.name))
+ return
+ } catch {}
+ }
+ throw err
+ }
+ })
+ } else {
+ throw new Error('Unexpected file directory entry type')
+ }
+ }
}
- function go () {
-
- log.verbose('ensuring nodedir is created', devDir)
+ async function go () {
+ log.verbose('ensuring devDir is created', devDir)
// first create the dir for the node dev files
- mkdir(devDir, function (err, created) {
- if (err) {
- if (err.code == 'EACCES') {
- eaccesFallback(err)
- } else {
- cb(err)
- }
- return
- }
+ try {
+ const created = await fs.mkdir(devDir, { recursive: true })
if (created) {
- log.verbose('created nodedir', created)
+ log.verbose('created devDir', created)
}
-
- // now download the node tarball
- var tarPath = gyp.opts.tarball
- var badDownload = false
- , extractCount = 0
-
- var contentShasums = {}
- var expectShasums = {}
-
- // checks if a file to be extracted from the tarball is valid.
- // only .h header files and the gyp files get extracted
- function isValid (path, entry) {
- var isValid = valid(path)
- if (isValid) {
- log.verbose('extracted file from tarball', path)
- extractCount++
- } else {
- // invalid
- log.silly('ignoring from tarball', path)
- }
- return isValid
+ } catch (err) {
+ if (err.code === 'EACCES') {
+ return eaccesFallback(err)
}
- // download the tarball and extract!
+ throw err
+ }
- if (tarPath) {
- return tar.extract({
- file: tarPath,
- strip: 1,
- filter: isValid,
- cwd: devDir
- }).then(afterTarball, cb)
+ // now download the node tarball
+ const tarPath = gyp.opts.tarball
+ let extractErrors = false
+ let extractCount = 0
+ const contentShasums = {}
+ const expectShasums = {}
+
+ // checks if a file to be extracted from the tarball is valid.
+ // only .h header files and the gyp files get extracted
+ function isValid (path) {
+ const isValid = valid(path)
+ if (isValid) {
+ log.verbose('extracted file from tarball', path)
+ extractCount++
+ } else {
+ // invalid
+ log.silly('ignoring from tarball', path)
}
+ return isValid
+ }
- try {
- var req = download(gyp, process.env, release.tarballUrl)
- } catch (e) {
- return cb(e)
- }
+ function onwarn (code, message) {
+ extractErrors = true
+ log.error('error while extracting tarball', code, message)
+ }
- // something went wrong downloading the tarball?
- req.on('error', function (err) {
- if (err.code === 'ENOTFOUND') {
- return cb(new Error('This is most likely not a problem with node-gyp or the package itself and\n' +
- 'is related to network connectivity. In most cases you are behind a proxy or have bad \n' +
- 'network settings.'))
- }
- badDownload = true
- cb(err)
- })
+ // download the tarball and extract!
+ // Omitted on Windows if only new node.lib is required
+
+ // there can be file errors from tar if parallel installs
+ // are happening (not uncommon with multiple native modules) so
+ // extract the tarball to a temp directory first and then copy over
+ const tarExtractDir = await fs.mkdtemp(path.join(os.tmpdir(), 'node-gyp-tmp-'))
+
+ try {
+ if (shouldDownloadTarball) {
+ if (tarPath) {
+ await tar.extract({
+ file: tarPath,
+ strip: 1,
+ filter: isValid,
+ onwarn,
+ cwd: tarExtractDir
+ })
+ } else {
+ try {
+ const res = await download(gyp, release.tarballUrl)
- req.on('close', function () {
- if (extractCount === 0) {
- cb(new Error('Connection closed while downloading tarball file'))
- }
- })
+ if (res.status !== 200) {
+ throw new Error(`${res.status} response downloading ${release.tarballUrl}`)
+ }
- req.on('response', function (res) {
- if (res.statusCode !== 200) {
- badDownload = true
- cb(new Error(res.statusCode + ' response downloading ' + release.tarballUrl))
- return
+ await pipeline(
+ res.body,
+ // content checksum
+ new ShaSum((_, checksum) => {
+ const filename = path.basename(release.tarballUrl).trim()
+ contentShasums[filename] = checksum
+ log.verbose('content checksum', filename, checksum)
+ }),
+ tar.extract({
+ strip: 1,
+ cwd: tarExtractDir,
+ filter: isValid,
+ onwarn
+ })
+ )
+ } catch (err) {
+ // something went wrong downloading the tarball?
+ if (err.code === 'ENOTFOUND') {
+ throw new Error('This is most likely not a problem with node-gyp or the package itself and\n' +
+ 'is related to network connectivity. In most cases you are behind a proxy or have bad \n' +
+ 'network settings.')
+ }
+ throw err
+ }
}
- // content checksum
- getContentSha(res, function (_, checksum) {
- var filename = path.basename(release.tarballUrl).trim()
- contentShasums[filename] = checksum
- log.verbose('content checksum', filename, checksum)
- })
- // start unzipping and untaring
- res.pipe(tar.extract({
- strip: 1,
- cwd: devDir,
- filter: isValid
- }).on('close', afterTarball).on('error', cb))
- })
-
- // invoked after the tarball has finished being extracted
- function afterTarball () {
- if (badDownload) return
- if (extractCount === 0) {
- return cb(new Error('There was a fatal problem while downloading/extracting the tarball'))
+ // invoked after the tarball has finished being extracted
+ if (extractErrors || extractCount === 0) {
+ throw new Error('There was a fatal problem while downloading/extracting the tarball')
}
- log.verbose('tarball', 'done parsing tarball')
- var async = 0
- if (win) {
- // need to download node.lib
- async++
- downloadNodeLib(deref)
- }
+ log.verbose('tarball', 'done parsing tarball')
+ }
+ const installVersionPath = path.resolve(tarExtractDir, 'installVersion')
+ await Promise.all([
+ // need to download node.lib
+ ...(win ? [downloadNodeLib()] : []),
// write the "installVersion" file
- async++
- var installVersionPath = path.resolve(devDir, 'installVersion')
- fs.writeFile(installVersionPath, gyp.package.installVersion + '\n', deref)
-
- // Only download SHASUMS.txt if not using tarPath override
- if (!tarPath) {
- // download SHASUMS.txt
- async++
- downloadShasums(deref)
- }
-
- if (async === 0) {
- // no async tasks required
- cb()
- }
-
- function deref (err) {
- if (err) return cb(err)
-
- async--
- if (!async) {
- log.verbose('download contents checksum', JSON.stringify(contentShasums))
- // check content shasums
- for (var k in contentShasums) {
- log.verbose('validating download checksum for ' + k, '(%s == %s)', contentShasums[k], expectShasums[k])
- if (contentShasums[k] !== expectShasums[k]) {
- cb(new Error(k + ' local checksum ' + contentShasums[k] + ' not match remote ' + expectShasums[k]))
- return
- }
- }
- cb()
- }
+ fs.writeFile(installVersionPath, gyp.package.installVersion + '\n'),
+ // Only download SHASUMS.txt if we downloaded something in need of SHA verification
+ ...(!tarPath || win ? [downloadShasums()] : [])
+ ])
+
+ log.verbose('download contents checksum', JSON.stringify(contentShasums))
+ // check content shasums
+ for (const k in contentShasums) {
+ log.verbose('validating download checksum for ' + k, '(%s == %s)', contentShasums[k], expectShasums[k])
+ if (contentShasums[k] !== expectShasums[k]) {
+ throw new Error(k + ' local checksum ' + contentShasums[k] + ' not match remote ' + expectShasums[k])
}
}
- function downloadShasums(done) {
- log.verbose('check download content checksum, need to download `SHASUMS256.txt`...')
- var shasumsPath = path.resolve(devDir, 'SHASUMS256.txt')
-
- log.verbose('checksum url', release.shasumsUrl)
- try {
- var req = download(gyp, process.env, release.shasumsUrl)
- } catch (e) {
- return cb(e)
- }
-
- req.on('error', done)
- req.on('response', function (res) {
- if (res.statusCode !== 200) {
- done(new Error(res.statusCode + ' status code downloading checksum'))
- return
- }
-
- var chunks = []
- res.on('data', function (chunk) {
- chunks.push(chunk)
- })
- res.on('end', function () {
- var lines = Buffer.concat(chunks).toString().trim().split('\n')
- lines.forEach(function (line) {
- var items = line.trim().split(/\s+/)
- if (items.length !== 2) return
-
- // 0035d18e2dcf9aad669b1c7c07319e17abfe3762 ./node-v0.11.4.tar.gz
- var name = items[1].replace(/^\.\//, '')
- expectShasums[name] = items[0]
- })
-
- log.verbose('checksum data', JSON.stringify(expectShasums))
- done()
- })
- })
+ // copy over the files from the temp tarball extract directory to devDir
+ await copyDirectory(tarExtractDir, devDir)
+ } finally {
+ try {
+ // try to cleanup temp dir
+ await fs.rm(tarExtractDir, { recursive: true, maxRetries: 3 })
+ } catch {
+ log.warn('failed to clean up temp tarball extract directory')
}
+ }
- function downloadNodeLib (done) {
- log.verbose('on Windows; need to download `' + release.name + '.lib`...')
- var dir32 = path.resolve(devDir, 'ia32')
- , dir64 = path.resolve(devDir, 'x64')
- , libPath32 = path.resolve(dir32, release.name + '.lib')
- , libPath64 = path.resolve(dir64, release.name + '.lib')
-
- log.verbose('32-bit ' + release.name + '.lib dir', dir32)
- log.verbose('64-bit ' + release.name + '.lib dir', dir64)
- log.verbose('`' + release.name + '.lib` 32-bit url', release.libUrl32)
- log.verbose('`' + release.name + '.lib` 64-bit url', release.libUrl64)
-
- var async = 2
- mkdir(dir32, function (err) {
- if (err) return done(err)
- log.verbose('streaming 32-bit ' + release.name + '.lib to:', libPath32)
+ async function downloadShasums () {
+ log.verbose('check download content checksum, need to download `SHASUMS256.txt`...')
+ log.verbose('checksum url', release.shasumsUrl)
- try {
- var req = download(gyp, process.env, release.libUrl32, cb)
- } catch (e) {
- return cb(e)
- }
+ const res = await download(gyp, release.shasumsUrl)
- req.on('error', done)
- req.on('response', function (res) {
- if (res.statusCode !== 200) {
- done(new Error(res.statusCode + ' status code downloading 32-bit ' + release.name + '.lib'))
- return
- }
+ if (res.status !== 200) {
+ throw new Error(`${res.status} status code downloading checksum`)
+ }
- getContentSha(res, function (_, checksum) {
- contentShasums[release.libPath32] = checksum
- log.verbose('content checksum', release.libPath32, checksum)
- })
+ for (const line of (await res.text()).trim().split('\n')) {
+ const items = line.trim().split(/\s+/)
+ if (items.length !== 2) {
+ return
+ }
- var ws = fs.createWriteStream(libPath32)
- ws.on('error', cb)
- req.pipe(ws)
- })
- req.on('end', function () {
- --async || done()
- })
- })
- mkdir(dir64, function (err) {
- if (err) return done(err)
- log.verbose('streaming 64-bit ' + release.name + '.lib to:', libPath64)
+ // 0035d18e2dcf9aad669b1c7c07319e17abfe3762 ./node-v0.11.4.tar.gz
+ const name = items[1].replace(/^\.\//, '')
+ expectShasums[name] = items[0]
+ }
- try {
- var req = download(gyp, process.env, release.libUrl64, cb)
- } catch (e) {
- return cb(e)
- }
+ log.verbose('checksum data', JSON.stringify(expectShasums))
+ }
- req.on('error', done)
- req.on('response', function (res) {
- if (res.statusCode !== 200) {
- done(new Error(res.statusCode + ' status code downloading 64-bit ' + release.name + '.lib'))
- return
- }
+ async function downloadNodeLib () {
+ log.verbose('on Windows; need to download `' + release.name + '.lib`...')
+ const dir = path.resolve(tarExtractDir, arch)
+ const targetLibPath = path.resolve(dir, release.name + '.lib')
+ const { libUrl, libPath } = release[arch]
+ const name = `${arch} ${release.name}.lib`
+ log.verbose(name, 'dir', dir)
+ log.verbose(name, 'url', libUrl)
- getContentSha(res, function (_, checksum) {
- contentShasums[release.libPath64] = checksum
- log.verbose('content checksum', release.libPath64, checksum)
- })
+ await fs.mkdir(dir, { recursive: true })
+ log.verbose('streaming', name, 'to:', targetLibPath)
- var ws = fs.createWriteStream(libPath64)
- ws.on('error', cb)
- req.pipe(ws)
- })
- req.on('end', function () {
- --async || done()
- })
- })
- } // downloadNodeLib()
+ const res = await download(gyp, libUrl)
- }) // mkdir()
+ // Since only required node.lib is downloaded throw error if it is not fetched
+ if (res.status !== 200) {
+ throw new Error(`${res.status} status code downloading ${name}`)
+ }
+ return pipeline(
+ res.body,
+ new ShaSum((_, checksum) => {
+ contentShasums[libPath] = checksum
+ log.verbose('content checksum', libPath, checksum)
+ }),
+ createWriteStream(targetLibPath)
+ )
+ } // downloadNodeLib()
} // go()
/**
@@ -394,8 +347,15 @@ function install (fs, gyp, argv, callback) {
function valid (file) {
// header files
- var extname = path.extname(file);
- return extname === '.h' || extname === '.gypi';
+ const extname = path.extname(file)
+ return extname === '.h' || extname === '.gypi'
+ }
+
+ async function rollback (err) {
+ log.warn('install', 'got an error, rolling back install')
+ // roll-back the install if anything went wrong
+ await gyp.commands.remove([release.versionDir])
+ throw err
}
/**
@@ -407,63 +367,45 @@ function install (fs, gyp, argv, callback) {
* the compilation will succeed...
*/
- function eaccesFallback (err) {
- var noretry = '--node_gyp_internal_noretry'
- if (-1 !== argv.indexOf(noretry)) return cb(err)
- var tmpdir = osenv.tmpdir()
+ async function eaccesFallback (err) {
+ const noretry = '--node_gyp_internal_noretry'
+ if (argv.indexOf(noretry) !== -1) {
+ throw err
+ }
+ const tmpdir = os.tmpdir()
gyp.devDir = path.resolve(tmpdir, '.node-gyp')
- log.warn('EACCES', 'user "%s" does not have permission to access the dev dir "%s"', osenv.user(), devDir)
+ let userString = ''
+ try {
+ // os.userInfo can fail on some systems, it's not critical here
+ userString = ` ("${os.userInfo().username}")`
+ } catch (e) {}
+ log.warn('EACCES', 'current user%s does not have permission to access the dev dir "%s"', userString, devDir)
log.warn('EACCES', 'attempting to reinstall using temporary dev dir "%s"', gyp.devDir)
- if (process.cwd() == tmpdir) {
+ if (process.cwd() === tmpdir) {
log.verbose('tmpdir == cwd', 'automatically will remove dev files after to save disk space')
gyp.todo.push({ name: 'remove', args: argv })
}
- gyp.commands.install([noretry].concat(argv), cb)
+ return gyp.commands.install([noretry].concat(argv))
}
-
}
-function download (gyp, env, url) {
- log.http('GET', url)
-
- var requestOpts = {
- uri: url
- , headers: {
- 'User-Agent': 'node-gyp v' + gyp.version + ' (node ' + process.version + ')'
- }
+class ShaSum extends Transform {
+ constructor (callback) {
+ super()
+ this._callback = callback
+ this._digester = crypto.createHash('sha256')
}
- var cafile = gyp.opts.cafile
- if (cafile) {
- requestOpts.ca = readCAFile(cafile)
+ _transform (chunk, _, callback) {
+ this._digester.update(chunk)
+ callback(null, chunk)
}
- // basic support for a proxy server
- var proxyUrl = gyp.opts.proxy
- || env.http_proxy
- || env.HTTP_PROXY
- || env.npm_config_proxy
- if (proxyUrl) {
- if (/^https?:\/\//i.test(proxyUrl)) {
- log.verbose('download', 'using proxy url: "%s"', proxyUrl)
- requestOpts.proxy = proxyUrl
- } else {
- log.warn('download', 'ignoring invalid "proxy" config setting: "%s"', proxyUrl)
- }
+ _flush (callback) {
+ this._callback(null, this._digester.digest('hex'))
+ callback()
}
-
- var req = request(requestOpts)
- req.on('response', function (res) {
- log.http(res.statusCode, url)
- })
-
- return req
}
-function readCAFile (filename) {
- // The CA file can contain multiple certificates so split on certificate
- // boundaries. [\S\s]*? is used to match everything including newlines.
- var ca = fs.readFileSync(filename, 'utf8')
- var re = /(-----BEGIN CERTIFICATE-----[\S\s]*?-----END CERTIFICATE-----)/g
- return ca.match(re)
-}
+module.exports = install
+module.exports.usage = 'Install node development files for the specified node version.'
diff --git a/lib/list.js b/lib/list.js
index 9d680a56a4..36889ad4f7 100644
--- a/lib/list.js
+++ b/lib/list.js
@@ -1,33 +1,26 @@
+'use strict'
-module.exports = exports = list
+const fs = require('graceful-fs').promises
+const log = require('./log')
-exports.usage = 'Prints a listing of the currently installed node development files'
-
-/**
- * Module dependencies.
- */
-
-var fs = require('graceful-fs')
- , path = require('path')
- , log = require('npmlog')
-
-function list (gyp, args, callback) {
-
- var devDir = gyp.devDir
+async function list (gyp, args) {
+ const devDir = gyp.devDir
log.verbose('list', 'using node-gyp dir:', devDir)
- // readdir() the node-gyp dir
- fs.readdir(devDir, onreaddir)
-
- function onreaddir (err, versions) {
- if (err && err.code != 'ENOENT') {
- return callback(err)
+ let versions = []
+ try {
+ const dir = await fs.readdir(devDir)
+ if (Array.isArray(dir)) {
+ versions = dir.filter((v) => v !== 'current')
}
- if (Array.isArray(versions)) {
- versions = versions.filter(function (v) { return v != 'current' })
- } else {
- versions = []
+ } catch (err) {
+ if (err && err.code !== 'ENOENT') {
+ throw err
}
- callback(null, versions)
}
+
+ return versions
}
+
+module.exports = list
+module.exports.usage = 'Prints a listing of the currently installed node development files'
diff --git a/lib/log.js b/lib/log.js
new file mode 100644
index 0000000000..36fa2487f5
--- /dev/null
+++ b/lib/log.js
@@ -0,0 +1,168 @@
+'use strict'
+
+const { log } = require('proc-log')
+const { format } = require('util')
+
+// helper to emit log messages with a predefined prefix
+const withPrefix = (prefix) => log.LEVELS.reduce((acc, level) => {
+ acc[level] = (...args) => log[level](prefix, ...args)
+ return acc
+}, {})
+
+// very basic ansi color generator
+const COLORS = {
+ wrap: (str, colors) => {
+ const codes = colors.filter(c => typeof c === 'number')
+ return `\x1b[${codes.join(';')}m${str}\x1b[0m`
+ },
+ inverse: 7,
+ fg: {
+ black: 30,
+ red: 31,
+ green: 32,
+ yellow: 33,
+ blue: 34,
+ magenta: 35,
+ cyan: 36,
+ white: 37
+ },
+ bg: {
+ black: 40,
+ red: 41,
+ green: 42,
+ yellow: 43,
+ blue: 44,
+ magenta: 45,
+ cyan: 46,
+ white: 47
+ }
+}
+
+class Logger {
+ #buffer = []
+ #paused = null
+ #level = null
+ #stream = null
+
+ // ordered from loudest to quietest
+ #levels = [{
+ id: 'silly',
+ display: 'sill',
+ style: { inverse: true }
+ }, {
+ id: 'verbose',
+ display: 'verb',
+ style: { fg: 'cyan', bg: 'black' }
+ }, {
+ id: 'info',
+ style: { fg: 'green' }
+ }, {
+ id: 'http',
+ style: { fg: 'green', bg: 'black' }
+ }, {
+ id: 'notice',
+ style: { fg: 'cyan', bg: 'black' }
+ }, {
+ id: 'warn',
+ display: 'WARN',
+ style: { fg: 'black', bg: 'yellow' }
+ }, {
+ id: 'error',
+ display: 'ERR!',
+ style: { fg: 'red', bg: 'black' }
+ }]
+
+ constructor (stream) {
+ process.on('log', (...args) => this.#onLog(...args))
+ this.#levels = new Map(this.#levels.map((level, index) => [level.id, { ...level, index }]))
+ this.level = 'info'
+ this.stream = stream
+ log.pause()
+ }
+
+ get stream () {
+ return this.#stream
+ }
+
+ set stream (stream) {
+ this.#stream = stream
+ }
+
+ get level () {
+ return this.#levels.get(this.#level) ?? null
+ }
+
+ set level (level) {
+ this.#level = this.#levels.get(level)?.id ?? null
+ }
+
+ isVisible (level) {
+ return this.level?.index <= this.#levels.get(level)?.index ?? -1
+ }
+
+ #onLog (...args) {
+ const [level] = args
+
+ if (level === 'pause') {
+ this.#paused = true
+ return
+ }
+
+ if (level === 'resume') {
+ this.#paused = false
+ this.#buffer.forEach((b) => this.#log(...b))
+ this.#buffer.length = 0
+ return
+ }
+
+ if (this.#paused) {
+ this.#buffer.push(args)
+ return
+ }
+
+ this.#log(...args)
+ }
+
+ #color (str, { fg, bg, inverse }) {
+ if (!this.#stream?.isTTY) {
+ return str
+ }
+
+ return COLORS.wrap(str, [
+ COLORS.fg[fg],
+ COLORS.bg[bg],
+ inverse && COLORS.inverse
+ ])
+ }
+
+ #log (levelId, msgPrefix, ...args) {
+ if (!this.isVisible(levelId) || typeof this.#stream?.write !== 'function') {
+ return
+ }
+
+ const level = this.#levels.get(levelId)
+
+ const prefixParts = [
+ this.#color('gyp', { fg: 'white', bg: 'black' }),
+ this.#color(level.display ?? level.id, level.style)
+ ]
+ if (msgPrefix) {
+ prefixParts.push(this.#color(msgPrefix, { fg: 'magenta' }))
+ }
+
+ const prefix = prefixParts.join(' ').trim() + ' '
+ const lines = format(...args).split(/\r?\n/).map(l => prefix + l.trim())
+
+ this.#stream.write(lines.join('\n') + '\n')
+ }
+}
+
+// used to suppress logs in tests
+const NULL_LOGGER = !!process.env.NODE_GYP_NULL_LOGGER
+
+module.exports = {
+ logger: new Logger(NULL_LOGGER ? null : process.stderr),
+ stdout: NULL_LOGGER ? () => {} : (...args) => console.log(...args),
+ withPrefix,
+ ...log
+}
diff --git a/lib/node-gyp.js b/lib/node-gyp.js
index a841161e32..dafce99d49 100644
--- a/lib/node-gyp.js
+++ b/lib/node-gyp.js
@@ -1,216 +1,199 @@
+'use strict'
+
+const path = require('path')
+const nopt = require('nopt')
+const log = require('./log')
+const childProcess = require('child_process')
+const { EventEmitter } = require('events')
+
+const commands = [
+ // Module build commands
+ 'build',
+ 'clean',
+ 'configure',
+ 'rebuild',
+ // Development Header File management commands
+ 'install',
+ 'list',
+ 'remove'
+]
+
+class Gyp extends EventEmitter {
+ /**
+ * Export the contents of the package.json.
+ */
+ package = require('../package.json')
+
+ /**
+ * nopt configuration definitions
+ */
+ configDefs = {
+ help: Boolean, // everywhere
+ arch: String, // 'configure'
+ cafile: String, // 'install'
+ debug: Boolean, // 'build'
+ directory: String, // bin
+ make: String, // 'build'
+ 'msvs-version': String, // 'configure'
+ ensure: Boolean, // 'install'
+ solution: String, // 'build' (windows only)
+ proxy: String, // 'install'
+ noproxy: String, // 'install'
+ devdir: String, // everywhere
+ nodedir: String, // 'configure'
+ loglevel: String, // everywhere
+ python: String, // 'configure'
+ 'dist-url': String, // 'install'
+ tarball: String, // 'install'
+ jobs: String, // 'build'
+ thin: String, // 'configure'
+ 'force-process-config': Boolean // 'configure'
+ }
-/**
- * Module exports.
- */
-
-module.exports = exports = gyp
-
-/**
- * Module dependencies.
- */
-
-var fs = require('graceful-fs')
- , path = require('path')
- , nopt = require('nopt')
- , log = require('npmlog')
- , child_process = require('child_process')
- , EE = require('events').EventEmitter
- , inherits = require('util').inherits
- , commands = [
- // Module build commands
- 'build'
- , 'clean'
- , 'configure'
- , 'rebuild'
- // Development Header File management commands
- , 'install'
- , 'list'
- , 'remove'
- ]
- , aliases = {
- 'ls': 'list'
- , 'rm': 'remove'
- }
-
-// differentiate node-gyp's logs from npm's
-log.heading = 'gyp'
-
-/**
- * The `gyp` function.
- */
-
-function gyp () {
- return new Gyp()
-}
-
-function Gyp () {
- var self = this
-
- this.devDir = ''
- this.commands = {}
+ /**
+ * nopt shorthands
+ */
+ shorthands = {
+ release: '--no-debug',
+ C: '--directory',
+ debug: '--debug',
+ j: '--jobs',
+ silly: '--loglevel=silly',
+ verbose: '--loglevel=verbose',
+ silent: '--loglevel=silent'
+ }
- commands.forEach(function (command) {
- self.commands[command] = function (argv, callback) {
- log.verbose('command', command, argv)
- return require('./' + command)(self, argv, callback)
- }
- })
-}
-inherits(Gyp, EE)
-exports.Gyp = Gyp
-var proto = Gyp.prototype
-
-/**
- * Export the contents of the package.json.
- */
-
-proto.package = require('../package')
-
-/**
- * nopt configuration definitions
- */
-
-proto.configDefs = {
- help: Boolean // everywhere
- , arch: String // 'configure'
- , cafile: String // 'install'
- , debug: Boolean // 'build'
- , directory: String // bin
- , make: String // 'build'
- , msvs_version: String // 'configure'
- , ensure: Boolean // 'install'
- , solution: String // 'build' (windows only)
- , proxy: String // 'install'
- , devdir: String // everywhere
- , nodedir: String // 'configure'
- , loglevel: String // everywhere
- , python: String // 'configure'
- , 'dist-url': String // 'install'
- , 'tarball': String // 'install'
- , jobs: String // 'build'
- , thin: String // 'configure'
-}
+ /**
+ * expose the command aliases for the bin file to use.
+ */
+ aliases = {
+ ls: 'list',
+ rm: 'remove'
+ }
-/**
- * nopt shorthands
- */
-
-proto.shorthands = {
- release: '--no-debug'
- , C: '--directory'
- , debug: '--debug'
- , j: '--jobs'
- , silly: '--loglevel=silly'
- , verbose: '--loglevel=verbose'
- , silent: '--loglevel=silent'
-}
+ constructor (...args) {
+ super(...args)
-/**
- * expose the command aliases for the bin file to use.
- */
+ this.devDir = ''
-proto.aliases = aliases
+ this.commands = commands.reduce((acc, command) => {
+ acc[command] = (argv) => require('./' + command)(this, argv)
+ return acc
+ }, {})
-/**
- * Parses the given argv array and sets the 'opts',
- * 'argv' and 'command' properties.
- */
+ Object.defineProperty(this, 'version', {
+ enumerable: true,
+ get: function () { return this.package.version }
+ })
+ }
-proto.parseArgv = function parseOpts (argv) {
- this.opts = nopt(this.configDefs, this.shorthands, argv)
- this.argv = this.opts.argv.remain.slice()
+ /**
+ * Parses the given argv array and sets the 'opts',
+ * 'argv' and 'command' properties.
+ */
+ parseArgv (argv) {
+ this.opts = nopt(this.configDefs, this.shorthands, argv)
+ this.argv = this.opts.argv.remain.slice()
- var commands = this.todo = []
+ const commands = this.todo = []
- // create a copy of the argv array with aliases mapped
- argv = this.argv.map(function (arg) {
+ // create a copy of the argv array with aliases mapped
+ argv = this.argv.map((arg) => {
// is this an alias?
- if (arg in this.aliases) {
- arg = this.aliases[arg]
- }
- return arg
- }, this)
-
- // process the mapped args into "command" objects ("name" and "args" props)
- argv.slice().forEach(function (arg) {
- if (arg in this.commands) {
- var args = argv.splice(0, argv.indexOf(arg))
- argv.shift()
- if (commands.length > 0) {
- commands[commands.length - 1].args = args
+ if (arg in this.aliases) {
+ arg = this.aliases[arg]
+ }
+ return arg
+ })
+
+ // process the mapped args into "command" objects ("name" and "args" props)
+ argv.slice().forEach((arg) => {
+ if (arg in this.commands) {
+ const args = argv.splice(0, argv.indexOf(arg))
+ argv.shift()
+ if (commands.length > 0) {
+ commands[commands.length - 1].args = args
+ }
+ commands.push({ name: arg, args: [] })
}
- commands.push({ name: arg, args: [] })
+ })
+ if (commands.length > 0) {
+ commands[commands.length - 1].args = argv.splice(0)
}
- }, this)
- if (commands.length > 0) {
- commands[commands.length - 1].args = argv.splice(0)
- }
- // support for inheriting config env variables from npm
- var npm_config_prefix = 'npm_config_'
- Object.keys(process.env).forEach(function (name) {
- if (name.indexOf(npm_config_prefix) !== 0) return
- var val = process.env[name]
- if (name === npm_config_prefix + 'loglevel') {
- log.level = val
- } else {
+ // support for inheriting config env variables from npm
+ // npm will set environment variables in the following forms:
+ // - `npm_config_` for values from npm's own config. Setting arbitrary
+ // options on npm's config was deprecated in npm v11 but node-gyp still
+ // supports it for backwards compatibility.
+ // See https://github.com/nodejs/node-gyp/issues/3156
+ // - `npm_package_config_node_gyp_` for values from the `config` object
+ // in package.json. This is the preferred way to set options for node-gyp
+ // since npm v11. The `node_gyp_` prefix is used to avoid conflicts with
+ // other tools.
+ // The `npm_package_config_node_gyp_` prefix will take precedence over
+ // `npm_config_` keys.
+ const npmConfigPrefix = /^npm_config_/i
+ const npmPackageConfigPrefix = /^npm_package_config_node_gyp_/i
+
+ const configEnvKeys = Object.keys(process.env)
+ .filter((k) => npmConfigPrefix.test(k) || npmPackageConfigPrefix.test(k))
+ // sort so that npm_package_config_node_gyp_ keys come last and will override
+ .sort((a) => npmConfigPrefix.test(a) ? -1 : 1)
+
+ for (const key of configEnvKeys) {
// add the user-defined options to the config
- name = name.substring(npm_config_prefix.length)
+ const name = npmConfigPrefix.test(key)
+ ? key.replace(npmConfigPrefix, '')
+ : key.replace(npmPackageConfigPrefix, '')
// gyp@741b7f1 enters an infinite loop when it encounters
// zero-length options so ensure those don't get through.
- if (name) this.opts[name] = val
+ if (name) {
+ // convert names like force_process_config to force-process-config
+ // and convert to lowercase
+ this.opts[name.replaceAll('_', '-').toLowerCase()] = process.env[key]
+ }
}
- }, this)
- if (this.opts.loglevel) {
- log.level = this.opts.loglevel
+ if (this.opts.loglevel) {
+ log.logger.level = this.opts.loglevel
+ delete this.opts.loglevel
+ }
+ log.resume()
}
- log.resume()
-}
-
-/**
- * Spawns a child process and emits a 'spawn' event.
- */
-proto.spawn = function spawn (command, args, opts) {
- if (!opts) opts = {}
- if (!opts.silent && !opts.stdio) {
- opts.stdio = [ 0, 1, 2 ]
+ /**
+ * Spawns a child process and emits a 'spawn' event.
+ */
+ spawn (command, args, opts) {
+ if (!opts) {
+ opts = {}
+ }
+ if (!opts.silent && !opts.stdio) {
+ opts.stdio = [0, 1, 2]
+ }
+ const cp = childProcess.spawn(command, args, opts)
+ log.info('spawn', command)
+ log.info('spawn args', args)
+ return cp
}
- var cp = child_process.spawn(command, args, opts)
- log.info('spawn', command)
- log.info('spawn args', args)
- return cp
-}
-/**
- * Returns the usage instructions for node-gyp.
- */
-
-proto.usage = function usage () {
- var str = [
- ''
- , ' Usage: node-gyp [options]'
- , ''
- , ' where is one of:'
- , commands.map(function (c) {
- return ' - ' + c + ' - ' + require('./' + c).usage
- }).join('\n')
- , ''
- , 'node-gyp@' + this.version + ' ' + path.resolve(__dirname, '..')
- , 'node@' + process.versions.node
- ].join('\n')
- return str
+ /**
+ * Returns the usage instructions for node-gyp.
+ */
+ usage () {
+ return [
+ '',
+ ' Usage: node-gyp [options]',
+ '',
+ ' where is one of:',
+ commands.map((c) => ' - ' + c + ' - ' + require('./' + c).usage).join('\n'),
+ '',
+ 'node-gyp@' + this.version + ' ' + path.resolve(__dirname, '..'),
+ 'node@' + process.versions.node
+ ].join('\n')
+ }
}
-/**
- * Version number getter.
- */
-
-Object.defineProperty(proto, 'version', {
- get: function () {
- return this.package.version
- }
- , enumerable: true
-})
-
+module.exports = () => new Gyp()
+module.exports.Gyp = Gyp
diff --git a/lib/process-release.js b/lib/process-release.js
index 0d177f1c93..75f3fc136a 100644
--- a/lib/process-release.js
+++ b/lib/process-release.js
@@ -1,134 +1,121 @@
-var semver = require('semver')
- , url = require('url')
- , path = require('path')
- , log = require('npmlog')
-
- // versions where -headers.tar.gz started shipping
- , headersTarballRange = '>= 3.0.0 || ~0.12.10 || ~0.10.42'
- , bitsre = /\/win-(x86|x64)\//
- , bitsreV3 = /\/win-(x86|ia32|x64)\// // io.js v3.x.x shipped with "ia32" but should
- // have been "x86"
-
-// Captures all the logic required to determine download URLs, local directory and
+'use strict'
+
+const semver = require('semver')
+const path = require('path')
+const log = require('./log')
+
+// versions where -headers.tar.gz started shipping
+const headersTarballRange = '>= 3.0.0 || ~0.12.10 || ~0.10.42'
+const bitsre = /\/win-(x86|x64|arm64)\//
+const bitsreV3 = /\/win-(x86|ia32|x64)\// // io.js v3.x.x shipped with "ia32" but should
+// have been "x86"
+
+// Captures all the logic required to determine download URLs, local directory and
// file names. Inputs come from command-line switches (--target, --dist-url),
// `process.version` and `process.release` where it exists.
function processRelease (argv, gyp, defaultVersion, defaultRelease) {
- var version = (semver.valid(argv[0]) && argv[0]) || gyp.opts.target || defaultVersion
- , versionSemver = semver.parse(version)
- , overrideDistUrl = gyp.opts['dist-url'] || gyp.opts.disturl
- , isDefaultVersion
- , isIojs
- , name
- , distBaseUrl
- , baseUrl
- , libUrl32
- , libUrl64
- , tarballUrl
- , canGetHeaders
+ let version = (semver.valid(argv[0]) && argv[0]) || gyp.opts.target || defaultVersion
+ const versionSemver = semver.parse(version)
+ let overrideDistUrl = gyp.opts['dist-url'] || gyp.opts.disturl
+ let isNamedForLegacyIojs
+ let name
+ let distBaseUrl
+ let baseUrl
+ let libUrl32
+ let libUrl64
+ let libUrlArm64
+ let tarballUrl
+ let canGetHeaders
if (!versionSemver) {
// not a valid semver string, nothing we can do
- return { version: version }
+ return { version }
}
// flatten version into String
version = versionSemver.version
// defaultVersion should come from process.version so ought to be valid semver
- isDefaultVersion = version === semver.parse(defaultVersion).version
+ const isDefaultVersion = version === semver.parse(defaultVersion).version
// can't use process.release if we're using --target=x.y.z
- if (!isDefaultVersion)
+ if (!isDefaultVersion) {
defaultRelease = null
+ }
if (defaultRelease) {
// v3 onward, has process.release
name = defaultRelease.name.replace(/io\.js/, 'iojs') // remove the '.' for directory naming purposes
- isIojs = name === 'iojs'
} else {
// old node or alternative --target=
// semver.satisfies() doesn't like prerelease tags so test major directly
- isIojs = versionSemver.major >= 1 && versionSemver.major < 4
- name = isIojs ? 'iojs' : 'node'
+ isNamedForLegacyIojs = versionSemver.major >= 1 && versionSemver.major < 4
+ // isNamedForLegacyIojs is required to support Electron < 4 (in particular Electron 3)
+ // as previously this logic was used to ensure "iojs" was used to download iojs releases
+ // and "node" for node releases. Unfortunately the logic was broad enough that electron@3
+ // published release assets as "iojs" so that the node-gyp logic worked. Once Electron@3 has
+ // been EOL for a while (late 2019) we should remove this hack.
+ name = isNamedForLegacyIojs ? 'iojs' : 'node'
}
// check for the nvm.sh standard mirror env variables
- if (!overrideDistUrl) {
- if (isIojs) {
- if (process.env.IOJS_ORG_MIRROR) {
- overrideDistUrl = process.env.IOJS_ORG_MIRROR
- } else if (process.env.NVM_IOJS_ORG_MIRROR) {// remove on next semver-major
- overrideDistUrl = process.env.NVM_IOJS_ORG_MIRROR
- log.warn('download',
- 'NVM_IOJS_ORG_MIRROR is deprecated and will be removed in node-gyp v4, ' +
- 'please use IOJS_ORG_MIRROR')
- }
- } else {
- if (process.env.NODEJS_ORG_MIRROR) {
- overrideDistUrl = process.env.NODEJS_ORG_MIRROR
- } else if (process.env.NVM_NODEJS_ORG_MIRROR) {// remove on next semver-major
- overrideDistUrl = process.env.NVM_NODEJS_ORG_MIRROR
- log.warn('download',
- 'NVM_NODEJS_ORG_MIRROR is deprecated and will be removed in node-gyp v4, ' +
- 'please use NODEJS_ORG_MIRROR')
- }
- }
+ if (!overrideDistUrl && process.env.NODEJS_ORG_MIRROR) {
+ overrideDistUrl = process.env.NODEJS_ORG_MIRROR
}
- if (overrideDistUrl)
+ if (overrideDistUrl) {
log.verbose('download', 'using dist-url', overrideDistUrl)
+ }
- if (overrideDistUrl)
+ if (overrideDistUrl) {
distBaseUrl = overrideDistUrl.replace(/\/+$/, '')
- else
- distBaseUrl = isIojs ? 'https://iojs.org/download/release' : 'https://nodejs.org/dist'
- distBaseUrl += '/v' + version + '/'
+ } else {
+ distBaseUrl = 'https://nodejs.org/dist'
+ }
+ distBaseUrl = new URL(distBaseUrl + '/v' + version + '/')
// new style, based on process.release so we have a lot of the data we need
if (defaultRelease && defaultRelease.headersUrl && !overrideDistUrl) {
- baseUrl = url.resolve(defaultRelease.headersUrl, './')
+ baseUrl = new URL('./', defaultRelease.headersUrl)
libUrl32 = resolveLibUrl(name, defaultRelease.libUrl || baseUrl || distBaseUrl, 'x86', versionSemver.major)
libUrl64 = resolveLibUrl(name, defaultRelease.libUrl || baseUrl || distBaseUrl, 'x64', versionSemver.major)
-
- return {
- version: version,
- semver: versionSemver,
- name: name,
- baseUrl: baseUrl,
- tarballUrl: defaultRelease.headersUrl,
- shasumsUrl: url.resolve(baseUrl, 'SHASUMS256.txt'),
- versionDir: (name !== 'node' ? name + '-' : '') + version,
- libUrl32: libUrl32,
- libUrl64: libUrl64,
- libPath32: normalizePath(path.relative(url.parse(baseUrl).path, url.parse(libUrl32).path)),
- libPath64: normalizePath(path.relative(url.parse(baseUrl).path, url.parse(libUrl64).path))
- }
+ libUrlArm64 = resolveLibUrl(name, defaultRelease.libUrl || baseUrl || distBaseUrl, 'arm64', versionSemver.major)
+ tarballUrl = defaultRelease.headersUrl
+ } else {
+ // older versions without process.release are captured here and we have to make
+ // a lot of assumptions, additionally if you --target=x.y.z then we can't use the
+ // current process.release
+ baseUrl = distBaseUrl
+ libUrl32 = resolveLibUrl(name, baseUrl, 'x86', versionSemver.major)
+ libUrl64 = resolveLibUrl(name, baseUrl, 'x64', versionSemver.major)
+ libUrlArm64 = resolveLibUrl(name, baseUrl, 'arm64', versionSemver.major)
+
+ // making the bold assumption that anything with a version number >3.0.0 will
+ // have a *-headers.tar.gz file in its dist location, even some frankenstein
+ // custom version
+ canGetHeaders = semver.satisfies(versionSemver, headersTarballRange)
+ tarballUrl = new URL(name + '-v' + version + (canGetHeaders ? '-headers' : '') + '.tar.gz', baseUrl).href
}
- // older versions without process.release are captured here and we have to make
- // a lot of assumptions, additionally if you --target=x.y.z then we can't use the
- // current process.release
-
- baseUrl = distBaseUrl
- libUrl32 = resolveLibUrl(name, baseUrl, 'x86', versionSemver.major)
- libUrl64 = resolveLibUrl(name, baseUrl, 'x64', versionSemver.major)
- // making the bold assumption that anything with a version number >3.0.0 will
- // have a *-headers.tar.gz file in its dist location, even some frankenstein
- // custom version
- canGetHeaders = semver.satisfies(versionSemver, headersTarballRange)
- tarballUrl = url.resolve(baseUrl, name + '-v' + version + (canGetHeaders ? '-headers' : '') + '.tar.gz')
-
return {
- version: version,
+ version,
semver: versionSemver,
- name: name,
- baseUrl: baseUrl,
- tarballUrl: tarballUrl,
- shasumsUrl: url.resolve(baseUrl, 'SHASUMS256.txt'),
+ name,
+ baseUrl: baseUrl.href,
+ tarballUrl,
+ shasumsUrl: new URL('SHASUMS256.txt', baseUrl).href,
versionDir: (name !== 'node' ? name + '-' : '') + version,
- libUrl32: libUrl32,
- libUrl64: libUrl64,
- libPath32: normalizePath(path.relative(url.parse(baseUrl).path, url.parse(libUrl32).path)),
- libPath64: normalizePath(path.relative(url.parse(baseUrl).path, url.parse(libUrl64).path))
+ ia32: {
+ libUrl: libUrl32.href,
+ libPath: normalizePath(path.relative(baseUrl.pathname, libUrl32.pathname))
+ },
+ x64: {
+ libUrl: libUrl64.href,
+ libPath: normalizePath(path.relative(baseUrl.pathname, libUrl64.pathname))
+ },
+ arm64: {
+ libUrl: libUrlArm64.href,
+ libPath: normalizePath(path.relative(baseUrl.pathname, libUrlArm64.pathname))
+ }
}
}
@@ -137,19 +124,21 @@ function normalizePath (p) {
}
function resolveLibUrl (name, defaultUrl, arch, versionMajor) {
- var base = url.resolve(defaultUrl, './')
- , hasLibUrl = bitsre.test(defaultUrl) || (versionMajor === 3 && bitsreV3.test(defaultUrl))
+ if (!defaultUrl.pathname) defaultUrl = new URL(defaultUrl)
+ const base = new URL('./', defaultUrl)
+ const hasLibUrl = bitsre.test(defaultUrl.pathname) || (versionMajor === 3 && bitsreV3.test(defaultUrl.pathname))
if (!hasLibUrl) {
// let's assume it's a baseUrl then
- if (versionMajor >= 1)
- return url.resolve(base, 'win-' + arch +'/' + name + '.lib')
+ if (versionMajor >= 1) {
+ return new URL('win-' + arch + '/' + name + '.lib', base)
+ }
// prior to io.js@1.0.0 32-bit node.lib lives in /, 64-bit lives in /x64/
- return url.resolve(base, (arch === 'x64' ? 'x64/' : '') + name + '.lib')
+ return new URL((arch === 'x86' ? '' : arch + '/') + name + '.lib', base)
}
// else we have a proper url to a .lib, just make sure it's the right arch
- return defaultUrl.replace(versionMajor === 3 ? bitsreV3 : bitsre, '/win-' + arch + '/')
+ return new URL(defaultUrl.pathname.replace(versionMajor === 3 ? bitsreV3 : bitsre, '/win-' + arch + '/'), defaultUrl)
}
module.exports = processRelease
diff --git a/lib/rebuild.js b/lib/rebuild.js
index 4c6f472aa7..609817665e 100644
--- a/lib/rebuild.js
+++ b/lib/rebuild.js
@@ -1,14 +1,12 @@
+'use strict'
-module.exports = exports = rebuild
-
-exports.usage = 'Runs "clean", "configure" and "build" all at once'
-
-function rebuild (gyp, argv, callback) {
-
+async function rebuild (gyp, argv) {
gyp.todo.push(
- { name: 'clean', args: [] }
+ { name: 'clean', args: [] }
, { name: 'configure', args: argv }
, { name: 'build', args: [] }
)
- process.nextTick(callback)
}
+
+module.exports = rebuild
+module.exports.usage = 'Runs "clean", "configure" and "build" all at once'
diff --git a/lib/remove.js b/lib/remove.js
index eb80981b88..55736f71d9 100644
--- a/lib/remove.js
+++ b/lib/remove.js
@@ -1,52 +1,43 @@
+'use strict'
-module.exports = exports = remove
+const fs = require('graceful-fs').promises
+const path = require('path')
+const log = require('./log')
+const semver = require('semver')
-exports.usage = 'Removes the node development files for the specified version'
-
-/**
- * Module dependencies.
- */
-
-var fs = require('fs')
- , rm = require('rimraf')
- , path = require('path')
- , log = require('npmlog')
- , semver = require('semver')
-
-function remove (gyp, argv, callback) {
-
- var devDir = gyp.devDir
+async function remove (gyp, argv) {
+ const devDir = gyp.devDir
log.verbose('remove', 'using node-gyp dir:', devDir)
// get the user-specified version to remove
- var version = argv[0] || gyp.opts.target
+ let version = argv[0] || gyp.opts.target
log.verbose('remove', 'removing target version:', version)
if (!version) {
- return callback(new Error('You must specify a version number to remove. Ex: "' + process.version + '"'))
+ throw new Error('You must specify a version number to remove. Ex: "' + process.version + '"')
}
- var versionSemver = semver.parse(version)
+ const versionSemver = semver.parse(version)
if (versionSemver) {
// flatten the version Array into a String
version = versionSemver.version
}
- var versionPath = path.resolve(gyp.devDir, version)
+ const versionPath = path.resolve(gyp.devDir, version)
log.verbose('remove', 'removing development files for version:', version)
// first check if its even installed
- fs.stat(versionPath, function (err, stat) {
- if (err) {
- if (err.code == 'ENOENT') {
- callback(null, 'version was already uninstalled: ' + version)
- } else {
- callback(err)
- }
- return
+ try {
+ await fs.stat(versionPath)
+ } catch (err) {
+ if (err.code === 'ENOENT') {
+ return 'version was already uninstalled: ' + version
}
- // Go ahead and delete the dir
- rm(versionPath, callback)
- })
+ throw err
+ }
+ await fs.rm(versionPath, { recursive: true, force: true, maxRetries: 3 })
}
+
+module.exports = remove
+module.exports.usage = 'Removes the node development files for the specified version'
diff --git a/lib/util.js b/lib/util.js
new file mode 100644
index 0000000000..3f6aeeb7dc
--- /dev/null
+++ b/lib/util.js
@@ -0,0 +1,81 @@
+'use strict'
+
+const cp = require('child_process')
+const path = require('path')
+const { openSync, closeSync } = require('graceful-fs')
+const log = require('./log')
+
+const execFile = async (...args) => new Promise((resolve) => {
+ const child = cp.execFile(...args, (...a) => resolve(a))
+ child.stdin.end()
+})
+
+async function regGetValue (key, value, addOpts) {
+ const outReValue = value.replace(/\W/g, '.')
+ const outRe = new RegExp(`^\\s+${outReValue}\\s+REG_\\w+\\s+(\\S.*)$`, 'im')
+ const reg = path.join(process.env.SystemRoot, 'System32', 'reg.exe')
+ const regArgs = ['query', key, '/v', value].concat(addOpts)
+
+ log.silly('reg', 'running', reg, regArgs)
+ const [err, stdout, stderr] = await execFile(reg, regArgs, { encoding: 'utf8' })
+
+ log.silly('reg', 'reg.exe stdout = %j', stdout)
+ if (err || stderr.trim() !== '') {
+ log.silly('reg', 'reg.exe err = %j', err && (err.stack || err))
+ log.silly('reg', 'reg.exe stderr = %j', stderr)
+ if (err) {
+ throw err
+ }
+ throw new Error(stderr)
+ }
+
+ const result = outRe.exec(stdout)
+ if (!result) {
+ log.silly('reg', 'error parsing stdout')
+ throw new Error('Could not parse output of reg.exe')
+ }
+
+ log.silly('reg', 'found: %j', result[1])
+ return result[1]
+}
+
+async function regSearchKeys (keys, value, addOpts) {
+ for (const key of keys) {
+ try {
+ return await regGetValue(key, value, addOpts)
+ } catch {
+ continue
+ }
+ }
+}
+
+/**
+ * Returns the first file or directory from an array of candidates that is
+ * readable by the current user, or undefined if none of the candidates are
+ * readable.
+ */
+function findAccessibleSync (logprefix, dir, candidates) {
+ for (let next = 0; next < candidates.length; next++) {
+ const candidate = path.resolve(dir, candidates[next])
+ let fd
+ try {
+ fd = openSync(candidate, 'r')
+ } catch (e) {
+ // this candidate was not found or not readable, do nothing
+ log.silly(logprefix, 'Could not open %s: %s', candidate, e.message)
+ continue
+ }
+ closeSync(fd)
+ log.silly(logprefix, 'Found readable %s', candidate)
+ return candidate
+ }
+
+ return undefined
+}
+
+module.exports = {
+ execFile,
+ regGetValue,
+ regSearchKeys,
+ findAccessibleSync
+}
diff --git a/macOS_Catalina_acid_test.sh b/macOS_Catalina_acid_test.sh
new file mode 100644
index 0000000000..e1e98941a8
--- /dev/null
+++ b/macOS_Catalina_acid_test.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+pkgs=(
+ "com.apple.pkg.DeveloperToolsCLILeo" # standalone
+ "com.apple.pkg.DeveloperToolsCLI" # from XCode
+ "com.apple.pkg.CLTools_Executables" # Mavericks
+)
+
+for pkg in "${pkgs[@]}"; do
+ output=$(/usr/sbin/pkgutil --pkg-info "$pkg" 2>/dev/null)
+ if [ "$output" ]; then
+ version=$(echo "$output" | grep 'version' | cut -d' ' -f2)
+ break
+ fi
+done
+
+if [ "$version" ]; then
+ echo "Command Line Tools version: $version"
+else
+ echo >&2 'Command Line Tools not found'
+fi
diff --git a/package.json b/package.json
index d0a299ecf7..50f7889c0e 100644
--- a/package.json
+++ b/package.json
@@ -11,8 +11,8 @@
"bindings",
"gyp"
],
- "version": "4.0.0",
- "installVersion": 9,
+ "version": "13.0.0",
+ "installVersion": 11,
"author": "Nathan Rajlich (http://tootallnate.net)",
"repository": {
"type": "git",
@@ -22,28 +22,33 @@
"bin": "./bin/node-gyp.js",
"main": "./lib/node-gyp.js",
"dependencies": {
- "glob": "^7.0.3",
- "graceful-fs": "^4.1.2",
- "mkdirp": "^0.5.0",
- "nopt": "2 || 3",
- "npmlog": "0 || 1 || 2 || 3 || 4",
- "osenv": "0",
- "request": "^2.87.0",
- "rimraf": "2",
- "semver": "~5.3.0",
- "tar": "^4.4.8",
- "which": "1"
+ "env-paths": "^2.2.0",
+ "exponential-backoff": "^3.1.1",
+ "graceful-fs": "^4.2.6",
+ "nopt": "^10.0.0",
+ "proc-log": "^7.0.0",
+ "semver": "^7.3.5",
+ "tar": "^7.5.4",
+ "tinyglobby": "^0.2.12",
+ "undici": "^8.4.1",
+ "which": "^7.0.0"
},
"engines": {
- "node": ">= 4.0.0"
+ "node": "^22.22.2 || ^24.15.0 || >=26.0.0"
},
"devDependencies": {
- "tape": "~4.2.0",
- "bindings": "~1.2.1",
- "nan": "^2.0.0",
- "require-inject": "~1.3.0"
+ "@commitlint/cli": "^21.0.2",
+ "@commitlint/config-conventional": "^21.0.2",
+ "bindings": "^1.5.0",
+ "cross-env": "^10.1.0",
+ "eslint": "^9.39.1",
+ "mocha": "^11.7.5",
+ "nan": "^2.23.1",
+ "neostandard": "^0.13.0",
+ "require-inject": "^1.4.4"
},
"scripts": {
- "test": "tape test/test-*"
+ "lint": "eslint \"*/*.js\" \"test/**/*.js\" \".github/**/*.js\"",
+ "test": "cross-env NODE_GYP_NULL_LOGGER=true mocha --timeout 30000 test/test-download.js test/test-*"
}
}
diff --git a/release-please-config.json b/release-please-config.json
new file mode 100644
index 0000000000..94b8f8110e
--- /dev/null
+++ b/release-please-config.json
@@ -0,0 +1,40 @@
+{
+ "packages": {
+ ".": {
+ "include-component-in-tag": false,
+ "release-type": "node",
+ "changelog-sections": [
+ { "type": "feat", "section": "Features", "hidden": false },
+ { "type": "fix", "section": "Bug Fixes", "hidden": false },
+ { "type": "bin", "section": "Core", "hidden": false },
+ { "type": "gyp", "section": "Core", "hidden": false },
+ { "type": "lib", "section": "Core", "hidden": false },
+ { "type": "src", "section": "Core", "hidden": false },
+ { "type": "test", "section": "Tests", "hidden": false },
+ { "type": "build", "section": "Core", "hidden": false },
+ { "type": "clean", "section": "Core", "hidden": false },
+ { "type": "configure", "section": "Core", "hidden": false },
+ { "type": "install", "section": "Core", "hidden": false },
+ { "type": "list", "section": "Core", "hidden": false },
+ { "type": "rebuild", "section": "Core", "hidden": false },
+ { "type": "remove", "section": "Core", "hidden": false },
+ { "type": "deps", "section": "Core", "hidden": false },
+ { "type": "python", "section": "Core", "hidden": false },
+ { "type": "lin", "section": "Core", "hidden": false },
+ { "type": "linux", "section": "Core", "hidden": false },
+ { "type": "mac", "section": "Core", "hidden": false },
+ { "type": "macos", "section": "Core", "hidden": false },
+ { "type": "win", "section": "Core", "hidden": false },
+ { "type": "windows", "section": "Core", "hidden": false },
+ { "type": "zos", "section": "Core", "hidden": false },
+ { "type": "doc", "section": "Doc", "hidden": false },
+ { "type": "docs", "section": "Doc", "hidden": false },
+ { "type": "readme", "section": "Doc", "hidden": false },
+ { "type": "chore", "section": "Miscellaneous", "hidden": false },
+ { "type": "refactor", "section": "Miscellaneous", "hidden": false },
+ { "type": "ci", "section": "Miscellaneous", "hidden": false },
+ { "type": "meta", "section": "Miscellaneous", "hidden": false }
+ ]
+ }
+ }
+}
diff --git a/src/win_delay_load_hook.cc b/src/win_delay_load_hook.cc
index e75954b605..63e197706d 100644
--- a/src/win_delay_load_hook.cc
+++ b/src/win_delay_load_hook.cc
@@ -1,14 +1,16 @@
/*
* When this file is linked to a DLL, it sets up a delay-load hook that
- * intervenes when the DLL is trying to load 'node.exe' or 'iojs.exe'
- * dynamically. Instead of trying to locate the .exe file it'll just return
- * a handle to the process image.
+ * intervenes when the DLL is trying to load the host executable
+ * dynamically. Instead of trying to locate the .exe file it'll just
+ * return a handle to the process image.
*
- * This allows compiled addons to work when node.exe or iojs.exe is renamed.
+ * This allows compiled addons to work when the host executable is renamed.
*/
#ifdef _MSC_VER
+#pragma managed(push, off)
+
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
@@ -23,14 +25,17 @@ static FARPROC WINAPI load_exe_hook(unsigned int event, DelayLoadInfo* info) {
if (event != dliNotePreLoadLibrary)
return NULL;
- if (_stricmp(info->szDll, "iojs.exe") != 0 &&
- _stricmp(info->szDll, "node.exe") != 0)
+ if (_stricmp(info->szDll, HOST_BINARY) != 0)
return NULL;
- m = GetModuleHandle(NULL);
+ // try for libnode.dll to compat node.js that using 'vcbuild.bat dll'
+ m = GetModuleHandle(TEXT("libnode.dll"));
+ if (m == NULL) m = GetModuleHandle(NULL);
return (FARPROC) m;
}
decltype(__pfnDliNotifyHook2) __pfnDliNotifyHook2 = load_exe_hook;
+#pragma managed(pop)
+
#endif
diff --git a/test/common.js b/test/common.js
new file mode 100644
index 0000000000..489502d4dd
--- /dev/null
+++ b/test/common.js
@@ -0,0 +1,33 @@
+const envPaths = require('env-paths')
+const semver = require('semver')
+
+module.exports.devDir = envPaths('node-gyp', { suffix: '' }).cache
+
+module.exports.poison = (object, property) => {
+ function fail () {
+ console.error(Error(`Property ${property} should not have been accessed.`))
+ process.abort()
+ }
+ const descriptor = {
+ configurable: false,
+ enumerable: false,
+ get: fail,
+ set: fail
+ }
+ Object.defineProperty(object, property, descriptor)
+}
+
+// Only run full test suite when instructed and on a non-prerelease version of node
+module.exports.FULL_TEST =
+ process.env.FULL_TEST === '1' &&
+ process.release.name === 'node' &&
+ semver.prerelease(process.version) === null
+
+module.exports.platformTimeout = (def, obj) => {
+ for (const [key, value] of Object.entries(obj)) {
+ if (process.platform === key) {
+ return value * 60 * 1000
+ }
+ }
+ return def * 60 * 1000
+}
diff --git a/test/docker.sh b/test/docker.sh
deleted file mode 100755
index ac21aa8d75..0000000000
--- a/test/docker.sh
+++ /dev/null
@@ -1,164 +0,0 @@
-#!/bin/bash
-
-#set -e
-
-test_node_versions="0.8.28 0.10.40 0.12.7 4.3.0 5.6.0"
-test_iojs_versions="1.8.4 2.4.0 3.3.0"
-
-myuid=$(id -u)
-mygid=$(id -g)
-__dirname="$(CDPATH= cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-dot_node_gyp=${__dirname}/.node-gyp/
-
-# borrows from https://github.com/rvagg/dnt/
-
-# Simple setup function for a container:
-# setup_container(image id, base image, commands to run to set up)
-setup_container() {
- local container_id="$1"
- local base_container="$2"
- local run_cmd="$3"
-
- # Does this image exist? If yes, ignore
- docker inspect "$container_id" &> /dev/null
- if [[ $? -eq 0 ]]; then
- echo "Found existing container [$container_id]"
- else
- # No such image, so make it
- echo "Did not find container [$container_id], creating..."
- docker run -i $base_container /bin/bash -c "$run_cmd"
- sleep 2
- docker commit $(docker ps -l -q) $container_id
- fi
-}
-
-# Run tests inside each of the versioned containers, copy cwd into npm's copy of node-gyp
-# so it'll be invoked by npm when a compile is needed
-# run_tests(version, test-commands)
-run_tests() {
- local version="$1"
- local run_cmd="$2"
-
- run_cmd="rsync -aAXx --delete --exclude .git --exclude build /node-gyp-src/ /usr/lib/node_modules/npm/node_modules/node-gyp/;
- /bin/su -s /bin/bash node-gyp -c 'cd && ${run_cmd}'"
-
- rm -rf $dot_node_gyp
- mkdir $dot_node_gyp
-
- docker run \
- --rm -i \
- -v ~/.npm/:/node-gyp/.npm/ \
- -v ${dot_node_gyp}:/node-gyp/.node-gyp/ \
- -v $(pwd):/node-gyp-src/:ro \
- node-gyp-test/${version} /bin/bash -c "${run_cmd}"
-}
-
-# A base image with build tools and a user account
-setup_container "node-gyp-test/base" "ubuntu:14.04" "
- adduser --gecos node-gyp --home /node-gyp/ --disabled-login node-gyp --uid $myuid &&
- echo "node-gyp:node-gyp" | chpasswd &&
- apt-get update &&
- apt-get install -y build-essential python git rsync curl
-"
-
-# An image on top of the base containing clones of repos we want to use for testing
-setup_container "node-gyp-test/clones" "node-gyp-test/base" "
- cd /node-gyp/ && git clone https://github.com/justmoon/node-bignum.git &&
- cd /node-gyp/ && git clone https://github.com/bnoordhuis/node-buffertools.git &&
- chown -R node-gyp.node-gyp /node-gyp/
-"
-
-# An image for each of the node versions we want to test with that version installed and the latest npm
-for v in $test_node_versions; do
- setup_container "node-gyp-test/${v}" "node-gyp-test/clones" "
- curl -sL https://nodejs.org/dist/v${v}/node-v${v}-linux-x64.tar.gz | tar -zxv --strip-components=1 -C /usr/ &&
- npm install npm@latest -g &&
- node -v && npm -v
- "
-done
-
-# An image for each of the io.js versions we want to test with that version installed and the latest npm
-for v in $test_iojs_versions; do
- setup_container "node-gyp-test/${v}" "node-gyp-test/clones" "
- curl -sL https://iojs.org/dist/v${v}/iojs-v${v}-linux-x64.tar.gz | tar -zxv --strip-components=1 -C /usr/ &&
- npm install npm@latest -g &&
- node -v && npm -v
- "
-done
-
-# Run the tests for all of the test images we've created,
-# we should see node-gyp doing its download, configure and run thing
-# _NOTE: bignum doesn't compile on 0.8 currently so it'll fail for that version only_
-for v in $test_node_versions $test_iojs_versions; do
- run_tests $v "
- cd node-buffertools && npm install --loglevel=info && npm test && cd
- "
- # removed for now, too noisy: cd node-bignum && npm install --loglevel=info && npm test
-done
-
-# Test use of --target=x.y.z to compile against alternate versions
-test_download_node_version() {
- local run_with_ver="$1"
- local expected_dir="$2"
- local expected_ver="$3"
- run_tests $run_with_ver "cd node-buffertools && npm install --loglevel=info --target=${expected_ver}"
- local node_ver=$(cat "${dot_node_gyp}${expected_dir}/node_version.h" | grep '#define NODE_\w*_VERSION [0-9]*$')
- node_ver=$(echo $node_ver | sed 's/#define NODE_[A-Z]*_VERSION //g' | sed 's/ /./g')
- if [ "X$(echo $node_ver)" != "X${expected_ver}" ]; then
- echo "Did not download v${expected_ver} using --target, instead got: $(echo $node_ver)"
- exit 1
- fi
- echo "Verified correct download of [v${node_ver}]"
-}
-
-test_download_node_version "0.12.7" "0.10.30/src" "0.10.30"
-test_download_node_version "3.3.0" "iojs-1.8.4/src" "1.8.4"
-# should download the headers file
-test_download_node_version "3.3.0" "iojs-3.3.0/include/node" "3.3.0"
-test_download_node_version "4.3.0" "4.3.0/include/node" "4.3.0"
-test_download_node_version "5.6.0" "5.6.0/include/node" "5.6.0"
-
-# TODO: test --dist-url by starting up a localhost server and serving up tarballs
-
-# testing --dist-url, using simple-proxy.js to make localhost work as a distribution
-# point for tarballs
-# we can test whether it uses the proxy because after 2 connections the proxy will
-# die and therefore should not be running at the end of the test, `nc` can tell us this
-run_tests "3.3.0" "
- (node /node-gyp-src/test/simple-proxy.js 8080 /foobar/ https://iojs.org/dist/ &) &&
- cd node-buffertools &&
- /node-gyp-src/bin/node-gyp.js --loglevel=info --dist-url=http://localhost:8080/foobar/ rebuild &&
- nc -z localhost 8080 && echo -e \"\\n\\n\\033[31mFAILED TO USE LOCAL PROXY\\033[39m\\n\\n\"
-"
-
-# REMOVE after next semver-major
-run_tests "3.3.0" "
- (node /node-gyp-src/test/simple-proxy.js 8080 /doobar/ https://iojs.org/dist/ &) &&
- cd node-buffertools &&
- NVM_IOJS_ORG_MIRROR=http://localhost:8080/doobar/ /node-gyp-src/bin/node-gyp.js --loglevel=info rebuild &&
- nc -z localhost 8080 && echo -e \"\\n\\n\\033[31mFAILED TO USE LOCAL PROXY\\033[39m\\n\\n\"
-"
-
-# REMOVE after next semver-major
-run_tests "0.12.7" "
- (node /node-gyp-src/test/simple-proxy.js 8080 /boombar/ https://nodejs.org/dist/ &) &&
- cd node-buffertools &&
- NVM_NODEJS_ORG_MIRROR=http://localhost:8080/boombar/ /node-gyp-src/bin/node-gyp.js --loglevel=info rebuild &&
- nc -z localhost 8080 && echo -e \"\\n\\n\\033[31mFAILED TO USE LOCAL PROXY\\033[39m\\n\\n\"
-"
-
-run_tests "3.3.0" "
- (node /node-gyp-src/test/simple-proxy.js 8080 /doobar/ https://iojs.org/dist/ &) &&
- cd node-buffertools &&
- IOJS_ORG_MIRROR=http://localhost:8080/doobar/ /node-gyp-src/bin/node-gyp.js --loglevel=info rebuild &&
- nc -z localhost 8080 && echo -e \"\\n\\n\\033[31mFAILED TO USE LOCAL PROXY\\033[39m\\n\\n\"
-"
-
-run_tests "0.12.7" "
- (node /node-gyp-src/test/simple-proxy.js 8080 /boombar/ https://nodejs.org/dist/ &) &&
- cd node-buffertools &&
- NODEJS_ORG_MIRROR=http://localhost:8080/boombar/ /node-gyp-src/bin/node-gyp.js --loglevel=info rebuild &&
- nc -z localhost 8080 && echo -e \"\\n\\n\\033[31mFAILED TO USE LOCAL PROXY\\033[39m\\n\\n\"
-"
-
-rm -rf $dot_node_gyp
diff --git a/test/fixtures/VSSetup_VS_2019_Professional_workload.txt b/test/fixtures/VSSetup_VS_2019_Professional_workload.txt
new file mode 100644
index 0000000000..efafa41707
--- /dev/null
+++ b/test/fixtures/VSSetup_VS_2019_Professional_workload.txt
@@ -0,0 +1,235 @@
+[
+ {
+ "InstanceId": "2619cf21",
+ "InstallationName": "VisualStudio/16.11.33+34407.143",
+ "InstallationPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Professional",
+ "InstallationVersion": {
+ "Major": 16,
+ "Minor": 11,
+ "Build": 34407,
+ "Revision": 143,
+ "MajorRevision": 0,
+ "MinorRevision": 143
+ },
+ "InstallDate": "\/Date(1706804943503)\/",
+ "State": 4294967295,
+ "DisplayName": "Visual Studio Professional 2019",
+ "Description": "Professional IDE best suited to small teams",
+ "ProductPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Professional\\Common7\\IDE\\devenv.exe",
+ "Product": {
+ "Id": "Microsoft.VisualStudio.Product.Professional",
+ "Version": {
+ "Major": 16,
+ "Minor": 11,
+ "Build": 34407,
+ "Revision": 143,
+ "MajorRevision": 0,
+ "MinorRevision": 143
+ },
+ "Chip": null,
+ "Branch": null,
+ "Type": "Product",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Product.Professional,version=16.11.34407.143"
+ },
+ "Packages": [
+ {
+ "Id": "Microsoft.VisualStudio.Product.Professional",
+ "Version": "16.11.34407.143",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Product",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Product.Professional,version=16.11.34407.143"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Component.VC.14.29.16.10.ATL",
+ "Version": "16.11.31314.313",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Component",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Component.VC.14.29.16.10.ATL,version=16.11.31314.313"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.VC.MSBuild.X64.v142",
+ "Version": "16.11.31503.54",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Vsix",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.VC.MSBuild.X64.v142,version=16.11.31503.54"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.VC.MSBuild.X64",
+ "Version": "16.11.31503.54",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Vsix",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.VC.MSBuild.X64,version=16.11.31503.54"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.VC.MSBuild.x86.v142",
+ "Version": "16.11.31503.54",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Vsix",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.VC.MSBuild.x86.v142,version=16.11.31503.54"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.VC.MSBuild.X86",
+ "Version": "16.11.31503.54",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Vsix",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.VC.MSBuild.X86,version=16.11.31503.54"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.VC.MSBuild.Base",
+ "Version": "16.11.31829.152",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Vsix",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.VC.MSBuild.Base,version=16.11.31829.152"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.VC.MSBuild.Base.Resources",
+ "Version": "16.11.31829.152",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Vsix",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.VC.MSBuild.Base.Resources,version=16.11.31829.152,language=en-US"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Branding.Professional",
+ "Version": "16.11.31605.320",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Vsix",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Branding.Professional,version=16.11.31605.320,language=en-US"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Component.Windows10SDK.19041",
+ "Version": "16.10.31205.252",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Component",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Component.Windows10SDK.19041,version=16.10.31205.252"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ "Version": "16.11.32406.258",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Component",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Component.VC.Tools.x86.x64,version=16.11.32406.258"
+ }
+ ],
+ "Properties": [
+ {
+ "Key": "CampaignId",
+ "Value": "09"
+ },
+ {
+ "Key": "SetupEngineFilePath",
+ "Value": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\setup.exe"
+ },
+ {
+ "Key": "Nickname",
+ "Value": ""
+ },
+ {
+ "Key": "ChannelManifestId",
+ "Value": "VisualStudio.16.Release/16.11.33+34407.143"
+ }
+ ],
+ "Errors": null,
+ "EnginePath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\resources\\app\\ServiceHub\\Services\\Microsoft.VisualStudio.Setup.Service",
+ "IsComplete": true,
+ "IsLaunchable": true,
+ "CatalogInfo": [
+ {
+ "Key": "Id",
+ "Value": "VisualStudio/16.11.33+34407.143"
+ },
+ {
+ "Key": "BuildBranch",
+ "Value": "d16.11"
+ },
+ {
+ "Key": "BuildVersion",
+ "Value": "16.11.34407.143"
+ },
+ {
+ "Key": "LocalBuild",
+ "Value": "build-lab"
+ },
+ {
+ "Key": "ManifestName",
+ "Value": "VisualStudio"
+ },
+ {
+ "Key": "ManifestType",
+ "Value": "installer"
+ },
+ {
+ "Key": "ProductDisplayVersion",
+ "Value": "16.11.33"
+ },
+ {
+ "Key": "ProductLine",
+ "Value": "Dev16"
+ },
+ {
+ "Key": "ProductLineVersion",
+ "Value": "2019"
+ },
+ {
+ "Key": "ProductMilestone",
+ "Value": "RTW"
+ },
+ {
+ "Key": "ProductMilestoneIsPreRelease",
+ "Value": "False"
+ },
+ {
+ "Key": "ProductName",
+ "Value": "Visual Studio"
+ },
+ {
+ "Key": "ProductPatchVersion",
+ "Value": "33"
+ },
+ {
+ "Key": "ProductPreReleaseMilestoneSuffix",
+ "Value": "1.0"
+ },
+ {
+ "Key": "ProductSemanticVersion",
+ "Value": "16.11.33+34407.143"
+ },
+ {
+ "Key": "RequiredEngineVersion",
+ "Value": "2.11.72.18200"
+ }
+ ],
+ "IsPrerelease": false,
+ "PSPath": "Microsoft.PowerShell.Core\\FileSystem::C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "UpdateDate": "2024-01-09T19:19:11.0115234Z",
+ "ResolvedInstallationPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "ChannelId": "VisualStudio.17.Release",
+ "InstalledChannelId": "VisualStudio.17.Release",
+ "ChannelUri": "https://aka.ms/vs/17/release/channel",
+ "InstalledChannelUri": "https://aka.ms/vs/17/release/channel",
+ "ReleaseNotes": "https://docs.microsoft.com/en-us/visualstudio/releases/2022/release-notes-v17.8#17.8.4",
+ "ThirdPartyNotices": "https://go.microsoft.com/fwlink/?LinkId=661288"
+ }
+]
diff --git a/test/fixtures/VSSetup_VS_2022_VS2019_workload.txt b/test/fixtures/VSSetup_VS_2022_VS2019_workload.txt
new file mode 100644
index 0000000000..a6524171ae
--- /dev/null
+++ b/test/fixtures/VSSetup_VS_2022_VS2019_workload.txt
@@ -0,0 +1,371 @@
+[
+ {
+ "InstanceId": "621862c0",
+ "InstallationName": "VisualStudio/17.8.3+34330.188",
+ "InstallationPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "InstallationVersion": {
+ "Major": 17,
+ "Minor": 8,
+ "Build": 34330,
+ "Revision": 188,
+ "MajorRevision": 0,
+ "MinorRevision": 188
+ },
+ "InstallDate": "\/Date(1703254955000)\/",
+ "State": 4294967295,
+ "DisplayName": "Visual Studio Enterprise 2022",
+ "Description": "Scalable, end-to-end solution for teams of any size",
+ "ProductPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\IDE\\devenv.exe",
+ "Product": {
+ "Id": "Microsoft.VisualStudio.Product.Enterprise",
+ "Version": {
+ "Major": 17,
+ "Minor": 8,
+ "Build": 34330,
+ "Revision": 188,
+ "MajorRevision": 0,
+ "MinorRevision": 188
+ },
+ "Chip": "x64",
+ "Branch": null,
+ "Type": "Product",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Product.Enterprise,version=17.8.34330.188,chip=x64"
+ },
+ "Packages": [
+ {
+ "Id": "Microsoft.VisualStudio.Product.Enterprise",
+ "Version": "17.8.34330.188",
+ "Chip": "x64",
+ "Branch": null,
+ "Type": "Product",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Product.Enterprise,version=17.8.34330.188,chip=x64"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ "Version": "17.8.34129.139",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Component",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Component.VC.Tools.x86.x64,version=17.8.34129.139"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Component.Windows11SDK.22000",
+ "Version": "17.8.34129.139",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Component",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Component.Windows11SDK.22000,version=17.8.34129.139"
+ },
+ {
+ "Id": "Win11SDK_10.0.22000",
+ "Version": "10.0.22000.4",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Exe",
+ "IsExtension": false,
+ "UniqueId": "Win11SDK_10.0.22000,version=10.0.22000.4"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Component.Windows10SDK.20348",
+ "Version": "17.8.34129.139",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Component",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Component.Windows10SDK.20348,version=17.8.34129.139"
+ },
+ {
+ "Id": "Win10SDK_10.0.20348",
+ "Version": "10.0.20348.3",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Exe",
+ "IsExtension": false,
+ "UniqueId": "Win10SDK_10.0.20348,version=10.0.20348.3"
+ }
+ ],
+ "Properties": [
+ {
+ "Key": "CampaignId",
+ "Value": ""
+ },
+ {
+ "Key": "SetupEngineFilePath",
+ "Value": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\setup.exe"
+ },
+ {
+ "Key": "Nickname",
+ "Value": ""
+ },
+ {
+ "Key": "ChannelManifestId",
+ "Value": "VisualStudio.17.Release/17.8.3+34330.188"
+ }
+ ],
+ "Errors": null,
+ "EnginePath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\resources\\app\\ServiceHub\\Services\\Microsoft.VisualStudio.Setup.Service",
+ "IsComplete": true,
+ "IsLaunchable": true,
+ "CatalogInfo": [
+ {
+ "Key": "Id",
+ "Value": "VisualStudio/17.8.3+34330.188"
+ },
+ {
+ "Key": "BuildBranch",
+ "Value": "d17.8"
+ },
+ {
+ "Key": "BuildVersion",
+ "Value": "17.8.34330.188"
+ }
+ ],
+ "IsPrerelease": false,
+ "PSPath": "Microsoft.PowerShell.Core\\FileSystem::C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "UpdateDate": "2023-12-22T14:22:35.1818213Z",
+ "ResolvedInstallationPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "ChannelId": "VisualStudio.17.Release",
+ "InstalledChannelId": "VisualStudio.17.Release",
+ "ChannelUri": "https://aka.ms/vs/17/release/channel",
+ "InstalledChannelUri": "https://aka.ms/vs/17/release/channel",
+ "ReleaseNotes": "https://docs.microsoft.com/en-us/visualstudio/releases/2022/release-notes-v17.8#17.8.3",
+ "ThirdPartyNotices": "https://go.microsoft.com/fwlink/?LinkId=661288"
+},
+{
+ "InstanceId": "2619cf21",
+ "InstallationName": "VisualStudio/16.11.33+34407.143",
+ "InstallationPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Professional",
+ "InstallationVersion": {
+ "Major": 16,
+ "Minor": 11,
+ "Build": 34407,
+ "Revision": 143,
+ "MajorRevision": 0,
+ "MinorRevision": 143
+ },
+ "InstallDate": "\/Date(1706804943503)\/",
+ "State": 4294967295,
+ "DisplayName": "Visual Studio Professional 2019",
+ "Description": "Professional IDE best suited to small teams",
+ "ProductPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Professional\\Common7\\IDE\\devenv.exe",
+ "Product": {
+ "Id": "Microsoft.VisualStudio.Product.Professional",
+ "Version": {
+ "Major": 16,
+ "Minor": 11,
+ "Build": 34407,
+ "Revision": 143,
+ "MajorRevision": 0,
+ "MinorRevision": 143
+ },
+ "Chip": null,
+ "Branch": null,
+ "Type": "Product",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Product.Professional,version=16.11.34407.143"
+ },
+ "Packages": [
+ {
+ "Id": "Microsoft.VisualStudio.Product.Professional",
+ "Version": "16.11.34407.143",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Product",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Product.Professional,version=16.11.34407.143"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Component.VC.14.29.16.10.ATL",
+ "Version": "16.11.31314.313",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Component",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Component.VC.14.29.16.10.ATL,version=16.11.31314.313"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.VC.MSBuild.X64.v142",
+ "Version": "16.11.31503.54",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Vsix",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.VC.MSBuild.X64.v142,version=16.11.31503.54"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.VC.MSBuild.X64",
+ "Version": "16.11.31503.54",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Vsix",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.VC.MSBuild.X64,version=16.11.31503.54"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.VC.MSBuild.x86.v142",
+ "Version": "16.11.31503.54",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Vsix",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.VC.MSBuild.x86.v142,version=16.11.31503.54"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.VC.MSBuild.X86",
+ "Version": "16.11.31503.54",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Vsix",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.VC.MSBuild.X86,version=16.11.31503.54"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.VC.MSBuild.Base",
+ "Version": "16.11.31829.152",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Vsix",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.VC.MSBuild.Base,version=16.11.31829.152"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.VC.MSBuild.Base.Resources",
+ "Version": "16.11.31829.152",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Vsix",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.VC.MSBuild.Base.Resources,version=16.11.31829.152,language=en-US"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Branding.Professional",
+ "Version": "16.11.31605.320",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Vsix",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Branding.Professional,version=16.11.31605.320,language=en-US"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Component.Windows10SDK.19041",
+ "Version": "16.10.31205.252",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Component",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Component.Windows10SDK.19041,version=16.10.31205.252"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ "Version": "16.11.32406.258",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Component",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Component.VC.Tools.x86.x64,version=16.11.32406.258"
+ }
+ ],
+ "Properties": [
+ {
+ "Key": "CampaignId",
+ "Value": "09"
+ },
+ {
+ "Key": "SetupEngineFilePath",
+ "Value": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\setup.exe"
+ },
+ {
+ "Key": "Nickname",
+ "Value": ""
+ },
+ {
+ "Key": "ChannelManifestId",
+ "Value": "VisualStudio.16.Release/16.11.33+34407.143"
+ }
+ ],
+ "Errors": null,
+ "EnginePath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\resources\\app\\ServiceHub\\Services\\Microsoft.VisualStudio.Setup.Service",
+ "IsComplete": true,
+ "IsLaunchable": true,
+ "CatalogInfo": [
+ {
+ "Key": "Id",
+ "Value": "VisualStudio/16.11.33+34407.143"
+ },
+ {
+ "Key": "BuildBranch",
+ "Value": "d16.11"
+ },
+ {
+ "Key": "BuildVersion",
+ "Value": "16.11.34407.143"
+ },
+ {
+ "Key": "LocalBuild",
+ "Value": "build-lab"
+ },
+ {
+ "Key": "ManifestName",
+ "Value": "VisualStudio"
+ },
+ {
+ "Key": "ManifestType",
+ "Value": "installer"
+ },
+ {
+ "Key": "ProductDisplayVersion",
+ "Value": "16.11.33"
+ },
+ {
+ "Key": "ProductLine",
+ "Value": "Dev16"
+ },
+ {
+ "Key": "ProductLineVersion",
+ "Value": "2019"
+ },
+ {
+ "Key": "ProductMilestone",
+ "Value": "RTW"
+ },
+ {
+ "Key": "ProductMilestoneIsPreRelease",
+ "Value": "False"
+ },
+ {
+ "Key": "ProductName",
+ "Value": "Visual Studio"
+ },
+ {
+ "Key": "ProductPatchVersion",
+ "Value": "33"
+ },
+ {
+ "Key": "ProductPreReleaseMilestoneSuffix",
+ "Value": "1.0"
+ },
+ {
+ "Key": "ProductSemanticVersion",
+ "Value": "16.11.33+34407.143"
+ },
+ {
+ "Key": "RequiredEngineVersion",
+ "Value": "2.11.72.18200"
+ }
+ ],
+ "IsPrerelease": false,
+ "PSPath": "Microsoft.PowerShell.Core\\FileSystem::C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "UpdateDate": "2024-01-09T19:19:11.0115234Z",
+ "ResolvedInstallationPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "ChannelId": "VisualStudio.17.Release",
+ "InstalledChannelId": "VisualStudio.17.Release",
+ "ChannelUri": "https://aka.ms/vs/17/release/channel",
+ "InstalledChannelUri": "https://aka.ms/vs/17/release/channel",
+ "ReleaseNotes": "https://docs.microsoft.com/en-us/visualstudio/releases/2022/release-notes-v17.8#17.8.4",
+ "ThirdPartyNotices": "https://go.microsoft.com/fwlink/?LinkId=661288"
+ }
+]
diff --git a/test/fixtures/VSSetup_VS_2022_multiple_install.txt b/test/fixtures/VSSetup_VS_2022_multiple_install.txt
new file mode 100644
index 0000000000..2bfd67a190
--- /dev/null
+++ b/test/fixtures/VSSetup_VS_2022_multiple_install.txt
@@ -0,0 +1,369 @@
+[
+ {
+ "InstanceId": "621862c0",
+ "InstallationName": "VisualStudio/17.8.3+34330.188",
+ "InstallationPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "InstallationVersion": {
+ "Major": 17,
+ "Minor": 8,
+ "Build": 34330,
+ "Revision": 188,
+ "MajorRevision": 0,
+ "MinorRevision": 188
+ },
+ "InstallDate": "\/Date(1703254955000)\/",
+ "State": 4294967295,
+ "DisplayName": "Visual Studio Enterprise 2022",
+ "Description": "Scalable, end-to-end solution for teams of any size",
+ "ProductPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\IDE\\devenv.exe",
+ "Product": {
+ "Id": "Microsoft.VisualStudio.Product.Enterprise",
+ "Version": {
+ "Major": 17,
+ "Minor": 8,
+ "Build": 34330,
+ "Revision": 188,
+ "MajorRevision": 0,
+ "MinorRevision": 188
+ },
+ "Chip": "x64",
+ "Branch": null,
+ "Type": "Product",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Product.Enterprise,version=17.8.34330.188,chip=x64"
+ },
+ "Packages": [
+ {
+ "Id": "Microsoft.VisualStudio.Product.Enterprise",
+ "Version": "17.8.34330.188",
+ "Chip": "x64",
+ "Branch": null,
+ "Type": "Product",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Product.Enterprise,version=17.8.34330.188,chip=x64"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ "Version": "17.8.34129.139",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Component",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Component.VC.Tools.x86.x64,version=17.8.34129.139"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Component.Windows11SDK.22000",
+ "Version": "17.8.34129.139",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Component",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Component.Windows11SDK.22000,version=17.8.34129.139"
+ },
+ {
+ "Id": "Win11SDK_10.0.22000",
+ "Version": "10.0.22000.4",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Exe",
+ "IsExtension": false,
+ "UniqueId": "Win11SDK_10.0.22000,version=10.0.22000.4"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Component.Windows10SDK.20348",
+ "Version": "17.8.34129.139",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Component",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Component.Windows10SDK.20348,version=17.8.34129.139"
+ },
+ {
+ "Id": "Win10SDK_10.0.20348",
+ "Version": "10.0.20348.3",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Exe",
+ "IsExtension": false,
+ "UniqueId": "Win10SDK_10.0.20348,version=10.0.20348.3"
+ }
+ ],
+ "Properties": [
+ {
+ "Key": "CampaignId",
+ "Value": ""
+ },
+ {
+ "Key": "SetupEngineFilePath",
+ "Value": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\setup.exe"
+ },
+ {
+ "Key": "Nickname",
+ "Value": ""
+ },
+ {
+ "Key": "ChannelManifestId",
+ "Value": "VisualStudio.17.Release/17.8.3+34330.188"
+ }
+ ],
+ "Errors": null,
+ "EnginePath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\resources\\app\\ServiceHub\\Services\\Microsoft.VisualStudio.Setup.Service",
+ "IsComplete": true,
+ "IsLaunchable": true,
+ "CatalogInfo": [
+ {
+ "Key": "Id",
+ "Value": "VisualStudio/17.8.3+34330.188"
+ },
+ {
+ "Key": "BuildBranch",
+ "Value": "d17.8"
+ },
+ {
+ "Key": "BuildVersion",
+ "Value": "17.8.34330.188"
+ },
+ {
+ "Key": "LocalBuild",
+ "Value": "build-lab"
+ },
+ {
+ "Key": "ManifestName",
+ "Value": "VisualStudio"
+ },
+ {
+ "Key": "ManifestType",
+ "Value": "installer"
+ },
+ {
+ "Key": "ProductDisplayVersion",
+ "Value": "17.8.3"
+ },
+ {
+ "Key": "ProductLine",
+ "Value": "Dev17"
+ },
+ {
+ "Key": "ProductLineVersion",
+ "Value": "2022"
+ },
+ {
+ "Key": "ProductMilestone",
+ "Value": "RTW"
+ },
+ {
+ "Key": "ProductMilestoneIsPreRelease",
+ "Value": "False"
+ },
+ {
+ "Key": "ProductName",
+ "Value": "Visual Studio"
+ },
+ {
+ "Key": "ProductPatchVersion",
+ "Value": "3"
+ },
+ {
+ "Key": "ProductPreReleaseMilestoneSuffix",
+ "Value": "1.0"
+ },
+ {
+ "Key": "ProductSemanticVersion",
+ "Value": "17.8.3+34330.188"
+ },
+ {
+ "Key": "RequiredEngineVersion",
+ "Value": "3.8.2112.61926"
+ }
+ ],
+ "IsPrerelease": false,
+ "PSPath": "Microsoft.PowerShell.Core\\FileSystem::C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "UpdateDate": "2023-12-22T14:22:35.1818213Z",
+ "ResolvedInstallationPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "ChannelId": "VisualStudio.17.Release",
+ "InstalledChannelId": "VisualStudio.17.Release",
+ "ChannelUri": "https://aka.ms/vs/17/release/channel",
+ "InstalledChannelUri": "https://aka.ms/vs/17/release/channel",
+ "ReleaseNotes": "https://docs.microsoft.com/en-us/visualstudio/releases/2022/release-notes-v17.8#17.8.3",
+ "ThirdPartyNotices": "https://go.microsoft.com/fwlink/?LinkId=661288"
+ },
+ {
+ "InstanceId": "dd50c6cc",
+ "InstallationName": "VisualStudio/17.8.3+34330.188",
+ "InstallationPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools",
+ "InstallationVersion": {
+ "Major": 17,
+ "Minor": 8,
+ "Build": 34330,
+ "Revision": 188,
+ "MajorRevision": 0,
+ "MinorRevision": 188
+ },
+ "InstallDate": "\/Date(1703262914503)\/",
+ "State": 4294967295,
+ "DisplayName": "Visual Studio Build Tools 2022",
+ "Description": "The Visual Studio Build Tools allows you to build native and managed MSBuild-based applications without requiring the Visual Studio IDE. There are options to install the Visual C++ compilers and libraries, MFC, ATL, and C++/CLI support.",
+ "ProductPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools\\Common7\\Tools\\LaunchDevCmd.bat",
+ "Product": {
+ "Id": "Microsoft.VisualStudio.Product.BuildTools",
+ "Version": {
+ "Major": 17,
+ "Minor": 8,
+ "Build": 34330,
+ "Revision": 188,
+ "MajorRevision": 0,
+ "MinorRevision": 188
+ },
+ "Chip": null,
+ "Branch": null,
+ "Type": "Product",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Product.BuildTools,version=17.8.34330.188"
+ },
+ "Packages": [
+ {
+ "Id": "Microsoft.VisualStudio.Product.BuildTools",
+ "Version": "17.8.34330.188",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Product",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Product.BuildTools,version=17.8.34330.188"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Workload.MSBuildTools",
+ "Version": "17.8.34129.139",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Workload",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Workload.MSBuildTools,version=17.8.34129.139"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.NuGet.BuildTools",
+ "Version": "17.0.60800.131",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Vsix",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.NuGet.BuildTools,version=17.0.60800.131"
+ },
+ {
+ "Id": "Microsoft.Build.UnGAC",
+ "Version": "17.8.3.2351904",
+ "Chip": "neutral",
+ "Branch": null,
+ "Type": "Exe",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.Build.UnGAC,version=17.8.3.2351904,chip=neutral,language=neutral"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.VC.Icons",
+ "Version": "17.8.34129.139",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Vsix",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.VC.Icons,version=17.8.34129.139"
+ }
+ ],
+ "Properties": [
+ {
+ "Key": "CampaignId",
+ "Value": "09"
+ },
+ {
+ "Key": "SetupEngineFilePath",
+ "Value": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\setup.exe"
+ },
+ {
+ "Key": "Nickname",
+ "Value": "2"
+ },
+ {
+ "Key": "ChannelManifestId",
+ "Value": "VisualStudio.17.Release/17.8.3+34330.188"
+ }
+ ],
+ "Errors": null,
+ "EnginePath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\resources\\app\\ServiceHub\\Services\\Microsoft.VisualStudio.Setup.Service",
+ "IsComplete": true,
+ "IsLaunchable": true,
+ "CatalogInfo": [
+ {
+ "Key": "Id",
+ "Value": "VisualStudio/17.8.3+34330.188"
+ },
+ {
+ "Key": "BuildBranch",
+ "Value": "d17.8"
+ },
+ {
+ "Key": "BuildVersion",
+ "Value": "17.8.34330.188"
+ },
+ {
+ "Key": "LocalBuild",
+ "Value": "build-lab"
+ },
+ {
+ "Key": "ManifestName",
+ "Value": "VisualStudio"
+ },
+ {
+ "Key": "ManifestType",
+ "Value": "installer"
+ },
+ {
+ "Key": "ProductDisplayVersion",
+ "Value": "17.8.3"
+ },
+ {
+ "Key": "ProductLine",
+ "Value": "Dev17"
+ },
+ {
+ "Key": "ProductLineVersion",
+ "Value": "2022"
+ },
+ {
+ "Key": "ProductMilestone",
+ "Value": "RTW"
+ },
+ {
+ "Key": "ProductMilestoneIsPreRelease",
+ "Value": "False"
+ },
+ {
+ "Key": "ProductName",
+ "Value": "Visual Studio"
+ },
+ {
+ "Key": "ProductPatchVersion",
+ "Value": "3"
+ },
+ {
+ "Key": "ProductPreReleaseMilestoneSuffix",
+ "Value": "1.0"
+ },
+ {
+ "Key": "ProductSemanticVersion",
+ "Value": "17.8.3+34330.188"
+ },
+ {
+ "Key": "RequiredEngineVersion",
+ "Value": "3.8.2112.61926"
+ }
+ ],
+ "IsPrerelease": false,
+ "PSPath": "Microsoft.PowerShell.Core\\FileSystem::C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "UpdateDate": "2023-12-22T14:22:35.1818213Z",
+ "ResolvedInstallationPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "ChannelId": "VisualStudio.17.Release",
+ "InstalledChannelId": "VisualStudio.17.Release",
+ "ChannelUri": "https://aka.ms/vs/17/release/channel",
+ "InstalledChannelUri": "https://aka.ms/vs/17/release/channel",
+ "ReleaseNotes": "https://docs.microsoft.com/en-us/visualstudio/releases/2022/release-notes-v17.8#17.8.3",
+ "ThirdPartyNotices": "https://go.microsoft.com/fwlink/?LinkId=661288"
+ }
+]
diff --git a/test/fixtures/VSSetup_VS_2022_workload.txt b/test/fixtures/VSSetup_VS_2022_workload.txt
new file mode 100644
index 0000000000..3e1abdc30d
--- /dev/null
+++ b/test/fixtures/VSSetup_VS_2022_workload.txt
@@ -0,0 +1,136 @@
+{
+ "InstanceId": "621862c0",
+ "InstallationName": "VisualStudio/17.8.3+34330.188",
+ "InstallationPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "InstallationVersion": {
+ "Major": 17,
+ "Minor": 8,
+ "Build": 34330,
+ "Revision": 188,
+ "MajorRevision": 0,
+ "MinorRevision": 188
+ },
+ "InstallDate": "\/Date(1703254955000)\/",
+ "State": 4294967295,
+ "DisplayName": "Visual Studio Enterprise 2022",
+ "Description": "Scalable, end-to-end solution for teams of any size",
+ "ProductPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\IDE\\devenv.exe",
+ "Product": {
+ "Id": "Microsoft.VisualStudio.Product.Enterprise",
+ "Version": {
+ "Major": 17,
+ "Minor": 8,
+ "Build": 34330,
+ "Revision": 188,
+ "MajorRevision": 0,
+ "MinorRevision": 188
+ },
+ "Chip": "x64",
+ "Branch": null,
+ "Type": "Product",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Product.Enterprise,version=17.8.34330.188,chip=x64"
+ },
+ "Packages": [
+ {
+ "Id": "Microsoft.VisualStudio.Product.Enterprise",
+ "Version": "17.8.34330.188",
+ "Chip": "x64",
+ "Branch": null,
+ "Type": "Product",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Product.Enterprise,version=17.8.34330.188,chip=x64"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ "Version": "17.8.34129.139",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Component",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Component.VC.Tools.x86.x64,version=17.8.34129.139"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Component.Windows11SDK.22000",
+ "Version": "17.8.34129.139",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Component",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Component.Windows11SDK.22000,version=17.8.34129.139"
+ },
+ {
+ "Id": "Win11SDK_10.0.22000",
+ "Version": "10.0.22000.4",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Exe",
+ "IsExtension": false,
+ "UniqueId": "Win11SDK_10.0.22000,version=10.0.22000.4"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Component.Windows10SDK.20348",
+ "Version": "17.8.34129.139",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Component",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Component.Windows10SDK.20348,version=17.8.34129.139"
+ },
+ {
+ "Id": "Win10SDK_10.0.20348",
+ "Version": "10.0.20348.3",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Exe",
+ "IsExtension": false,
+ "UniqueId": "Win10SDK_10.0.20348,version=10.0.20348.3"
+ }
+ ],
+ "Properties": [
+ {
+ "Key": "CampaignId",
+ "Value": ""
+ },
+ {
+ "Key": "SetupEngineFilePath",
+ "Value": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\setup.exe"
+ },
+ {
+ "Key": "Nickname",
+ "Value": ""
+ },
+ {
+ "Key": "ChannelManifestId",
+ "Value": "VisualStudio.17.Release/17.8.3+34330.188"
+ }
+ ],
+ "Errors": null,
+ "EnginePath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\resources\\app\\ServiceHub\\Services\\Microsoft.VisualStudio.Setup.Service",
+ "IsComplete": true,
+ "IsLaunchable": true,
+ "CatalogInfo": [
+ {
+ "Key": "Id",
+ "Value": "VisualStudio/17.8.3+34330.188"
+ },
+ {
+ "Key": "BuildBranch",
+ "Value": "d17.8"
+ },
+ {
+ "Key": "BuildVersion",
+ "Value": "17.8.34330.188"
+ }
+ ],
+ "IsPrerelease": false,
+ "PSPath": "Microsoft.PowerShell.Core\\FileSystem::C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "UpdateDate": "2023-12-22T14:22:35.1818213Z",
+ "ResolvedInstallationPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "ChannelId": "VisualStudio.17.Release",
+ "InstalledChannelId": "VisualStudio.17.Release",
+ "ChannelUri": "https://aka.ms/vs/17/release/channel",
+ "InstalledChannelUri": "https://aka.ms/vs/17/release/channel",
+ "ReleaseNotes": "https://docs.microsoft.com/en-us/visualstudio/releases/2022/release-notes-v17.8#17.8.3",
+ "ThirdPartyNotices": "https://go.microsoft.com/fwlink/?LinkId=661288"
+}
diff --git a/test/fixtures/VSSetup_VS_2022_workload_missing_sdk.txt b/test/fixtures/VSSetup_VS_2022_workload_missing_sdk.txt
new file mode 100644
index 0000000000..8edee24607
--- /dev/null
+++ b/test/fixtures/VSSetup_VS_2022_workload_missing_sdk.txt
@@ -0,0 +1,152 @@
+{
+ "InstanceId": "621862c0",
+ "InstallationName": "VisualStudio/17.8.3+34330.188",
+ "InstallationPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "InstallationVersion": {
+ "Major": 17,
+ "Minor": 8,
+ "Build": 34330,
+ "Revision": 188,
+ "MajorRevision": 0,
+ "MinorRevision": 188
+ },
+ "InstallDate": "\/Date(1703254955000)\/",
+ "State": 4294967295,
+ "DisplayName": "Visual Studio Enterprise 2022",
+ "Description": "Scalable, end-to-end solution for teams of any size",
+ "ProductPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\IDE\\devenv.exe",
+ "Product": {
+ "Id": "Microsoft.VisualStudio.Product.Enterprise",
+ "Version": {
+ "Major": 17,
+ "Minor": 8,
+ "Build": 34330,
+ "Revision": 188,
+ "MajorRevision": 0,
+ "MinorRevision": 188
+ },
+ "Chip": "x64",
+ "Branch": null,
+ "Type": "Product",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Product.Enterprise,version=17.8.34330.188,chip=x64"
+ },
+ "Packages": [
+ {
+ "Id": "Microsoft.VisualStudio.Product.Enterprise",
+ "Version": "17.8.34330.188",
+ "Chip": "x64",
+ "Branch": null,
+ "Type": "Product",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Product.Enterprise,version=17.8.34330.188,chip=x64"
+ },
+ {
+ "Id": "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ "Version": "17.8.34129.139",
+ "Chip": null,
+ "Branch": null,
+ "Type": "Component",
+ "IsExtension": false,
+ "UniqueId": "Microsoft.VisualStudio.Component.VC.Tools.x86.x64,version=17.8.34129.139"
+ },
+ ],
+ "Properties": [
+ {
+ "Key": "CampaignId",
+ "Value": ""
+ },
+ {
+ "Key": "SetupEngineFilePath",
+ "Value": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\setup.exe"
+ },
+ {
+ "Key": "Nickname",
+ "Value": ""
+ },
+ {
+ "Key": "ChannelManifestId",
+ "Value": "VisualStudio.17.Release/17.8.3+34330.188"
+ }
+ ],
+ "Errors": null,
+ "EnginePath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\resources\\app\\ServiceHub\\Services\\Microsoft.VisualStudio.Setup.Service",
+ "IsComplete": true,
+ "IsLaunchable": true,
+ "CatalogInfo": [
+ {
+ "Key": "Id",
+ "Value": "VisualStudio/17.8.3+34330.188"
+ },
+ {
+ "Key": "BuildBranch",
+ "Value": "d17.8"
+ },
+ {
+ "Key": "BuildVersion",
+ "Value": "17.8.34330.188"
+ },
+ {
+ "Key": "LocalBuild",
+ "Value": "build-lab"
+ },
+ {
+ "Key": "ManifestName",
+ "Value": "VisualStudio"
+ },
+ {
+ "Key": "ManifestType",
+ "Value": "installer"
+ },
+ {
+ "Key": "ProductDisplayVersion",
+ "Value": "17.8.3"
+ },
+ {
+ "Key": "ProductLine",
+ "Value": "Dev17"
+ },
+ {
+ "Key": "ProductLineVersion",
+ "Value": "2022"
+ },
+ {
+ "Key": "ProductMilestone",
+ "Value": "RTW"
+ },
+ {
+ "Key": "ProductMilestoneIsPreRelease",
+ "Value": "False"
+ },
+ {
+ "Key": "ProductName",
+ "Value": "Visual Studio"
+ },
+ {
+ "Key": "ProductPatchVersion",
+ "Value": "3"
+ },
+ {
+ "Key": "ProductPreReleaseMilestoneSuffix",
+ "Value": "1.0"
+ },
+ {
+ "Key": "ProductSemanticVersion",
+ "Value": "17.8.3+34330.188"
+ },
+ {
+ "Key": "RequiredEngineVersion",
+ "Value": "3.8.2112.61926"
+ }
+ ],
+ "IsPrerelease": false,
+ "PSPath": "Microsoft.PowerShell.Core\\FileSystem::C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "UpdateDate": "2023-12-22T14:22:35.1818213Z",
+ "ResolvedInstallationPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
+ "ChannelId": "VisualStudio.17.Release",
+ "InstalledChannelId": "VisualStudio.17.Release",
+ "ChannelUri": "https://aka.ms/vs/17/release/channel",
+ "InstalledChannelUri": "https://aka.ms/vs/17/release/channel",
+ "ReleaseNotes": "https://docs.microsoft.com/en-us/visualstudio/releases/2022/release-notes-v17.8#17.8.3",
+ "ThirdPartyNotices": "https://go.microsoft.com/fwlink/?LinkId=661288"
+}
diff --git a/test/fixtures/VS_2017_BuildTools_minimal.txt b/test/fixtures/VS_2017_BuildTools_minimal.txt
new file mode 100644
index 0000000000..244f6b0798
--- /dev/null
+++ b/test/fixtures/VS_2017_BuildTools_minimal.txt
@@ -0,0 +1 @@
+[{"path":"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools","version":"15.9.28307.665","packages":["Microsoft.VisualStudio.Product.BuildTools","Microsoft.VisualStudio.Component.VC.CoreIde","Microsoft.VisualStudio.VC.Ide.Pro","Microsoft.VisualStudio.VC.Ide.Pro.Resources","Microsoft.VisualStudio.VC.Templates.Pro","Microsoft.VisualStudio.VC.Templates.Pro.Resources","Microsoft.VisualStudio.VC.Items.Pro","Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Reduced","Microsoft.VisualStudio.VC.Ide.MDD","Microsoft.VisualStudio.VC.Ide.x64","Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Express","Microsoft.VisualStudio.PackageGroup.Debugger.Script","Microsoft.VisualStudio.JavaScript.LanguageService","Microsoft.VisualStudio.JavaScript.LanguageService.Resources","Microsoft.VisualStudio.Debugger.Script.Msi","Microsoft.VisualStudio.Debugger.Script","Microsoft.VisualStudio.Debugger.Script","Microsoft.VisualStudio.Debugger.Script.Resources","Microsoft.VisualStudio.Debugger.Script.Resources","Microsoft.VisualStudio.VC.Ide.WinXPlus","Microsoft.VisualStudio.VC.Ide.Dskx","Microsoft.VisualStudio.VC.Ide.Dskx.Resources","Microsoft.VisualStudio.VC.Ide.Core","Microsoft.VisualStudio.VC.Ide.Core.Resources","Microsoft.VisualStudio.VC.Ide.Base","Microsoft.VisualStudio.VC.Ide.LanguageService","Microsoft.VisualStudio.VC.Ide.ResourceEditor","Microsoft.VisualStudio.VC.Ide.ResourceEditor.Resources","Microsoft.VisualStudio.VC.Ide.ProjectSystem","Microsoft.VisualStudio.VC.Ide.ProjectSystem.Resources","Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine","Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine.Resources","Microsoft.VisualStudio.VC.Ide.LanguageService.Resources","Microsoft.VisualStudio.VC.Ide.Base.Resources","Microsoft.VisualStudio.PackageGroup.Core","Microsoft.VisualStudio.TestTools.TeamFoundationClient","Microsoft.VisualStudio.PackageGroup.Debugger.Core","Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost","Microsoft.VisualStudio.VC.Ide.Debugger","Microsoft.VisualStudio.VC.Ide.Debugger.Resources","Microsoft.VisualStudio.VC.Ide.Common","Microsoft.VisualStudio.VC.Ide.Common.Resources","Microsoft.VisualStudio.Debugger.Parallel","Microsoft.VisualStudio.Debugger.Parallel.Resources","Microsoft.VisualStudio.Debugger.CollectionAgents","Microsoft.VisualStudio.Debugger.Managed","Microsoft.CodeAnalysis.VisualStudio.Setup.Resources","Microsoft.CodeAnalysis.VisualStudio.Setup","Microsoft.CodeAnalysis.ExpressionEvaluator.Resources","Microsoft.CodeAnalysis.ExpressionEvaluator","Microsoft.VisualStudio.Debugger.Managed.Resources","Microsoft.VisualStudio.Debugger.Remote","Microsoft.VisualStudio.Debugger.Remote","Microsoft.VisualStudio.Debugger.Remote.Resources","Microsoft.VisualStudio.Debugger.Remote.Resources","Microsoft.VisualStudio.Debugger","Microsoft.VisualStudio.VC.MSVCDis","Microsoft.VisualStudio.ScriptedHost","Microsoft.VisualStudio.ScriptedHost.Targeted","Microsoft.VisualStudio.ScriptedHost.Resources","Microsoft.IntelliTrace.DiagnosticsHub","Microsoft.VisualStudio.Debugger.Resources","Microsoft.PackageGroup.ClientDiagnostics","Microsoft.VisualStudio.AppResponsiveness","Microsoft.VisualStudio.AppResponsiveness.Targeted","Microsoft.VisualStudio.AppResponsiveness.Resources","Microsoft.VisualStudio.ClientDiagnostics","Microsoft.VisualStudio.ClientDiagnostics.Targeted","Microsoft.VisualStudio.ClientDiagnostics.Resources","Microsoft.VisualStudio.PackageGroup.CommunityCore","Microsoft.VisualStudio.ProjectSystem.Full","Microsoft.VisualStudio.ProjectSystem","Microsoft.VisualStudio.Community.x86","Microsoft.VisualStudio.Community.x64","Microsoft.VisualStudio.Community","Microsoft.IntelliTrace.CollectorCab","Microsoft.VisualStudio.Community.Resources","Microsoft.VisualStudio.WebSiteProject.DTE","Microsoft.MSHtml","Microsoft.VisualStudio.Community.Msi.Resources","Microsoft.VisualStudio.Community.Msi","Microsoft.VisualStudio.MinShell.Interop.Msi","Microsoft.VisualStudio.PackageGroup.CoreEditor","PortableFacades","Microsoft.VisualStudio.VirtualTree","Microsoft.VisualStudio.PackageGroup.Progression","Microsoft.VisualStudio.PerformanceProvider","Microsoft.VisualStudio.GraphModel","Microsoft.VisualStudio.GraphProvider","Microsoft.DiaSymReader","Microsoft.VisualStudio.TextMateGrammars","Microsoft.VisualStudio.PackageGroup.TeamExplorer","Microsoft.TeamFoundation.OfficeIntegration","Microsoft.TeamFoundation.OfficeIntegration.Resources","Microsoft.VisualStudio.TeamExplorer","Microsoft.ServiceHub","Microsoft.VisualStudio.ProjectServices","Microsoft.VisualStudio.SLNX.VSIX","Microsoft.VisualStudio.FileHandler.Msi","Microsoft.VisualStudio.FileHandler.Msi","Microsoft.VisualStudio.PackageGroup.MinShell","Microsoft.VisualStudio.MinShell.Msi","Microsoft.VisualStudio.MinShell.Msi.Resources","Microsoft.VisualStudio.MinShell.Interop","Microsoft.VisualStudio.Log","Microsoft.VisualStudio.Log.Targeted","Microsoft.VisualStudio.Log.Resources","Microsoft.VisualStudio.Finalizer","Microsoft.VisualStudio.CoreEditor","Microsoft.VisualStudio.Connected","Microsoft.VisualStudio.Connected.Resources","Microsoft.VisualStudio.MinShell","Microsoft.VisualStudio.MinShell.Platform","Microsoft.VisualStudio.MinShell.Platform.Resources","Microsoft.VisualStudio.MefHosting","Microsoft.VisualStudio.MefHosting.Resources","Microsoft.VisualStudio.Initializer","Microsoft.VisualStudio.ExtensionManager","Microsoft.VisualStudio.Editors","Microsoft.Net.4.TargetingPack","Microsoft.VisualStudio.Component.Windows10SDK.17134","Win10SDK_10.0.17134","Microsoft.VisualStudio.Component.VC.Tools.x86.x64","Microsoft.VisualCpp.CodeAnalysis.Extensions","Microsoft.VisualCpp.CodeAnalysis.Extensions.X86","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X86","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X86.Resources","Microsoft.VisualCpp.CodeAnalysis.Extensions.X64","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X64","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X64.Resources","Microsoft.VisualStudio.Component.Static.Analysis.Tools","Microsoft.VisualStudio.StaticAnalysis","Microsoft.VisualStudio.StaticAnalysis.Resources","Microsoft.VisualCpp.Tools.HostX64.TargetX86","Microsoft.VisualCpp.VCTip.HostX64.TargetX86","Microsoft.VisualCpp.Tools.HostX64.TargetX86.Resources","Microsoft.VisualCpp.Tools.HostX64.TargetX64","Microsoft.VisualCpp.VCTip.HostX64.TargetX64","Microsoft.VisualCpp.Tools.HostX64.TargetX64.Resources","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX64","Microsoft.VisualCpp.Premium.Tools.Hostx86.Targetx64.Resources","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX86","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX86.Resources","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX86","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX86.Resources","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX64","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX64.Resources","Microsoft.VisualCpp.PGO.X86","Microsoft.VisualCpp.PGO.X64","Microsoft.VisualCpp.PGO.Headers","Microsoft.VisualCpp.CRT.x86.Store","Microsoft.VisualCpp.CRT.x86.OneCore.Desktop","Microsoft.VisualCpp.CRT.x64.Store","Microsoft.VisualCpp.CRT.x64.OneCore.Desktop","Microsoft.VisualCpp.CRT.Redist.x86.OneCore.Desktop","Microsoft.VisualCpp.CRT.Redist.x64.OneCore.Desktop","Microsoft.VisualCpp.CRT.ClickOnce.Msi","Microsoft.VisualStudio.PackageGroup.VC.Tools.x86","Microsoft.VisualCpp.Tools.HostX86.TargetX64","Microsoft.VisualCpp.VCTip.hostX86.targetX64","Microsoft.VisualCpp.Tools.Hostx86.Targetx64.Resources","Microsoft.VisualCpp.Tools.HostX86.TargetX86","Microsoft.VisualCpp.VCTip.hostX86.targetX86","Microsoft.VisualCpp.Tools.HostX86.TargetX86.Resources","Microsoft.VisualCpp.Tools.Core.Resources","Microsoft.VisualCpp.Tools.Core.x86","Microsoft.VisualCpp.Tools.Common.Utils","Microsoft.VisualCpp.Tools.Common.Utils.Resources","Microsoft.VisualCpp.DIA.SDK","Microsoft.VisualCpp.CRT.x86.Desktop","Microsoft.VisualCpp.CRT.x64.Desktop","Microsoft.VisualCpp.CRT.Source","Microsoft.VisualCpp.CRT.Redist.X86","Microsoft.VisualCpp.CRT.Redist.X64","Microsoft.VisualCpp.CRT.Redist.Resources","Microsoft.VisualCpp.RuntimeDebug.14","Microsoft.VisualCpp.RuntimeDebug.14","Microsoft.VisualCpp.Redist.14","Microsoft.VisualCpp.Redist.14","Microsoft.VisualCpp.CRT.Headers","Microsoft.VisualStudio.VC.MSBuild.X86","Microsoft.VisualStudio.VC.MSBuild.X64","Microsoft.VS.VC.MSBuild.X64.Resources","Microsoft.VisualStudio.VC.MSBuild.Base","Microsoft.VisualStudio.VC.MSBuild.Base.Resources","Microsoft.VisualStudio.VC.MSBuild.ARM","Microsoft.VisualStudio.Workload.MSBuildTools","Microsoft.VisualStudio.Component.CoreBuildTools","Microsoft.VisualStudio.Setup.Configuration","Microsoft.VisualStudio.PackageGroup.VsDevCmd","Microsoft.VisualStudio.VsDevCmd.Ext.NetFxSdk","Microsoft.VisualStudio.VsDevCmd.Core.WinSdk","Microsoft.VisualStudio.VsDevCmd.Core.DotNet","Microsoft.VisualStudio.VC.DevCmd","Microsoft.VisualStudio.VC.DevCmd.Resources","Microsoft.VisualStudio.BuildTools.Resources","Microsoft.VisualStudio.Net.Eula.Resources","Microsoft.Build.Dependencies","Microsoft.Build.FileTracker.Msi","Microsoft.Component.MSBuild","Microsoft.PythonTools.BuildCore.Vsix","Microsoft.NuGet.Build.Tasks","Microsoft.VisualStudio.Component.Roslyn.Compiler","Microsoft.CodeAnalysis.Compilers.Resources","Microsoft.CodeAnalysis.Compilers","Microsoft.Net.PackageGroup.4.6.1.Redist","Microsoft.VisualStudio.NativeImageSupport","Microsoft.Build"]}]
diff --git a/test/fixtures/VS_2017_Community_workload.txt b/test/fixtures/VS_2017_Community_workload.txt
new file mode 100644
index 0000000000..dd5e77dafb
--- /dev/null
+++ b/test/fixtures/VS_2017_Community_workload.txt
@@ -0,0 +1 @@
+[{"path":"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community","version":"15.9.28307.665","packages":["Microsoft.VisualStudio.Component.Windows10SDK.IpOverUsb","Win10SDK_IpOverUsb","Microsoft.VisualStudio.Component.VC.ATL.ARM64","Microsoft.VisualCpp.ATL.ARM64","Microsoft.VisualStudio.Component.VC.ATL.ARM","Microsoft.VisualCpp.ATL.ARM","Microsoft.VisualStudio.Component.VC.Tools.ARM","Microsoft.VisualCpp.Tools.HostX64.TargetX86.Resources","Microsoft.VisualStudio.Graphics.Analyzer.Resources","Microsoft.Icecap.Analysis","Microsoft.VisualCpp.CRT.Redist.arm.OneCore.Desktop","Microsoft.VisualCpp.CRT.arm.Store","Microsoft.VisualCpp.CRT.arm.Desktop","Microsoft.VisualStudio.PackageGroup.VC.Tools.x64.ARM","Microsoft.VisualCpp.Premium.Tools.Hostx86.Targetarm","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetARM.Resources","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetARM","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetARM.Resources","Microsoft.VisualCpp.Premium.Tools.ARM.Base","Microsoft.VisualCpp.Premium.Tools.ARM.Base.Resources","Microsoft.VisualCpp.PGO.ARM","Microsoft.VisualCpp.Tools.HostX64.TargetX64","Microsoft.VisualStudio.Product.Community","Microsoft.VisualCpp.Tools.Hostx86.Targetarm","Microsoft.VisualStudio.Component.VC.Tools.ARM64","Microsoft.VisualStudio.VC.MSBuild.Arm64","Microsoft.VisualCpp.CRT.Redist.ARM64.OneCore.Desktop","Microsoft.VisualCpp.VCTip.HostX64.TargetX64","Microsoft.VisualCpp.CRT.ARM64.OneCore.Desktop","Microsoft.VisualCpp.CRT.ARM64.Store","Microsoft.VisualCpp.CRT.ARM64.Desktop","Microsoft.VisualCpp.Tools.HostX64.TargetX64.Resources","Microsoft.Icecap.Analysis.Resources","Microsoft.VisualCpp.VCTip.hostX86.targetARM","Microsoft.VisualStudio.PackageGroup.VC.Tools.x64.ARM64","Microsoft.VisualCpp.Tools.Core","Microsoft.VisualCpp.PGO.ARM64","Microsoft.VisualCpp.Premium.Tools.Hostx86.Targetarm64","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetARM64.Resources","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetARM64","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetARM64.Resources","Microsoft.VisualCpp.Premium.Tools.ARM64.Base","Microsoft.VisualCpp.Tools.HostX86.TargetX64","Microsoft.VisualCpp.Tools.HostX86.TargetARM.Resources","Microsoft.VisualCpp.CRT.Redist.ARM64","Microsoft.VisualCpp.CRT.arm.OneCore.Desktop","Microsoft.VisualCpp.CodeAnalysis.Extensions.X86","Microsoft.VisualCpp.CodeAnalysis.Extensions.X64","Microsoft.VisualCpp.VCTip.HostX64.TargetX86","Component.WixToolset.VisualStudioExtension.Dev15","WixToolset.VisualStudioExtension.Dev15","Microsoft.VisualCpp.MFC.X64","Microsoft.VisualCpp.ATL.Headers","Microsoft.VisualStudio.Component.VC.CMake.Project","Microsoft.VisualStudio.VC.CMake","Microsoft.VisualStudio.VC.CMake.Project","Microsoft.VisualStudio.Component.Windows10SDK.17763","Microsoft.VisualStudio.VC.MSBuild.Base.Resources","MLGen","Microsoft.VisualStudio.Graphics.Analyzer","Microsoft.VisualStudio.Component.TestTools.Core","Microsoft.VisualCpp.Tools.Core.x86","Microsoft.VisualCpp.CRT.x86.OneCore.Desktop","Microsoft.VisualCpp.DIA.SDK","Microsoft.VisualCpp.CRT.x64.OneCore.Desktop","Microsoft.VisualCpp.CRT.ClickOnce.Msi","Microsoft.VisualStudio.NuGet.Licenses","SQLCommon","Microsoft.VisualStudio.VC.MSBuild.X86","Microsoft.VisualCpp.Tools.HostX64.TargetARM","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X64","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X64.Resources","Microsoft.VisualCpp.HTMLHelpWorkshop.Msi","Microsoft.Icecap.Collection.Msi.Resources","Microsoft.VisualCpp.Tools.Hostx86.Targetx64.Resources","Microsoft.VisualCpp.VCTip.hostX64.targetARM","Microsoft.VisualStudio.VC.Ide.Dskx.Resources","Microsoft.VisualStudio.VC.Templates.UnitTest","Microsoft.VisualStudio.TestTools.TestPlatform.V1.CPP","Microsoft.VisualStudio.VC.Ide.Core","Microsoft.VisualStudio.Graphics.Appid","Microsoft.VisualCpp.ATL.Source","Microsoft.VisualStudio.VC.Ide.Core.Resources","Microsoft.VisualStudio.Debugger.ImmersiveActivateHelper.Msi","Microsoft.VisualStudio.Debugger.JustInTime","Microsoft.DiagnosticsHub.CpuSampling","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Common","Microsoft.VisualStudio.TestTools.TP.Legacy.Common.Res","Microsoft.VisualStudio.ProTools.Resources","Microsoft.VisualStudio.Community.Msi","Microsoft.VisualCpp.Tools.HostX64.TargetARM.Resources","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Agent","Microsoft.Component.MSBuild","Microsoft.VisualStudio.Graphics.Msi","Microsoft.VisualStudio.WebToolsExtensions","Microsoft.VisualCpp.Tools.Hostx86.Targetarm64","Microsoft.VisualStudio.TextTemplating.MSBuild","Microsoft.VisualCpp.VCTip.hostX86.targetARM64","Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine","Microsoft.VisualCpp.Tools.HostX86.TargetARM64.Resources","Microsoft.VisualStudio.RazorExtension","Microsoft.VisualCpp.CRT.x86.Store","Microsoft.VisualCpp.Tools.Core.Resources","Microsoft.VisualStudio.Debugger.Script","Microsoft.VisualStudio.Debugger.Script","Microsoft.VisualCpp.MFC.Source","Microsoft.VisualCpp.CRT.x86.Desktop","Microsoft.VisualStudio.VC.MSBuild.X64","Microsoft.VisualStudio.VC.Items.Pro","Microsoft.VisualStudio.Graphics.Viewers","Microsoft.VisualCpp.CRT.x64.Desktop","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X86.Resources","Microsoft.VisualCpp.MFC.Redist.X86","Microsoft.VisualStudio.WebToolsExtensions.Chip","Microsoft.DiagnosticsHub.Runtime.Resources","Microsoft.DiagnosticsHub.CpuSampling.Targeted","Microsoft.VisualStudio.VC.Ide.LanguageService.Resources","Microsoft.VisualStudio.Component.VC.DiagnosticTools","Microsoft.VisualCpp.MFC.Redist.X64","Microsoft.VisualStudio.PackageGroup.TestTools.Native","Microsoft.VisualStudio.Graphics.Viewers.Resources","Microsoft.VisualCpp.MFC.MBCS","Microsoft.VisualStudio.Debugger.Remote.Resources","Microsoft.VisualStudio.Component.TextTemplating","Win10SDK_10.0.17763","Microsoft.VisualStudio.VC.Ide.Base.Resources","Microsoft.VisualCpp.MFC.MBCS.X64","Microsoft.VisualStudio.PackageGroup.TestTools.CodeCoverage","Microsoft.VisualStudio.Graphics.EnableTools","Microsoft.VisualStudio.Graphics.Appid.Resources","Microsoft.VisualStudio.VC.MSBuild.Base","Microsoft.VisualStudio.VC.MSBuild.ARM","Microsoft.VisualCpp.MFC.Headers","Microsoft.VisualCpp.CRT.Redist.x86.OneCore.Desktop","Microsoft.VisualCpp.Tools.HostX86.TargetX86","Microsoft.VisualStudio.VC.Ide.Base","Microsoft.VisualStudio.Graphics.Analyzer.Targeted","Microsoft.VisualCpp.CRT.Headers","Microsoft.DiagnosticsHub.Runtime.Targeted","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX86","Microsoft.VisualCpp.Tools.HostX64.TargetARM64","Microsoft.VisualCpp.VCTip.hostX64.targetARM64","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX86.Resources","Microsoft.Icecap.Collection.Msi","Microsoft.VisualCpp.ATL.X86","Microsoft.VisualCpp.Tools.HostX64.TargetARM64.Resources","Microsoft.VisualStudio.Component.VC.ATLMFC","Microsoft.VisualCpp.VCTip.hostX86.targetX86","Microsoft.Icecap.Collection.Msi.Resources.Targeted","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX86","Microsoft.VisualStudio.Component.Graphics.Tools","Microsoft.VisualStudio.WebTools.Resources","Microsoft.VisualCpp.ATL.X64","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX86.Resources","Microsoft.VisualStudio.Component.Graphics.Win81","Microsoft.VisualStudio.VC.Ide.MDD","Microsoft.VisualStudio.VC.Ide.ResourceEditor","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX64","Microsoft.Icecap.Analysis.Resources.Targeted","Microsoft.VisualStudio.Debugger.Script.Msi","Microsoft.VisualStudio.Component.VC.CoreIde","Microsoft.VisualStudio.VC.Ide.MFC.Resources","Microsoft.VisualStudio.Debugger.Script.Resources","Microsoft.VisualStudio.PackageGroup.VC.Tools.x86","Microsoft.VisualStudio.TextTemplating.Core","Microsoft.VisualStudio.JavaScript.LanguageService","Microsoft.VisualStudio.VC.Ide.ResourceEditor.Resources","Microsoft.VisualStudio.VC.Ide.ProjectSystem.Resources","Microsoft.VisualStudio.Component.VC.TestAdapterForBoostTest","Microsoft.VisualStudio.VC.Ide.ProjectSystem","Microsoft.VisualStudio.VC.Ide.Dskx","Microsoft.VisualCpp.Tools.HostX86.TargetX86.Resources","Microsoft.CredentialProvider","Microsoft.VisualStudio.VC.Templates.Desktop","Microsoft.VisualStudio.VC.Ide.Pro.Resources","Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Core","Microsoft.VisualStudio.TextTemplating.Integration","Microsoft.VisualStudio.Component.NuGet","Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Reduced","Microsoft.VisualCpp.PGO.Headers","Microsoft.DiagnosticsHub.Collection","Microsoft.Icecap.Collection.Msi.Targeted","Microsoft.VisualStudio.VC.Ide.LanguageService","Microsoft.VisualStudio.WebTools.WSP.FSA","Microsoft.VisualStudio.Graphics.Msi","Microsoft.VisualCpp.CRT.Redist.X86","Microsoft.VisualStudio.Branding.Community","Microsoft.VisualStudio.VC.Ide.x64","Microsoft.VisualStudio.WebToolsExtensions.Common","Microsoft.VisualStudio.WebTools.MSBuild","Microsoft.VisualStudio.NuGet.Core","Microsoft.DiagnosticsHub.Collection.Service","Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine.Resources","Microsoft.CodeAnalysis.ExpressionEvaluator","Microsoft.VisualCpp.CRT.Redist.X64","Microsoft.VisualCpp.RuntimeDebug.14","Microsoft.VS.VC.MSBuild.X64.Resources","Microsoft.VisualCpp.CRT.Source","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Tips.Resources","Microsoft.VisualStudio.VC.Ide.WinXPlus","Microsoft.VisualCpp.RuntimeDebug.14","Microsoft.VisualCpp.Redist.14.Latest","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX64","Microsoft.VisualCpp.CRT.Redist.Resources","Microsoft.VisualCpp.Redist.14.Latest","Microsoft.Net.4.TargetingPack","Microsoft.VisualStudio.Debugger.Script.Resources","Microsoft.VisualCpp.CRT.x64.Store","Microsoft.VisualStudio.VC.Ide.Debugger.Resources","Microsoft.DiaSymReader.Native","Microsoft.VisualCpp.Redist.14","Microsoft.VisualCpp.Redist.14","Microsoft.VisualStudio.StaticAnalysis","Microsoft.VisualStudio.TestTools.TeamFoundationClient","Microsoft.VisualStudio.TestTools.TestPlatform.V1.CLI","Microsoft.VisualStudio.VC.Ide.Common","Microsoft.VisualStudio.Community.Extra.Resources","Microsoft.VisualStudio.Component.Roslyn.LanguageServices","Microsoft.DiagnosticsHub.Collection.StopService.Install","Microsoft.VisualStudio.InteractiveWindow","Microsoft.PackageGroup.DiagnosticsHub.Platform","Microsoft.VisualStudio.StaticAnalysis.Resources","Microsoft.VisualStudio.Debugger.Remote","Microsoft.VisualStudio.VC.Ide.Common.Resources","Microsoft.VisualCpp.Tools.HostX64.TargetX86","Microsoft.VisualStudio.VC.DevCmd","Microsoft.VisualStudio.Community.Extra","Microsoft.VisualStudio.TestTools.TP.Legacy.Tips.Msi","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.TestTools","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Core.Resources","Microsoft.VisualStudio.PackageGroup.TestTools.Core","Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.V2.CLI","Microsoft.VisualStudio.Debugger.Remote","Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.V1.CLI","Microsoft.VisualStudio.Component.VC.Tools.x86.x64","Microsoft.VisualStudio.TestTools.Pex.Common","Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.Legacy","Microsoft.VisualStudio.PackageGroup.MinShell.Interop","Microsoft.CodeAnalysis.ExpressionEvaluator.Resources","Microsoft.VisualCpp.CodeAnalysis.Extensions","Microsoft.VisualStudio.PackageGroup.CoreEditor","Microsoft.VisualStudio.Component.Roslyn.Compiler","Microsoft.VisualStudio.ScriptedHost.Targeted","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Professional","Microsoft.VisualStudio.Debugger.Resources","Microsoft.VisualStudio.Debugger.Parallel","Microsoft.VisualStudio.Debugger.Parallel.Resources","Microsoft.VisualCpp.PGO.X64","Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.IDE","Microsoft.VisualStudio.GraphModel","Microsoft.VisualStudio.PackageGroup.TestTools.DataCollectors","sqlsysclrtypes","Microsoft.VisualStudio.ProTools","Component.Microsoft.VisualStudio.RazorExtension","Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI","Microsoft.Build.Dependencies","Microsoft.VisualStudio.WebTools.WSP.FSA.Resources","Microsoft.VisualStudio.Component.Static.Analysis.Tools","Microsoft.VisualStudio.VC.Ide.ATL.Resources","Microsoft.VisualStudio.VC.Templates.UnitTest.Resources","Microsoft.VisualStudio.Debugger.Managed","Microsoft.VisualStudio.Workload.NativeDesktop","Microsoft.VisualStudio.Component.VC.TestAdapterForGoogleTest","Microsoft.VisualStudio.Debugger.JustInTime.Msi","Microsoft.Net.PackageGroup.4.6.1.Redist","Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost","sqlsysclrtypes","Microsoft.VisualStudio.Debugger.Managed.Resources","Microsoft.VisualStudio.TestTools.TP.Legacy.Tips.Common","Microsoft.VisualStudio.VC.Ide.Debugger","Microsoft.VisualStudio.AppResponsiveness","Microsoft.VisualStudio.Debugger.Remote.Resources","Microsoft.VisualStudio.TestTools.TestWIExtension","Microsoft.VisualStudio.VC.Ide.Pro","Microsoft.VisualStudio.PackageGroup.Debugger.Core","Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Express","Microsoft.VisualStudio.WebTools","Microsoft.VisualStudio.Component.VC.Redist.14.Latest","Microsoft.VisualStudio.VsDevCmd.Core.WinSdk","Microsoft.VisualStudio.TestTools.TestPlatform.IDE","Microsoft.VisualStudio.TextTemplating.Integration.Resources","Microsoft.VisualStudio.Debugger.CollectionAgents","Microsoft.VisualStudio.Debugger","Microsoft.VisualStudio.PackageGroup.Debugger.Script","Microsoft.VisualStudio.VC.MSVCDis","Microsoft.VisualStudio.ScriptedHost","Microsoft.VisualStudio.ClientDiagnostics.Targeted","Microsoft.VisualStudio.ScriptedHost.Resources","Microsoft.TeamFoundation.OfficeIntegration.Resources","Microsoft.IntelliTrace.DiagnosticsHub","Microsoft.VisualStudio.JavaScript.LanguageService.Resources","Microsoft.VisualStudio.VC.Ide.TestAdapterForGoogleTest","Microsoft.VisualStudio.PackageGroup.Community","Microsoft.VisualStudio.ClientDiagnostics","Microsoft.VisualStudio.Component.Windows10SDK.17134","Microsoft.VisualStudio.PackageGroup.Core","PortableFacades","Microsoft.DiaSymReader","Microsoft.DiagnosticsHub.Runtime","Microsoft.VisualStudio.Component.CoreEditor","Microsoft.VisualStudio.AppResponsiveness.Targeted","Microsoft.VisualStudio.AppResponsiveness.Resources","Microsoft.VisualStudio.Community","Microsoft.TeamFoundation.OfficeIntegration","Microsoft.VisualStudio.WebSiteProject.DTE","Microsoft.VisualStudio.ClientDiagnostics.Resources","Microsoft.VisualStudio.ProjectSystem.Full","Microsoft.VisualStudio.ProjectSystem","Microsoft.VisualCpp.Tools.Common.UtilsPrereq","Microsoft.IntelliTrace.CollectorCab","Microsoft.VisualStudio.Community.Resources","Microsoft.VisualCpp.Tools.Common.Utils","Microsoft.ServiceHub","Microsoft.VisualStudio.Editors","Microsoft.VisualStudio.TeamExplorer","Microsoft.CodeAnalysis.VisualStudio.InteractiveComponents.Resources","Microsoft.VisualStudio.MinShell.Interop.Msi","Microsoft.VisualStudio.GraphProvider","Microsoft.CodeAnalysis.VisualStudio.InteractiveComponents","Microsoft.CodeAnalysis.VisualStudio.Setup.Interactive.Resources","Microsoft.CodeAnalysis.VisualStudio.Setup.Resources","Microsoft.VisualStudio.Community.x86","Microsoft.VisualStudio.Community.x64","Microsoft.CodeAnalysis.VisualStudio.Setup","Microsoft.NuGet.Build.Tasks","Microsoft.PackageGroup.ClientDiagnostics","Microsoft.CodeAnalysis.Compilers.Resources","Microsoft.CodeAnalysis.Compilers","Microsoft.VisualCpp.Tools.Common.Utils.Resources","Microsoft.VisualStudio.Net.Eula.Resources","Microsoft.VisualStudio.PackageGroup.CommunityCore","Microsoft.Build","Microsoft.VisualStudio.VC.Ide.TestAdapterForBoostTest","Microsoft.VisualStudio.VC.Ide.ATL","Microsoft.VisualStudio.TextMateGrammars","Microsoft.VisualStudio.Workload.CoreEditor","Microsoft.VisualStudio.MinShell.Interop","Microsoft.Build.FileTracker.Msi","Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core","Microsoft.MSHtml","Microsoft.VisualStudio.Community.Msi.Resources","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Tips","Microsoft.VisualStudio.Devenv.Msi","Microsoft.VisualStudio.Component.VC.ATL","Microsoft.VisualStudio.VC.Templates.Pro","Microsoft.VisualCpp.CRT.Redist.x64.OneCore.Desktop","Microsoft.VisualStudio.SLNX.VSIX","Microsoft.VisualStudio.CoreEditor","Win10SDK_10.0.17134","Microsoft.VisualStudio.VsDevCmd.Ext.NetFxSdk","Microsoft.VisualStudio.Component.Debugger.JustInTime","Microsoft.VisualStudio.VC.Ide.MFC","Microsoft.VisualStudio.VsDevCmd.Core.DotNet","Microsoft.VisualStudio.PackageGroup.VsDevCmd","Microsoft.VisualStudio.Finalizer","Microsoft.VisualStudio.VirtualTree","Microsoft.VisualStudio.FileHandler.Msi","Microsoft.VisualStudio.ProjectServices","Microsoft.VisualStudio.VC.DevCmd.Resources","Microsoft.VisualStudio.MinShell","Microsoft.VisualStudio.PackageGroup.Progression","Microsoft.VisualStudio.PerformanceProvider","Microsoft.VisualStudio.Connected.Resources","Microsoft.VisualStudio.Log","Microsoft.VisualStudio.PackageGroup.TeamExplorer","Microsoft.VisualStudio.Log.Targeted","Microsoft.VisualStudio.MinShell.Platform","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X86","Microsoft.VisualStudio.FileHandler.Msi","Microsoft.VisualStudio.VC.Templates.Pro.Resources","Microsoft.VisualStudio.Devenv","Microsoft.VisualCpp.VCTip.hostX86.targetX64","Microsoft.VisualStudio.Devenv.Resources","Microsoft.VisualStudio.MinShell.Platform.Resources","Microsoft.VisualStudio.Connected","Microsoft.VisualStudio.MefHosting","Microsoft.DiagnosticsHub.Collection.StopService.Uninstall","Microsoft.VisualStudio.PackageGroup.MinShell","Microsoft.VisualStudio.MefHosting.Resources","Microsoft.VisualCpp.MFC.X86","Microsoft.VisualStudio.Log.Resources","Microsoft.Icecap.Analysis.Targeted","Microsoft.VisualCpp.Premium.Tools.Hostx86.Targetx64.Resources","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX64.Resources","Microsoft.VisualCpp.PGO.X86","Microsoft.VisualStudio.ExtensionManager","Microsoft.VisualStudio.MinShell.x86","Microsoft.VisualStudio.MinShell.Msi","Microsoft.VisualStudio.Setup.Configuration","Microsoft.VisualStudio.LanguageServer","Microsoft.VisualStudio.NativeImageSupport","Microsoft.VisualStudio.MinShell.Msi.Resources","Microsoft.VisualStudio.Devenv.Config","Microsoft.VisualStudio.MinShell.Resources","Microsoft.VisualStudio.Initializer","Microsoft.Net.PackageGroup.4.6.Redist"]}]
diff --git a/test/fixtures/VS_2017_Express.txt b/test/fixtures/VS_2017_Express.txt
new file mode 100644
index 0000000000..c4b3b5f2b0
--- /dev/null
+++ b/test/fixtures/VS_2017_Express.txt
@@ -0,0 +1 @@
+[{"path":"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\WDExpress","version":"15.9.28307.858","packages":["Microsoft.VisualStudio.Product.WDExpress","Microsoft.VisualStudio.Workload.WDExpress","Microsoft.VisualStudio.Component.Windows10SDK.17763","MLGen","Win10SDK_10.0.17763","Microsoft.VisualStudio.Component.Windows10SDK.14393","Win10SDK_10.0.14393.795","Microsoft.VisualStudio.VC.Items.Pro","Microsoft.VisualStudio.VC.Ide.Pro","Microsoft.VisualStudio.VC.Ide.Pro.Resources","Microsoft.VisualStudio.Component.VC.Tools.ARM64","Microsoft.VisualStudio.VC.MSBuild.Arm64","Microsoft.VisualCpp.CRT.Redist.ARM64.OneCore.Desktop","Microsoft.VisualCpp.CRT.Redist.ARM64","Microsoft.VisualCpp.CRT.ARM64.OneCore.Desktop","Microsoft.VisualCpp.CRT.ARM64.Store","Microsoft.VisualCpp.CRT.ARM64.Desktop","Microsoft.VisualCpp.Tools.Hostx86.Targetarm64","Microsoft.VisualCpp.VCTip.hostX86.targetARM64","Microsoft.VisualCpp.Tools.HostX86.TargetARM64.Resources","Microsoft.VisualStudio.Component.VC.Tools.ARM","Microsoft.VisualCpp.Tools.Hostx86.Targetarm","Microsoft.VisualCpp.VCTip.hostX86.targetARM","Microsoft.VisualCpp.Tools.HostX86.TargetARM.Resources","Microsoft.VisualCpp.CRT.x86.Store","Microsoft.VisualCpp.CRT.x86.OneCore.Desktop","Microsoft.VisualCpp.CRT.x64.Store","Microsoft.VisualCpp.CRT.x64.OneCore.Desktop","Microsoft.VisualCpp.CRT.Redist.arm.OneCore.Desktop","Microsoft.VisualCpp.CRT.arm.OneCore.Desktop","Microsoft.VisualCpp.CRT.arm.Store","Microsoft.VisualCpp.CRT.arm.Desktop","Microsoft.VisualStudio.VC.Templates.UnitTest","Microsoft.VisualStudio.TestTools.TestPlatform.V1.CPP","Microsoft.VisualStudio.VC.Templates.UnitTest.Resources","Microsoft.VisualStudio.VC.Templates.Desktop","Microsoft.VisualStudio.VC.Templates.Pro","Microsoft.VisualStudio.VC.Templates.Pro.Resources","Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Express","Microsoft.VisualStudio.PackageGroup.Debugger.Script","Microsoft.VisualStudio.JavaScript.LanguageService","Microsoft.VisualStudio.JavaScript.LanguageService.Resources","Microsoft.VisualStudio.Debugger.Script.Msi","Microsoft.VisualStudio.Debugger.Script","Microsoft.VisualStudio.Debugger.Script","Microsoft.VisualStudio.Debugger.Script.Resources","Microsoft.VisualStudio.Debugger.Script.Resources","Microsoft.VisualStudio.VC.MSBuild.X64","Microsoft.VS.VC.MSBuild.X64.Resources","Microsoft.VisualStudio.VC.MSBuild.ARM","Microsoft.VisualStudio.VC.MSBuild.X86","Microsoft.VisualStudio.VC.MSBuild.Base","Microsoft.VisualStudio.VC.MSBuild.Base.Resources","Microsoft.VisualStudio.VC.Ide.WinXPlus","Microsoft.VisualStudio.VC.Ide.Dskx","Microsoft.VisualStudio.VC.Ide.Dskx.Resources","Microsoft.VisualStudio.VC.Ide.Core","Microsoft.VisualStudio.VC.Ide.Core.Resources","Microsoft.VisualStudio.VC.Ide.Base","Microsoft.VisualStudio.VC.Ide.Base.Resources","Microsoft.VisualStudio.Component.VC.CLI.Support","Microsoft.VisualCpp.CLI.X86","Microsoft.VisualCpp.CLI.X64","Microsoft.VisualCpp.CLI.Source","Microsoft.VisualCpp.CLI.ARM64","Microsoft.VisualCpp.CLI.ARM","Microsoft.VisualStudio.VC.Templates.CLR","Microsoft.VisualStudio.VC.Ide.LanguageService","Microsoft.VisualStudio.VC.Ide.ResourceEditor","Microsoft.VisualStudio.VC.Ide.ResourceEditor.Resources","Microsoft.VisualStudio.VC.Ide.ProjectSystem","Microsoft.VisualStudio.VC.Ide.ProjectSystem.Resources","Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine","Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine.Resources","Microsoft.VisualStudio.VC.Ide.LanguageService.Resources","Microsoft.VisualStudio.VC.Templates.CLR.Resources","Microsoft.Component.VC.Runtime.OSSupport","Microsoft.Windows.UniversalCRT.Tools.Msi","Microsoft.Windows.UniversalCRT.Tools.Msi","Microsoft.Windows.UniversalCRT.ExtensionSDK.Msi","Microsoft.Windows.UniversalCRT.HeadersLibsSources.Msi","Microsoft.VisualStudio.PackageGroup.VC.Tools.x86","Microsoft.VisualCpp.Tools.HostX86.TargetX64","Microsoft.VisualCpp.VCTip.hostX86.targetX64","Microsoft.VisualCpp.Tools.Hostx86.Targetx64.Resources","Microsoft.VisualCpp.Tools.HostX86.TargetX86","Microsoft.VisualCpp.VCTip.hostX86.targetX86","Microsoft.VisualCpp.Tools.HostX86.TargetX86.Resources","Microsoft.VisualCpp.Tools.Core.Resources","Microsoft.VisualCpp.Tools.Core.x86","Microsoft.VisualCpp.Tools.Common.Utils","Microsoft.VisualCpp.Tools.Common.Utils.Resources","Microsoft.VisualCpp.DIA.SDK","Microsoft.VisualCpp.CRT.x86.Desktop","Microsoft.VisualCpp.CRT.x64.Desktop","Microsoft.VisualCpp.CRT.Source","Microsoft.VisualCpp.CRT.Redist.X86","Microsoft.VisualCpp.CRT.Redist.X64","Microsoft.VisualCpp.CRT.Redist.Resources","Microsoft.VisualCpp.RuntimeDebug.14","Microsoft.VisualCpp.RuntimeDebug.14","Microsoft.VisualCpp.CRT.Headers","Microsoft.Component.HelpViewer","Microsoft.HelpViewer","Microsoft.VisualStudio.Help.Configuration.Msi","Microsoft.VisualStudio.Component.SQL.DataSources","Microsoft.VisualStudio.Component.SQL.SSDT","Microsoft.VisualStudio.Component.SQL.CMDUtils","sqlcmdlnutils","Microsoft.VisualStudio.Component.Common.Azure.Tools","Microsoft.VisualStudio.Azure.CommonAzureTools","SSDT","Microsoft.VisualStudio.Component.SQL.ADAL","sql_adalsql","Microsoft.VisualStudio.Component.NuGet","Microsoft.CredentialProvider","Microsoft.VisualStudio.NuGet.Licenses","Microsoft.VisualStudio.Component.SQL.LocalDB.Runtime","Microsoft.VisualStudio.Component.SQL.NCLI","sqllocaldb","sqlncli","Microsoft.VisualStudio.Component.EntityFramework","Microsoft.VisualStudio.PackageGroup.DslRuntime","Microsoft.VisualStudio.Dsl.Core","Microsoft.VisualStudio.Dsl.GraphObject","Microsoft.VisualStudio.Dsl.Core.Resources","Microsoft.VisualStudio.EntityFrameworkTools","Microsoft.VisualStudio.EntityFrameworkTools.Msi","Microsoft.VisualStudio.Component.Roslyn.LanguageServices","Microsoft.VisualStudio.InteractiveWindow","Microsoft.DiaSymReader.Native","Microsoft.VisualStudio.Component.Static.Analysis.Tools","Microsoft.VisualCpp.Redist.14","Microsoft.VisualCpp.Redist.14","Microsoft.VisualStudio.StaticAnalysis","Microsoft.VisualStudio.StaticAnalysis.Resources","Microsoft.CodeAnalysis.VisualStudio.InteractiveComponents.Resources","Microsoft.CodeAnalysis.VisualStudio.InteractiveComponents","Microsoft.CodeAnalysis.VisualStudio.Setup.Interactive.Resources","Microsoft.Net.ComponentGroup.TargetingPacks.Common","Microsoft.Net.Component.4.6.TargetingPack","Microsoft.Net.4.6.TargetingPack","Microsoft.Net.Component.4.5.2.TargetingPack","Microsoft.Net.4.5.2.TargetingPack","Microsoft.Net.Component.4.5.1.TargetingPack","Microsoft.Net.4.5.1.TargetingPack","Microsoft.Net.Component.4.5.TargetingPack","Microsoft.Net.4.5.TargetingPack","Microsoft.Net.Component.4.TargetingPack","Microsoft.Net.4.TargetingPack","Microsoft.Net.ComponentGroup.DevelopmentPrerequisites","Microsoft.Net.Component.4.6.1.TargetingPack","Microsoft.Net.4.6.1.TargetingPack","Microsoft.Net.Cumulative.TargetingPack.Resources","Microsoft.Net.Component.4.6.1.SDK","Microsoft.Net.4.6.1.SDK","Microsoft.VisualStudio.Component.TextTemplating","Microsoft.VisualStudio.TextTemplating.MSBuild","Microsoft.VisualStudio.TextTemplating.Integration","Microsoft.VisualStudio.TextTemplating.Core","Microsoft.VisualStudio.TextTemplating.Integration.Resources","Microsoft.VisualStudio.Component.VisualStudioData","Microsoft.VisualStudio.Component.SQL.CLR","Microsoft.VisualStudio.ProTools","sqlsysclrtypes","sqlsysclrtypes","SQLCommon","Microsoft.VisualStudio.ProTools.Resources","Microsoft.VisualStudio.XamlDiagnostics","Microsoft.VisualStudio.XamlDiagnostics.Resources","Microsoft.VisualStudio.XamlDesigner","Microsoft.VisualStudio.XamlDesigner.Resources","Microsoft.VisualStudio.XamlDesigner.Executables","Microsoft.VisualStudio.XamlShared","Microsoft.VisualStudio.XamlShared.Resources","Microsoft.VisualStudio.PackageGroup.TestTools.Managed","Microsoft.VisualStudio.PackageGroup.IntelliTrace.Core","Microsoft.IntelliTrace.Core","Microsoft.IntelliTrace.Core.Targeted","Microsoft.IntelliTrace.ProfilerProxy.Msi.x64","Microsoft.IntelliTrace.ProfilerProxy.Msi","Microsoft.VisualStudio.NuGet.Core","Microsoft.VisualStudio.TestWindow.SourceBasedTestDiscovery","Microsoft.VisualStudio.TestWindow.Dotnet","Microsoft.VisualStudio.TestTools.TestGeneration","Microsoft.VisualStudio.PackageGroup.TestTools.CodeCoverage","Microsoft.VisualStudio.PackageGroup.TestTools.Enterprise","Microsoft.VisualStudio.PackageGroup.TestTools.MSTestV2.Managed","Microsoft.VisualStudio.TestTools.MSTestV2.WizardExtension.UnitTest","Microsoft.VisualStudio.PackageGroup.TestTools.Core","Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.V2.CLI","Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI","Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.V1.CLI","Microsoft.VisualStudio.TestTools.TestPlatform.V1.CLI","Microsoft.VisualStudio.TestTools.Pex.Common","Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.Legacy","Microsoft.VisualStudio.PackageGroup.MinShell.Interop","Microsoft.VisualStudio.TestTools.TP.Legacy.Tips.Msi","Microsoft.VisualStudio.TestTools.TP.Legacy.Tips.Common","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Tips","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Tips.Resources","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.TestTools","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Professional","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Common","Microsoft.VisualStudio.TestTools.TP.Legacy.Common.Res","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Core","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Core.Resources","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Agent","Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.IDE","Microsoft.VisualStudio.TestTools.TestWIExtension","Microsoft.VisualStudio.TestTools.TestPlatform.IDE","Microsoft.VisualStudio.PackageGroup.TestTools.DataCollectors","Microsoft.Component.ClickOnce","Microsoft.VisualStudio.PackageGroup.ClickOnce.MSBuild","Microsoft.VisualCpp.CRT.ClickOnce.Msi","Microsoft.ClickOnce.SignTool.Msi","Microsoft.SQL.ClickOnceBootstrapper.Msi","Microsoft.Net.ClickOnceBootstrapper","Microsoft.ClickOnce.BootStrapper.Msi.Resources","Microsoft.ClickOnce.BootStrapper.Msi","Microsoft.VisualStudio.WebTools.WSP.FSA","Microsoft.VisualStudio.WebTools.WSP.FSA.Resources","Microsoft.VisualStudio.PackageGroup.Community","Microsoft.VisualStudio.Community.Extra.Resources","Microsoft.VisualStudio.Community.Extra","Microsoft.VisualStudio.PackageGroup.Core","Microsoft.VisualStudio.TestTools.TeamFoundationClient","Microsoft.VisualStudio.PackageGroup.Debugger.Core","Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost","Microsoft.VisualStudio.VC.Ide.Debugger","Microsoft.VisualStudio.VC.Ide.Debugger.Resources","Microsoft.VisualStudio.VC.Ide.Common","Microsoft.VisualStudio.VC.Ide.Common.Resources","Microsoft.VisualStudio.Debugger.Parallel","Microsoft.VisualStudio.Debugger.Parallel.Resources","Microsoft.VisualStudio.Debugger.CollectionAgents","Microsoft.VisualStudio.Debugger.Managed","Microsoft.CodeAnalysis.VisualStudio.Setup.Resources","Microsoft.CodeAnalysis.VisualStudio.Setup","Microsoft.CodeAnalysis.ExpressionEvaluator.Resources","Microsoft.CodeAnalysis.ExpressionEvaluator","Microsoft.VisualStudio.Debugger.Managed.Resources","Microsoft.VisualStudio.Debugger.Remote","Microsoft.VisualStudio.Debugger.Remote","Microsoft.VisualStudio.Debugger.Remote.Resources","Microsoft.VisualStudio.Debugger.Remote.Resources","Microsoft.VisualStudio.Debugger","Microsoft.VisualStudio.VC.MSVCDis","Microsoft.VisualStudio.ScriptedHost","Microsoft.VisualStudio.ScriptedHost.Targeted","Microsoft.VisualStudio.ScriptedHost.Resources","Microsoft.IntelliTrace.DiagnosticsHub","Microsoft.VisualStudio.Debugger.Resources","Microsoft.PackageGroup.ClientDiagnostics","Microsoft.VisualStudio.AppResponsiveness","Microsoft.VisualStudio.AppResponsiveness.Targeted","Microsoft.VisualStudio.AppResponsiveness.Resources","Microsoft.VisualStudio.ClientDiagnostics","Microsoft.VisualStudio.ClientDiagnostics.Targeted","Microsoft.VisualStudio.ClientDiagnostics.Resources","Microsoft.VisualStudio.PackageGroup.CommunityCore","Microsoft.VisualStudio.ProjectSystem.Full","Microsoft.VisualStudio.ProjectSystem","Microsoft.VisualStudio.Community.x86","Microsoft.VisualStudio.Community.x64","Microsoft.VisualStudio.Community","Microsoft.IntelliTrace.CollectorCab","Microsoft.VisualStudio.Community.Resources","Microsoft.VisualStudio.Net.Eula.Resources","Microsoft.VisualStudio.WebSiteProject.DTE","Microsoft.MSHtml","Microsoft.VisualStudio.Community.Msi.Resources","Microsoft.VisualStudio.Community.Msi","Microsoft.VisualStudio.MinShell.Interop.Msi","Microsoft.VisualStudio.Editors","Microsoft.VisualStudio.ClickOnce.Resources","Microsoft.VisualStudio.ClickOnce","Microsoft.Component.MSBuild","Microsoft.NuGet.Build.Tasks","Microsoft.VisualStudio.Component.Roslyn.Compiler","Microsoft.CodeAnalysis.Compilers.Resources","Microsoft.CodeAnalysis.Compilers","Microsoft.Net.PackageGroup.4.6.1.Redist","Microsoft.VisualStudio.TemplateEngine","Microsoft.VisualStudio.WebToolsExtensions.Common","Microsoft.NET.Sdk","Microsoft.VisualStudio.PackageGroup.TestTools.Templates.Managed","Microsoft.VisualStudio.TestTools.Templates.Managed","Microsoft.VisualStudio.TestTools.Templates.Managed.Resources","Microsoft.VisualStudio.Templates.VB.MSTestv2.Desktop.UnitTest","Microsoft.VisualStudio.Templates.CS.MSTestv2.Desktop.UnitTest","Microsoft.VisualStudio.Templates.VB.Wpf","Microsoft.VisualStudio.Templates.VB.Wpf.Resources","Microsoft.VisualStudio.Templates.VB.Winforms","Microsoft.VisualStudio.Templates.VB.ManagedCore","Microsoft.VisualStudio.Templates.VB.Shared","Microsoft.VisualStudio.Templates.VB.Shared.Resources","Microsoft.VisualStudio.Templates.VB.ManagedCore.Resources","Microsoft.VisualStudio.Templates.CS.GettingStarted.Desktop.Package","Microsoft.VisualStudio.Templates.GetStarted.Desktop.Setup","Microsoft.VisualStudio.Templates.CS.GettingStarted.Console.Package","Microsoft.VisualStudio.Templates.GetStarted.Resources","Microsoft.VisualStudio.Templates.GetStarted.Common.Setup","Microsoft.VisualStudio.Templates.GetStarted.Console.Setup","Microsoft.VisualStudio.Templates.CS.Wpf","Microsoft.VisualStudio.Templates.CS.Wpf.Resources","Microsoft.VisualStudio.Templates.CS.Winforms","Microsoft.VisualStudio.Templates.CS.ManagedCore","Microsoft.VisualStudio.Templates.CS.Shared","Microsoft.VisualStudio.Templates.Editorconfig.Wizard.Setup","Templates.Editorconfig.SolutionFile.Setup","Microsoft.VisualStudio.Templates.CS.Shared.Resources","Microsoft.VisualStudio.Templates.CS.ManagedCore.Resources","Microsoft.VisualStudio.Component.CoreEditor","Microsoft.VisualStudio.PackageGroup.CoreEditor","PortableFacades","Microsoft.VisualStudio.PackageGroup.VsDevCmd","Microsoft.VisualStudio.VsDevCmd.Ext.NetFxSdk","Microsoft.VisualStudio.VsDevCmd.Core.WinSdk","Microsoft.VisualStudio.VsDevCmd.Core.DotNet","Microsoft.VisualStudio.VC.DevCmd","Microsoft.VisualStudio.VC.DevCmd.Resources","Microsoft.VisualStudio.VirtualTree","Microsoft.VisualStudio.PackageGroup.Progression","Microsoft.VisualStudio.PerformanceProvider","Microsoft.VisualStudio.GraphModel","Microsoft.VisualStudio.GraphProvider","Microsoft.DiaSymReader","Microsoft.Build.Dependencies","Microsoft.Build.FileTracker.Msi","Microsoft.Build","Microsoft.VisualStudio.TextMateGrammars","Microsoft.VisualStudio.PackageGroup.TeamExplorer","Microsoft.TeamFoundation.OfficeIntegration","Microsoft.TeamFoundation.OfficeIntegration.Resources","Microsoft.VisualStudio.TeamExplorer","Microsoft.ServiceHub","Microsoft.VisualStudio.ProjectServices","Microsoft.VisualStudio.SLNX.VSIX","Microsoft.VisualStudio.FileHandler.Msi","Microsoft.VisualStudio.FileHandler.Msi","Microsoft.VisualStudio.PackageGroup.MinShell","Microsoft.VisualStudio.MinShell.Interop","Microsoft.VisualStudio.Log","Microsoft.VisualStudio.Log.Targeted","Microsoft.VisualStudio.Log.Resources","Microsoft.VisualStudio.Finalizer","Microsoft.VisualStudio.WDExpress","Microsoft.VisualStudio.WDExpress.Resources","Microsoft.VisualStudio.CoreEditor","Microsoft.VisualStudio.Connected","Microsoft.VisualStudio.Connected.Resources","Microsoft.VisualStudio.MinShell","Microsoft.VisualStudio.Setup.Configuration","Microsoft.VisualStudio.MinShell.Platform","Microsoft.VisualStudio.MinShell.Platform.Resources","Microsoft.VisualStudio.MefHosting","Microsoft.VisualStudio.MefHosting.Resources","Microsoft.VisualStudio.Initializer","Microsoft.VisualStudio.ExtensionManager","Microsoft.VisualStudio.MinShell.x86","Microsoft.VisualStudio.NativeImageSupport","Microsoft.VisualStudio.MinShell.Msi","Microsoft.VisualStudio.MinShell.Msi.Resources","Microsoft.VisualStudio.LanguageServer","Microsoft.VisualStudio.MinShell.Resources","Microsoft.Net.PackageGroup.4.6.Redist","Microsoft.VisualStudio.Branding.WDExpress"]}]
diff --git a/test/fixtures/VS_2017_Unusable.txt b/test/fixtures/VS_2017_Unusable.txt
new file mode 100644
index 0000000000..fc0a257f44
--- /dev/null
+++ b/test/fixtures/VS_2017_Unusable.txt
@@ -0,0 +1 @@
+[{"path":"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildToolsUnusable","version":"15.9.28307.665","packages":["Microsoft.VisualStudio.Product.BuildTools","Microsoft.VisualStudio.Component.Windows10SDK.17134","Win10SDK_10.0.17134","Microsoft.VisualStudio.Component.VC.Tools.x86.x64","Microsoft.VisualCpp.CodeAnalysis.Extensions","Microsoft.VisualCpp.CodeAnalysis.Extensions.X86","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X86","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X86.Resources","Microsoft.VisualCpp.CodeAnalysis.Extensions.X64","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X64","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X64.Resources","Microsoft.VisualStudio.Component.Static.Analysis.Tools","Microsoft.VisualStudio.StaticAnalysis","Microsoft.VisualStudio.StaticAnalysis.Resources","Microsoft.VisualCpp.Tools.HostX64.TargetX86","Microsoft.VisualCpp.VCTip.HostX64.TargetX86","Microsoft.VisualCpp.Tools.HostX64.TargetX86.Resources","Microsoft.VisualCpp.Tools.HostX64.TargetX64","Microsoft.VisualCpp.VCTip.HostX64.TargetX64","Microsoft.VisualCpp.Tools.HostX64.TargetX64.Resources","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX64","Microsoft.VisualCpp.Premium.Tools.Hostx86.Targetx64.Resources","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX86","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX86.Resources","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX86","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX86.Resources","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX64","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX64.Resources","Microsoft.VisualCpp.PGO.X86","Microsoft.VisualCpp.PGO.X64","Microsoft.VisualCpp.PGO.Headers","Microsoft.VisualCpp.CRT.x86.Store","Microsoft.VisualCpp.CRT.x86.OneCore.Desktop","Microsoft.VisualCpp.CRT.x64.Store","Microsoft.VisualCpp.CRT.x64.OneCore.Desktop","Microsoft.VisualCpp.CRT.Redist.x86.OneCore.Desktop","Microsoft.VisualCpp.CRT.Redist.x64.OneCore.Desktop","Microsoft.VisualCpp.CRT.ClickOnce.Msi","Microsoft.VisualStudio.PackageGroup.VC.Tools.x86","Microsoft.VisualCpp.Tools.HostX86.TargetX64","Microsoft.VisualCpp.VCTip.hostX86.targetX64","Microsoft.VisualCpp.Tools.Hostx86.Targetx64.Resources","Microsoft.VisualCpp.Tools.HostX86.TargetX86","Microsoft.VisualCpp.VCTip.hostX86.targetX86","Microsoft.VisualCpp.Tools.HostX86.TargetX86.Resources","Microsoft.VisualCpp.Tools.Core.Resources","Microsoft.VisualCpp.Tools.Core.x86","Microsoft.VisualCpp.Tools.Common.Utils","Microsoft.VisualCpp.Tools.Common.Utils.Resources","Microsoft.VisualCpp.DIA.SDK","Microsoft.VisualCpp.CRT.x86.Desktop","Microsoft.VisualCpp.CRT.x64.Desktop","Microsoft.VisualCpp.CRT.Source","Microsoft.VisualCpp.CRT.Redist.X86","Microsoft.VisualCpp.CRT.Redist.X64","Microsoft.VisualCpp.CRT.Redist.Resources","Microsoft.VisualCpp.RuntimeDebug.14","Microsoft.VisualCpp.RuntimeDebug.14","Microsoft.VisualCpp.Redist.14","Microsoft.VisualCpp.Redist.14","Microsoft.VisualCpp.CRT.Headers","Microsoft.VisualStudio.Workload.MSBuildTools","Microsoft.VisualStudio.Component.CoreBuildTools","Microsoft.VisualStudio.Setup.Configuration","Microsoft.VisualStudio.PackageGroup.VsDevCmd","Microsoft.VisualStudio.VsDevCmd.Ext.NetFxSdk","Microsoft.VisualStudio.VsDevCmd.Core.WinSdk","Microsoft.VisualStudio.VsDevCmd.Core.DotNet","Microsoft.VisualStudio.VC.DevCmd","Microsoft.VisualStudio.VC.DevCmd.Resources","Microsoft.VisualStudio.BuildTools.Resources","Microsoft.VisualStudio.Net.Eula.Resources","Microsoft.Build.Dependencies","Microsoft.Build.FileTracker.Msi","Microsoft.Component.MSBuild","Microsoft.PythonTools.BuildCore.Vsix","Microsoft.NuGet.Build.Tasks","Microsoft.VisualStudio.Component.Roslyn.Compiler","Microsoft.CodeAnalysis.Compilers.Resources","Microsoft.CodeAnalysis.Compilers","Microsoft.Net.PackageGroup.4.6.1.Redist","Microsoft.Net.4.6.1.FullRedist.NonThreshold","Microsoft.Windows.UniversalCRT.Msu.81","Microsoft.VisualStudio.NativeImageSupport","Microsoft.Build"]}]
diff --git a/test/fixtures/VS_2019_BuildTools_minimal.txt b/test/fixtures/VS_2019_BuildTools_minimal.txt
new file mode 100644
index 0000000000..f07d254164
--- /dev/null
+++ b/test/fixtures/VS_2019_BuildTools_minimal.txt
@@ -0,0 +1 @@
+[{"path":"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools","version":"16.1.28922.388","packages":["Microsoft.VisualStudio.Product.BuildTools","Microsoft.VisualStudio.Component.VC.CoreIde","Microsoft.VisualStudio.VC.Ide.Pro","Microsoft.VisualStudio.VC.Ide.Pro.Resources","Microsoft.VisualStudio.VC.Templates.Pro","Microsoft.VisualStudio.VC.Templates.Pro.Resources","Microsoft.VisualStudio.VC.Items.Pro","Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Reduced","Microsoft.VisualStudio.VC.Ide.MDD","Microsoft.VisualStudio.PackageGroup.Core","Microsoft.VisualStudio.CodeSense.Community","Microsoft.VisualStudio.TestTools.TeamFoundationClient","Microsoft.PackageGroup.ClientDiagnostics","Microsoft.VisualStudio.AppResponsiveness","Microsoft.VisualStudio.AppResponsiveness.Targeted","Microsoft.VisualStudio.AppResponsiveness.Resources","Microsoft.VisualStudio.ClientDiagnostics","Microsoft.VisualStudio.ClientDiagnostics.Targeted","Microsoft.VisualStudio.ClientDiagnostics.Resources","Microsoft.VisualStudio.PackageGroup.CommunityCore","Microsoft.VisualStudio.ProjectSystem.Full","Microsoft.VisualStudio.ProjectSystem","Microsoft.VisualStudio.Community.x86","Microsoft.VisualStudio.Community.x64","Microsoft.VisualStudio.Community","Microsoft.IntelliTrace.CollectorCab","Microsoft.VisualStudio.Community.Resources","Microsoft.VisualStudio.WebSiteProject.DTE","Microsoft.MSHtml","Microsoft.VisualStudio.Platform.CallHierarchy","Microsoft.VisualStudio.Community.Msi.Resources","Microsoft.VisualStudio.Community.Msi","Microsoft.VisualStudio.MinShell.Interop.Msi","Microsoft.VisualStudio.PackageGroup.CoreEditor","Microsoft.VisualStudio.VirtualTree","Microsoft.VisualStudio.PackageGroup.Progression","Microsoft.VisualStudio.PerformanceProvider","Microsoft.VisualStudio.GraphModel","Microsoft.VisualStudio.GraphProvider","Microsoft.VisualStudio.TextMateGrammars","Microsoft.VisualStudio.PackageGroup.TeamExplorer.Common","Microsoft.VisualStudio.TeamExplorer","Microsoft.ServiceHub","Microsoft.VisualStudio.ProjectServices","Microsoft.VisualStudio.OpenFolder.VSIX","Microsoft.VisualStudio.FileHandler.Msi","Microsoft.VisualStudio.FileHandler.Msi","Microsoft.VisualStudio.PackageGroup.MinShell","Microsoft.VisualStudio.MinShell.Msi","Microsoft.VisualStudio.MinShell.Msi.Resources","Microsoft.VisualStudio.MinShell.Interop","Microsoft.VisualStudio.Log","Microsoft.VisualStudio.Log.Targeted","Microsoft.VisualStudio.Log.Resources","Microsoft.VisualStudio.Finalizer","Microsoft.VisualStudio.CoreEditor","Microsoft.VisualStudio.Platform.NavigateTo","Microsoft.VisualStudio.Connected","Microsoft.VisualStudio.Connected.Resources","Microsoft.VisualStudio.VC.Ide.x64","Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Express","Microsoft.VisualStudio.PackageGroup.Debugger.Script","Microsoft.VisualStudio.Debugger.Script.Msi","Microsoft.VisualStudio.Debugger.Script","Microsoft.VisualStudio.Debugger.Script","Microsoft.VisualStudio.Debugger.Script.Resources","Microsoft.VisualStudio.Debugger.Script.Resources","Microsoft.VisualStudio.VC.Ide.WinXPlus","Microsoft.VisualStudio.VC.Ide.Dskx","Microsoft.VisualStudio.VC.Ide.Dskx.Resources","Microsoft.VisualStudio.VC.Ide.Base","Microsoft.VisualStudio.VC.Ide.LanguageService","Microsoft.VisualStudio.VC.Ide.Core","Microsoft.VisualStudio.VisualC.Logging","Microsoft.VisualStudio.VC.Ide.Core.Resources","Microsoft.VisualStudio.VC.Ide.VCPkgDatabase","Microsoft.VisualStudio.VC.Ide.ResourceEditor","Microsoft.VisualStudio.VC.Ide.ResourceEditor.Resources","Microsoft.VisualStudio.VC.Ide.ProjectSystem","Microsoft.VisualStudio.VC.Ide.ProjectSystem.Resources","Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine","Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine.Resources","Microsoft.VisualStudio.VC.Ide.LanguageService.Resources","Microsoft.VisualStudio.VC.Ide.Base.Resources","Microsoft.Net.4.TargetingPack","Microsoft.VisualStudio.PackageGroup.Debugger.Core","Microsoft.VisualStudio.PackageGroup.Debugger.TimeTravel.Record","Microsoft.VisualStudio.Debugger.TimeTravel.Runtime","Microsoft.VisualStudio.Debugger.TimeTravel.Runtime","Microsoft.VisualStudio.Debugger.TimeTravel.Agent","Microsoft.VisualStudio.Debugger.TimeTravel.Record","Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost","Microsoft.VisualStudio.VC.Ide.Debugger","Microsoft.VisualStudio.VC.Ide.Debugger.Concord","Microsoft.VisualStudio.VC.Ide.Debugger.Concord.Resources","Microsoft.VisualStudio.VC.Ide.Debugger.Resources","Microsoft.VisualStudio.VC.Ide.Common","Microsoft.VisualStudio.VC.Ide.Common.Resources","Microsoft.VisualStudio.Debugger.Parallel","Microsoft.VisualStudio.Debugger.Parallel.Resources","Microsoft.VisualStudio.Debugger.CollectionAgents","Microsoft.VisualStudio.Debugger.Managed","Microsoft.DiaSymReader","Microsoft.CodeAnalysis.ExpressionEvaluator","Microsoft.VisualStudio.Debugger.Concord.Managed","Microsoft.VisualStudio.Debugger.Concord.Managed.Resources","Microsoft.VisualStudio.Debugger.Managed.Resources","Microsoft.VisualStudio.Debugger.Remote","Microsoft.VisualStudio.Debugger.Concord.Remote","Microsoft.VisualStudio.Debugger.Concord.Remote.Resources","Microsoft.VisualStudio.Debugger.Remote","Microsoft.VisualStudio.Debugger.Concord.Remote","Microsoft.VisualStudio.Debugger.Concord.Remote.Resources","Microsoft.VisualStudio.Debugger.Remote.Resources","Microsoft.VisualStudio.Debugger.Remote.Resources","Microsoft.VisualStudio.Debugger","Microsoft.VisualStudio.PerfLib","Microsoft.VisualStudio.Debugger.Package.DiagHub.Client.VSx86","Microsoft.VisualStudio.Debugger.Remote.DiagHub.Client","Microsoft.VisualStudio.Debugger.Remote.DiagHub.Client","Microsoft.VisualStudio.VC.MSVCDis","Microsoft.VisualStudio.ScriptedHost","Microsoft.VisualStudio.ScriptedHost.Targeted","Microsoft.VisualStudio.ScriptedHost.Resources","Microsoft.VisualStudio.Editors","Microsoft.IntelliTrace.DiagnosticsHub","Microsoft.VisualStudio.MinShell","Microsoft.VisualStudio.MinShell.Platform","Microsoft.VisualStudio.MinShell.Platform.Resources","Microsoft.VisualStudio.MefHosting","Microsoft.VisualStudio.MefHosting.Resources","Microsoft.VisualStudio.Initializer","Microsoft.VisualStudio.ExtensionManager","Microsoft.VisualStudio.Platform.Editor","Microsoft.VisualStudio.Debugger.Concord","Microsoft.VisualStudio.Debugger.Concord.Resources","Microsoft.VisualStudio.Debugger.Resources","Microsoft.CodeAnalysis.VisualStudio.Setup","Microsoft.VisualStudio.Component.Windows10SDK.17134","Win10SDK_10.0.17134","Microsoft.VisualStudio.Component.VC.Tools.x86.x64","Microsoft.VisualCpp.CodeAnalysis.Extensions","Microsoft.VisualCpp.CodeAnalysis.Extensions.X86","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X86","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X86.Resources","Microsoft.VisualCpp.CodeAnalysis.Extensions.X64","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X64","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X64.Resources","Microsoft.VisualStudio.StaticAnalysis","Microsoft.VisualStudio.StaticAnalysis.Resources","Microsoft.VisualCpp.Tools.HostX64.TargetX86","Microsoft.VisualCpp.VCTip.HostX64.TargetX86","Microsoft.VisualCpp.Tools.HostX64.TargetX86.Resources","Microsoft.VisualCpp.Tools.HostX64.TargetX64","Microsoft.VisualCpp.VCTip.HostX64.TargetX64","Microsoft.VisualCpp.Tools.HostX64.TargetX64.Resources","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX64","Microsoft.VisualCpp.Premium.Tools.Hostx86.Targetx64.Resources","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX86","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX86.Resources","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX86","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX86.Resources","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX64","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX64.Resources","Microsoft.VisualCpp.PGO.X86","Microsoft.VisualCpp.PGO.X64","Microsoft.VisualCpp.PGO.Headers","Microsoft.VisualCpp.CRT.x86.Store","Microsoft.VisualCpp.CRT.x86.OneCore.Desktop","Microsoft.VisualCpp.CRT.x64.Store","Microsoft.VisualCpp.CRT.x64.OneCore.Desktop","Microsoft.VisualCpp.CRT.Redist.x86.OneCore.Desktop","Microsoft.VisualCpp.CRT.Redist.x64.OneCore.Desktop","Microsoft.VisualCpp.CRT.ClickOnce.Msi","Microsoft.VisualStudio.PackageGroup.VC.Tools.x86","Microsoft.VisualCpp.Tools.HostX86.TargetX64","Microsoft.VisualCpp.VCTip.hostX86.targetX64","Microsoft.VisualCpp.Tools.Hostx86.Targetx64.Resources","Microsoft.VisualCpp.Tools.HostX86.TargetX86","Microsoft.VisualCpp.VCTip.hostX86.targetX86","Microsoft.VisualCpp.Tools.HostX86.TargetX86.Resources","Microsoft.VisualCpp.Tools.Core.Resources","Microsoft.VisualCpp.Tools.Core.x86","Microsoft.VisualCpp.Tools.Common.Utils","Microsoft.VisualCpp.Tools.Common.Utils.Resources","Microsoft.VisualCpp.DIA.SDK","Microsoft.VisualCpp.CRT.x86.Desktop","Microsoft.VisualCpp.CRT.x64.Desktop","Microsoft.VisualCpp.CRT.Source","Microsoft.VisualCpp.CRT.Redist.X86","Microsoft.VisualCpp.CRT.Redist.X64","Microsoft.VisualCpp.CRT.Redist.Resources","Microsoft.VisualCpp.RuntimeDebug.14","Microsoft.VisualCpp.RuntimeDebug.14","Microsoft.VisualCpp.Redist.14.Latest","Microsoft.VisualCpp.Redist.14.Latest","Microsoft.VisualCpp.Redist.14","Microsoft.VisualCpp.Redist.14","Microsoft.VisualCpp.CRT.Headers","Microsoft.VisualStudio.VC.MSBuild.x86.v142","Microsoft.VisualStudio.VC.MSBuild.X86","Microsoft.VisualStudio.VC.MSBuild.X64.v142","Microsoft.VisualStudio.VC.MSBuild.X64","Microsoft.VS.VC.MSBuild.X64.Resources","Microsoft.VisualStudio.VC.MSBuild.ARM.v142","Microsoft.VisualStudio.VC.MSBuild.ARM","Microsoft.VisualStudio.VC.MSBuild.Base","Microsoft.VisualStudio.VC.MSBuild.Base.Resources","Microsoft.VisualStudio.Workload.MSBuildTools","Microsoft.VisualStudio.Component.CoreBuildTools","Microsoft.VisualStudio.Setup.Configuration","Microsoft.VisualStudio.PackageGroup.VsDevCmd","Microsoft.VisualStudio.VsDevCmd.Ext.NetFxSdk","Microsoft.VisualStudio.VsDevCmd.Core.WinSdk","Microsoft.VisualStudio.VsDevCmd.Core.DotNet","Microsoft.VisualStudio.VC.DevCmd","Microsoft.VisualStudio.VC.DevCmd.Resources","Microsoft.VisualStudio.BuildTools.Resources","Microsoft.VisualStudio.Net.Eula.Resources","Microsoft.Build.Dependencies","Microsoft.Build.FileTracker.Msi","Microsoft.Component.MSBuild","Microsoft.PythonTools.BuildCore.Vsix","Microsoft.NuGet.Build.Tasks","Microsoft.VisualStudio.Component.Roslyn.Compiler","Microsoft.CodeAnalysis.Compilers","Microsoft.Net.PackageGroup.4.7.2.Redist","Microsoft.VisualStudio.NativeImageSupport","Microsoft.Build","Microsoft.VisualStudio.PackageGroup.NuGet","Microsoft.VisualStudio.NuGet.BuildTools"]}]
diff --git a/test/fixtures/VS_2019_Community_workload.txt b/test/fixtures/VS_2019_Community_workload.txt
new file mode 100644
index 0000000000..50071c25f1
--- /dev/null
+++ b/test/fixtures/VS_2019_Community_workload.txt
@@ -0,0 +1 @@
+[{"path":"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community","version":"16.1.28922.388","packages":["Microsoft.VisualStudio.Workload.NativeDesktop","Microsoft.VisualStudio.Component.VC.TestAdapterForGoogleTest","Microsoft.VisualStudio.VC.Ide.TestAdapterForGoogleTest","Microsoft.VisualStudio.Component.VC.TestAdapterForBoostTest","Microsoft.VisualStudio.VC.Ide.TestAdapterForBoostTest","Microsoft.VisualStudio.Component.VC.ATL","Microsoft.VisualStudio.VC.Ide.ATL","Microsoft.VisualStudio.VC.Ide.ATL.Resources","Microsoft.VisualCpp.ATL.X86","Microsoft.VisualCpp.ATL.X64","Microsoft.VisualCpp.ATL.Source","Microsoft.VisualCpp.ATL.Headers","Microsoft.VisualStudio.Component.VC.CMake.Project","Microsoft.VisualStudio.VC.CMake","Microsoft.VisualStudio.VC.CMake.Project","Microsoft.VisualStudio.VC.ExternalBuildFramework","Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core","Microsoft.VisualStudio.PackageGroup.TestTools.Native","Microsoft.VisualStudio.Component.VC.Redist.14.Latest","Microsoft.VisualStudio.VC.Templates.UnitTest","Microsoft.VisualStudio.VC.UnitTest.Desktop.Build.Core","Microsoft.VisualStudio.TestTools.TestPlatform.V1.CPP","Microsoft.VisualStudio.VC.Templates.UnitTest.Resources","Microsoft.VisualStudio.VC.Templates.Desktop","Microsoft.VisualStudio.Component.Debugger.JustInTime","Microsoft.VisualStudio.Debugger.ImmersiveActivateHelper.Msi","Microsoft.VisualStudio.Debugger.JustInTime","Microsoft.VisualStudio.Debugger.JustInTime.Msi","Microsoft.VisualStudio.Component.Windows10SDK.17763","Win10SDK_10.0.17763","Microsoft.VisualStudio.Component.VC.DiagnosticTools","Microsoft.VisualStudio.Component.Graphics.Tools","Microsoft.VisualStudio.Component.VC.Tools.x86.x64","Microsoft.VisualCpp.CodeAnalysis.Extensions","Microsoft.VisualCpp.CodeAnalysis.Extensions.X86","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X86","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X86.Resources","Microsoft.VisualCpp.CodeAnalysis.Extensions.X64","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X64","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X64.Resources","Microsoft.VisualCpp.Tools.HostX64.TargetX86","Microsoft.VisualCpp.VCTip.HostX64.TargetX86","Microsoft.VisualCpp.Tools.HostX64.TargetX86.Resources","Microsoft.VisualCpp.Tools.HostX64.TargetX64","Microsoft.VisualCpp.VCTip.HostX64.TargetX64","Microsoft.VisualCpp.Tools.HostX64.TargetX64.Resources","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX64","Microsoft.VisualCpp.Premium.Tools.Hostx86.Targetx64.Resources","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX86","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX86.Resources","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX86","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX86.Resources","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX64","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX64.Resources","Microsoft.VisualCpp.PGO.X86","Microsoft.VisualCpp.PGO.X64","Microsoft.VisualCpp.PGO.Headers","Microsoft.VisualCpp.CRT.x86.Store","Microsoft.VisualCpp.CRT.x86.OneCore.Desktop","Microsoft.VisualCpp.CRT.x64.Store","Microsoft.VisualCpp.CRT.x64.OneCore.Desktop","Microsoft.VisualCpp.CRT.Redist.x86.OneCore.Desktop","Microsoft.VisualCpp.CRT.Redist.x64.OneCore.Desktop","Microsoft.VisualStudio.PackageGroup.VC.Tools.x86","Microsoft.VisualCpp.Tools.HostX86.TargetX64","Microsoft.VisualCpp.VCTip.hostX86.targetX64","Microsoft.VisualCpp.Tools.Hostx86.Targetx64.Resources","Microsoft.VisualCpp.Tools.HostX86.TargetX86","Microsoft.VisualCpp.VCTip.hostX86.targetX86","Microsoft.VisualCpp.Tools.HostX86.TargetX86.Resources","Microsoft.VisualCpp.Tools.Core.Resources","Microsoft.VisualCpp.Tools.Core.x86","Microsoft.VisualCpp.DIA.SDK","Microsoft.VisualCpp.CRT.x86.Desktop","Microsoft.VisualCpp.CRT.x64.Desktop","Microsoft.VisualCpp.CRT.Source","Microsoft.VisualCpp.CRT.Redist.X86","Microsoft.VisualCpp.CRT.Redist.X64","Microsoft.VisualCpp.CRT.Redist.Resources","Microsoft.VisualCpp.RuntimeDebug.14","Microsoft.VisualCpp.RuntimeDebug.14","Microsoft.VisualCpp.Redist.14.Latest","Microsoft.VisualCpp.Redist.14.Latest","Microsoft.VisualCpp.CRT.Headers","Microsoft.VisualStudio.Graphics.Viewers","Microsoft.VisualStudio.Graphics.Viewers.Resources","Microsoft.VisualStudio.Graphics.Msi","Microsoft.VisualStudio.Graphics.Msi","Microsoft.VisualStudio.Graphics.Analyzer","Microsoft.VisualStudio.Graphics.Analyzer.Targeted","Microsoft.VisualStudio.Graphics.Analyzer.Resources","Microsoft.VisualStudio.Graphics.Appid","Microsoft.VisualStudio.Graphics.Appid.Resources","Microsoft.VisualStudio.Component.VC.CoreIde","Microsoft.VisualStudio.VC.Ide.Pro","Microsoft.VisualStudio.VC.Ide.Pro.Resources","Microsoft.VisualStudio.VC.Templates.Pro","Microsoft.VisualStudio.VC.Templates.Pro.Resources","Microsoft.VisualStudio.VC.Items.Pro","Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Reduced","Microsoft.VisualStudio.VC.Ide.x64","Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Express","Microsoft.VisualStudio.VC.MSBuild.X64.v142","Microsoft.VisualStudio.VC.MSBuild.X64","Microsoft.VS.VC.MSBuild.X64.Resources","Microsoft.VisualStudio.VC.MSBuild.ARM.v142","Microsoft.VisualStudio.VC.MSBuild.ARM","Microsoft.VisualStudio.VC.MSBuild.x86.v142","Microsoft.VisualStudio.VC.MSBuild.X86","Microsoft.VisualStudio.VC.MSBuild.Base","Microsoft.VisualStudio.VC.MSBuild.Base.Resources","Microsoft.VisualStudio.VC.Ide.WinXPlus","Microsoft.VisualStudio.VC.Ide.Dskx","Microsoft.VisualStudio.VC.Ide.Dskx.Resources","Microsoft.VisualStudio.VC.Ide.Base","Microsoft.VisualStudio.VC.Ide.LanguageService","Microsoft.VisualStudio.VC.Ide.Core","Microsoft.VisualStudio.VC.Ide.Core.Resources","Microsoft.VisualStudio.VC.Ide.VCPkgDatabase","Microsoft.VisualStudio.VC.Ide.ProjectSystem","Microsoft.VisualStudio.VC.Ide.ProjectSystem.Resources","Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine","Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine.Resources","Microsoft.VisualStudio.VC.Ide.LanguageService.Resources","Microsoft.VisualStudio.VC.Ide.Base.Resources","Component.Microsoft.VisualStudio.LiveShare","Microsoft.VisualStudio.LiveShare","Microsoft.Icecap.Analysis","Microsoft.Icecap.Analysis.Targeted","Microsoft.Icecap.Analysis.Resources","Microsoft.Icecap.Analysis.Resources.Targeted","Microsoft.Icecap.Collection.Msi","Microsoft.Icecap.Collection.Msi.Targeted","Microsoft.Icecap.Collection.Msi.Resources","Microsoft.Icecap.Collection.Msi.Resources.Targeted","Microsoft.DiagnosticsHub.Instrumentation","Microsoft.DiagnosticsHub.CpuSampling.ExternalDependencies","Microsoft.DiagnosticsHub.CpuSampling","Microsoft.DiagnosticsHub.CpuSampling.Targeted","Microsoft.PackageGroup.DiagnosticsHub.Platform","Microsoft.DiagnosticsHub.Runtime.ExternalDependencies","Microsoft.DiagnosticsHub.Runtime.ExternalDependencies.Targeted","Microsoft.DiagnosticsHub.Collection.ExternalDependencies.x64","Microsoft.DiagnosticsHub.Collection.StopService.Uninstall","Microsoft.DiagnosticsHub.Runtime","Microsoft.DiagnosticsHub.Runtime.Targeted","Microsoft.DiagnosticsHub.Collection","Microsoft.DiagnosticsHub.Collection.Service","Microsoft.DiagnosticsHub.Collection.StopService.Install","Microsoft.VisualStudio.Component.IntelliCode","Microsoft.VisualStudio.IntelliCode","Microsoft.Net.4.TargetingPack","Microsoft.VisualStudio.VC.Ide.ResourceEditor","Microsoft.VisualStudio.VC.Ide.ResourceEditor.Resources","Microsoft.VisualStudio.PackageGroup.TestTools.CodeCoverage","Microsoft.VisualStudio.PackageGroup.TestTools.Core","Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.V2.CLI","Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI","Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.V1.CLI","Microsoft.VisualStudio.TestTools.TestPlatform.V1.CLI","Microsoft.VisualStudio.TestTools.Pex.Common","Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.Legacy","Microsoft.VisualStudio.PackageGroup.MinShell.Interop","Microsoft.VisualStudio.TestTools.TP.Legacy.Tips.Msi","Microsoft.VisualStudio.TestTools.TP.Legacy.Tips.Common","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Tips","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Tips.Resources","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.TestTools","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Professional","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Common","Microsoft.VisualStudio.TestTools.TP.Legacy.Common.Res","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Core","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Core.Resources","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Agent","Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.IDE","Microsoft.VisualStudio.TestTools.TestWIExtension","Microsoft.VisualStudio.TestTools.TestPlatform.IDE","Microsoft.VisualStudio.PackageGroup.TestTools.DataCollectors","Microsoft.VisualStudio.LiveShareApi","Microsoft.VisualStudio.Component.TextTemplating","Microsoft.VisualStudio.TextTemplating.MSBuild","Microsoft.VisualStudio.TextTemplating.Integration","Microsoft.VisualStudio.TextTemplating.Core","Microsoft.VisualStudio.TextTemplating.Integration.Resources","Microsoft.VisualCpp.CRT.ClickOnce.Msi","Microsoft.Component.MSBuild","Microsoft.NuGet.Build.Tasks","Microsoft.DiagnosticsHub.KB2882822.Win7","Microsoft.VisualStudio.PackageGroup.Debugger.Script","Microsoft.VisualStudio.Debugger.Script.Msi","Microsoft.VisualStudio.Debugger.Script","Microsoft.VisualStudio.Debugger.Script","Microsoft.VisualStudio.Debugger.Script.Resources","Microsoft.VisualStudio.Debugger.Script.Resources","Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions","Microsoft.VisualStudio.ProTools","sqlsysclrtypes","sqlsysclrtypes","SQLCommon","Microsoft.VisualStudio.ProTools.Resources","Microsoft.VisualStudio.WebToolsExtensions","Microsoft.VisualStudio.WebTools","Microsoft.VisualStudio.WebTools.Resources","Microsoft.VisualStudio.WebTools.MSBuild","Microsoft.VisualStudio.WebTools.WSP.FSA","Microsoft.VisualStudio.WebTools.WSP.FSA.Resources","Microsoft.VisualStudio.VC.Ide.MDD","Microsoft.VisualStudio.VisualC.Logging","Microsoft.WebTools.Shared","Microsoft.WebTools.DotNet.Core.ItemTemplates","Microsoft.VisualCpp.Redist.14","Microsoft.VisualCpp.Redist.14","Microsoft.Windows.UniversalCRT.Msu.7","Microsoft.VisualStudio.StaticAnalysis","Microsoft.VisualStudio.StaticAnalysis.Resources","Microsoft.VisualStudio.Component.Roslyn.Compiler","Microsoft.CodeAnalysis.Compilers","Microsoft.VisualStudio.Component.NuGet","Microsoft.CredentialProvider","Microsoft.VisualStudio.NuGet.PowershellBindingRedirect","Microsoft.VisualStudio.NuGet.Licenses","Microsoft.VisualStudio.PackageGroup.Community","Microsoft.VisualStudio.Community.Extra.Resources","Microsoft.VisualStudio.Community.Extra","Microsoft.VisualStudio.PackageGroup.Core","Microsoft.VisualStudio.CodeSense.Community","Microsoft.VisualStudio.TestTools.TeamFoundationClient","Microsoft.VisualStudio.PackageGroup.Debugger.Core","Microsoft.VisualStudio.PackageGroup.Debugger.TimeTravel.Record","Microsoft.VisualStudio.Debugger.TimeTravel.Runtime","Microsoft.VisualStudio.Debugger.TimeTravel.Runtime","Microsoft.VisualStudio.Debugger.TimeTravel.Agent","Microsoft.VisualStudio.Debugger.TimeTravel.Record","Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost","Microsoft.VisualStudio.VC.Ide.Debugger","Microsoft.VisualStudio.VC.Ide.Debugger.Concord","Microsoft.VisualStudio.VC.Ide.Debugger.Concord.Resources","Microsoft.VisualStudio.VC.Ide.Debugger.Resources","Microsoft.VisualStudio.VC.Ide.Common","Microsoft.VisualStudio.VC.Ide.Common.Resources","Microsoft.VisualStudio.Debugger.Parallel","Microsoft.VisualStudio.Debugger.Parallel.Resources","Microsoft.VisualStudio.Debugger.CollectionAgents","Microsoft.VisualStudio.Debugger.Managed","Microsoft.CodeAnalysis.VisualStudio.Setup","Microsoft.CodeAnalysis.ExpressionEvaluator","Microsoft.VisualStudio.Debugger.Concord.Managed","Microsoft.VisualStudio.Debugger.Concord.Managed.Resources","Microsoft.VisualStudio.Debugger.Managed.Resources","Microsoft.VisualStudio.Debugger.Remote","Microsoft.VisualStudio.Debugger.Remote.DbgHelp.Win8","Microsoft.VisualStudio.Debugger.Concord.Remote","Microsoft.VisualStudio.Debugger.Concord.Remote.Resources","Microsoft.VisualStudio.Debugger.Remote","Microsoft.VisualStudio.Debugger.Remote.DbgHelp.Win8","Microsoft.VisualStudio.Debugger.Concord.Remote","Microsoft.VisualStudio.Debugger.Concord.Remote.Resources","Microsoft.VisualStudio.Debugger.Remote.Resources","Microsoft.VisualStudio.Debugger.Remote.Resources","Microsoft.VisualStudio.Debugger","Microsoft.VisualStudio.Debugger.Package.DiagHub.Client.VSx86","Microsoft.VisualStudio.Debugger.Remote.DiagHub.Client","Microsoft.VisualStudio.Debugger.Remote.DiagHub.Client","Microsoft.VisualStudio.Debugger.DbgHelp.Win8","Microsoft.VisualStudio.VC.MSVCDis","Microsoft.VisualStudio.ScriptedHost","Microsoft.VisualStudio.ScriptedHost.Targeted","Microsoft.VisualStudio.ScriptedHost.Resources","Microsoft.IntelliTrace.DiagnosticsHub","Microsoft.VisualStudio.Debugger.Concord","Microsoft.VisualStudio.Debugger.Concord.Resources","Microsoft.VisualStudio.Debugger.Resources","Microsoft.PackageGroup.ClientDiagnostics","Microsoft.VisualStudio.AppResponsiveness","Microsoft.VisualStudio.AppResponsiveness.Targeted","Microsoft.VisualStudio.AppResponsiveness.Resources","Microsoft.VisualStudio.ClientDiagnostics","Microsoft.VisualStudio.ClientDiagnostics.Targeted","Microsoft.VisualStudio.ClientDiagnostics.Resources","Microsoft.VisualStudio.PackageGroup.CommunityCore","Microsoft.VisualStudio.ProjectSystem.Full","Microsoft.VisualStudio.ProjectSystem","Microsoft.VisualStudio.Community.x86","Microsoft.VisualStudio.Community.x64","Microsoft.VisualStudio.Community","Microsoft.IntelliTrace.CollectorCab","Microsoft.VisualStudio.Community.Resources","Microsoft.VisualStudio.Net.Eula.Resources","Microsoft.VisualStudio.WebSiteProject.DTE","Microsoft.MSHtml","Microsoft.VisualStudio.Platform.CallHierarchy","Microsoft.VisualStudio.Community.Msi.Resources","Microsoft.VisualStudio.Community.Msi","Microsoft.VisualStudio.Devenv.Msi","Microsoft.VisualStudio.MinShell.Interop.Msi","Microsoft.VisualStudio.Editors","Microsoft.VisualStudio.Product.Community","Microsoft.VisualStudio.Workload.CoreEditor","Microsoft.VisualStudio.Component.CoreEditor","Microsoft.VisualStudio.PackageGroup.CoreEditor","Microsoft.VisualCpp.Tools.Common.UtilsPrereq","Microsoft.VisualCpp.Tools.Common.Utils","Microsoft.VisualCpp.Tools.Common.Utils.Resources","Microsoft.VisualStudio.PackageGroup.VsDevCmd","Microsoft.VisualStudio.VsDevCmd.Ext.NetFxSdk","Microsoft.VisualStudio.VsDevCmd.Core.WinSdk","Microsoft.VisualStudio.VsDevCmd.Core.DotNet","Microsoft.VisualStudio.VC.DevCmd","Microsoft.VisualStudio.VC.DevCmd.Resources","Microsoft.VisualStudio.VirtualTree","Microsoft.VisualStudio.PackageGroup.Progression","Microsoft.VisualStudio.PerformanceProvider","Microsoft.VisualStudio.GraphModel","Microsoft.VisualStudio.GraphProvider","Microsoft.DiaSymReader","Microsoft.Build.Dependencies","Microsoft.Build.FileTracker.Msi","Microsoft.Build","Microsoft.VisualStudio.PackageGroup.NuGet","Microsoft.VisualStudio.NuGet.Core","Microsoft.VisualStudio.TextMateGrammars","Microsoft.VisualStudio.PackageGroup.TeamExplorer.Common","Microsoft.VisualStudio.TeamExplorer","Microsoft.ServiceHub","Microsoft.VisualStudio.ProjectServices","Microsoft.VisualStudio.OpenFolder.VSIX","Microsoft.VisualStudio.FileHandler.Msi","Microsoft.VisualStudio.FileHandler.Msi","Microsoft.VisualStudio.PackageGroup.MinShell","Microsoft.VisualStudio.MinShell.Interop","Microsoft.VisualStudio.Log","Microsoft.VisualStudio.Log.Targeted","Microsoft.VisualStudio.Log.Resources","Microsoft.VisualStudio.Finalizer","Microsoft.VisualStudio.Devenv","Microsoft.VisualStudio.Devenv.Resources","Microsoft.VisualStudio.CoreEditor","Microsoft.VisualStudio.Platform.NavigateTo","Microsoft.VisualStudio.Connected","Microsoft.VisualStudio.PerfLib","Microsoft.VisualStudio.Connected.Resources","Microsoft.VisualStudio.MinShell","Microsoft.VisualStudio.Setup.Configuration","Microsoft.VisualStudio.MinShell.Platform","Microsoft.VisualStudio.MinShell.Platform.Resources","Microsoft.VisualStudio.MefHosting","Microsoft.VisualStudio.MefHosting.Resources","Microsoft.VisualStudio.Initializer","Microsoft.VisualStudio.ExtensionManager","Microsoft.VisualStudio.Platform.Editor","Microsoft.VisualStudio.MinShell.x86","Microsoft.VisualStudio.NativeImageSupport","Microsoft.VisualStudio.MinShell.Msi","Microsoft.VisualStudio.MinShell.Msi.Resources","Microsoft.VisualStudio.LanguageServer","Microsoft.VisualStudio.Devenv.Config","Microsoft.VisualStudio.MinShell.Resources","Microsoft.Net.PackageGroup.4.7.2.Redist","Microsoft.Net.4.7.2.FullRedist","Microsoft.VisualStudio.Branding.Community"]}]
diff --git a/test/fixtures/VS_2019_Preview.txt b/test/fixtures/VS_2019_Preview.txt
new file mode 100644
index 0000000000..806509e7ce
--- /dev/null
+++ b/test/fixtures/VS_2019_Preview.txt
@@ -0,0 +1 @@
+[{"path":"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Preview","version":"16.0.28608.199","packages":["Microsoft.VisualStudio.Product.Enterprise","Microsoft.VisualStudio.Workload.NativeDesktop","Microsoft.VisualStudio.Component.VC.TestAdapterForGoogleTest","Microsoft.VisualStudio.VC.Ide.TestAdapterForGoogleTest","Microsoft.VisualStudio.Component.VC.TestAdapterForBoostTest","Microsoft.VisualStudio.VC.Ide.TestAdapterForBoostTest","Microsoft.VisualStudio.Component.VC.ATL","Microsoft.VisualStudio.VC.Ide.ATL","Microsoft.VisualStudio.VC.Ide.ATL.Resources","Microsoft.VisualCpp.ATL.X86","Microsoft.VisualCpp.ATL.X64","Microsoft.VisualCpp.ATL.Source","Microsoft.VisualCpp.ATL.Headers","Microsoft.VisualStudio.Component.VC.CMake.Project","Microsoft.VisualStudio.VC.CMake","Microsoft.VisualStudio.VC.CMake.Project","Microsoft.VisualStudio.VC.ExternalBuildFramework","Microsoft.VisualStudio.Component.VC.DiagnosticTools","Microsoft.VisualStudio.Component.Graphics.Tools","Microsoft.VisualStudio.Graphics.Viewers","Microsoft.VisualStudio.Graphics.Viewers.Resources","Microsoft.VisualStudio.Graphics.EnableTools","Microsoft.VisualStudio.Graphics.Msi","Microsoft.VisualStudio.Graphics.Msi","Microsoft.VisualStudio.Graphics.Analyzer","Microsoft.VisualStudio.Graphics.Analyzer.Targeted","Microsoft.VisualStudio.Graphics.Analyzer.Resources","Microsoft.VisualStudio.Graphics.Appid","Microsoft.VisualStudio.Graphics.Appid.Resources","Microsoft.VisualStudio.Component.Windows10SDK.17763","Win10SDK_10.0.17763","Microsoft.VisualStudio.Component.VC.Tools.x86.x64","Microsoft.VisualCpp.CodeAnalysis.Extensions","Microsoft.VisualCpp.CodeAnalysis.Extensions.X86","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X86","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X86.Resources","Microsoft.VisualCpp.CodeAnalysis.Extensions.X64","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X64","Microsoft.VisualCpp.CodeAnalysis.ConcurrencyCheck.X64.Resources","Microsoft.VisualCpp.Tools.HostX64.TargetX86","Microsoft.VisualCpp.VCTip.HostX64.TargetX86","Microsoft.VisualCpp.Tools.HostX64.TargetX86.Resources","Microsoft.VisualCpp.Tools.HostX64.TargetX64","Microsoft.VisualCpp.VCTip.HostX64.TargetX64","Microsoft.VisualCpp.Tools.HostX64.TargetX64.Resources","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX64","Microsoft.VisualCpp.Premium.Tools.Hostx86.Targetx64.Resources","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX86","Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX86.Resources","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX86","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX86.Resources","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX64","Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX64.Resources","Microsoft.VisualCpp.PGO.X86","Microsoft.VisualCpp.PGO.X64","Microsoft.VisualCpp.PGO.Headers","Microsoft.VisualCpp.CRT.x86.Store","Microsoft.VisualCpp.CRT.x86.OneCore.Desktop","Microsoft.VisualCpp.CRT.x64.Store","Microsoft.VisualCpp.CRT.x64.OneCore.Desktop","Microsoft.VisualCpp.CRT.Redist.x86.OneCore.Desktop","Microsoft.VisualCpp.CRT.Redist.x64.OneCore.Desktop","Microsoft.VisualStudio.PackageGroup.VC.Tools.x86","Microsoft.VisualCpp.Tools.HostX86.TargetX64","Microsoft.VisualCpp.VCTip.hostX86.targetX64","Microsoft.VisualCpp.Tools.Hostx86.Targetx64.Resources","Microsoft.VisualCpp.Tools.HostX86.TargetX86","Microsoft.VisualCpp.VCTip.hostX86.targetX86","Microsoft.VisualCpp.Tools.HostX86.TargetX86.Resources","Microsoft.VisualCpp.Tools.Core.Resources","Microsoft.VisualCpp.Tools.Core.x86","Microsoft.VisualCpp.DIA.SDK","Microsoft.VisualCpp.CRT.x86.Desktop","Microsoft.VisualCpp.CRT.x64.Desktop","Microsoft.VisualCpp.CRT.Source","Microsoft.VisualCpp.CRT.Redist.X86","Microsoft.VisualCpp.CRT.Redist.X64","Microsoft.VisualCpp.CRT.Redist.Resources","Microsoft.VisualCpp.RuntimeDebug.14","Microsoft.VisualCpp.RuntimeDebug.14","Microsoft.VisualCpp.CRT.Headers","Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core","Microsoft.VisualStudio.PackageGroup.TestTools.Native","Microsoft.VisualStudio.ComponentGroup.ArchitectureTools.Native","Microsoft.VisualStudio.Component.ClassDesigner","Microsoft.VisualStudio.ClassDesigner","Microsoft.VisualStudio.ClassDesigner.Resources","Microsoft.VisualStudio.Component.VC.Redist.14.Latest","Microsoft.VisualCpp.Redist.14.Latest","Microsoft.VisualCpp.Redist.14.Latest","Microsoft.VisualStudio.VC.Templates.UnitTest","Microsoft.VisualStudio.VC.UnitTest.Desktop.Build.Core","Microsoft.VisualStudio.TestTools.TestPlatform.V1.CPP","Microsoft.VisualStudio.VC.Templates.UnitTest.Resources","Microsoft.VisualStudio.VC.Templates.Desktop","Microsoft.VisualStudio.Component.VC.CoreIde","Microsoft.VisualStudio.VC.Ide.Pro","Microsoft.VisualStudio.VC.Ide.Pro.Resources","Microsoft.VisualStudio.VC.Templates.Pro","Microsoft.VisualStudio.VC.Templates.Pro.Resources","Microsoft.VisualStudio.VC.Items.Pro","Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Reduced","Microsoft.VisualStudio.VC.Ide.x64","Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Express","Microsoft.VisualStudio.VC.MSBuild.X64.v142","Microsoft.VisualStudio.VC.MSBuild.X64","Microsoft.VS.VC.MSBuild.X64.Resources","Microsoft.VisualStudio.VC.MSBuild.ARM.v142","Microsoft.VisualStudio.VC.MSBuild.ARM","Microsoft.VisualStudio.VC.MSBuild.x86.v142","Microsoft.VisualStudio.VC.MSBuild.X86","Microsoft.VisualStudio.VC.MSBuild.Base","Microsoft.VisualStudio.VC.MSBuild.Base.Resources","Microsoft.VisualStudio.VC.Ide.WinXPlus","Microsoft.VisualStudio.VC.Ide.Dskx","Microsoft.VisualStudio.VC.Ide.Dskx.Resources","Microsoft.VisualStudio.VC.Ide.Base","Microsoft.VisualStudio.VC.Ide.LanguageService","Microsoft.VisualStudio.VC.Ide.Core","Microsoft.VisualStudio.VC.Ide.Core.Resources","Microsoft.VisualStudio.VC.Ide.VCPkgDatabase","Microsoft.VisualStudio.VC.Ide.Progression.Enterprise","Microsoft.VisualStudio.VC.Ide.ProjectSystem","Microsoft.VisualStudio.VC.Ide.ProjectSystem.Resources","Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine","Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine.Resources","Microsoft.VisualStudio.VC.Ide.LanguageService.Resources","Microsoft.VisualStudio.VC.Ide.Base.Resources","Microsoft.VisualStudio.Component.CodeMap","Microsoft.VisualStudio.Component.GraphDocument","Microsoft.VisualStudio.Vmp","Microsoft.VisualStudio.GraphDocument","Microsoft.VisualStudio.GraphDocument.Resources","Microsoft.VisualStudio.CodeMap","Microsoft.VisualStudio.Component.SQL.LocalDB.Runtime","Microsoft.VisualStudio.Component.SQL.NCLI","sqllocaldb","sqlncli","Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions","Microsoft.VisualStudio.WebToolsExtensions","Microsoft.VisualStudio.WebTools","Microsoft.VisualStudio.WebTools.Resources","Microsoft.VisualStudio.WebTools.MSBuild","Microsoft.VisualStudio.PackageGroup.Debugger.Script","Microsoft.VisualStudio.Debugger.Script.Msi","Microsoft.VisualStudio.Debugger.Script","Microsoft.VisualStudio.Debugger.Script","Microsoft.VisualStudio.Debugger.Script.Resources","Microsoft.VisualStudio.Debugger.Script.Resources","Microsoft.VisualStudio.VC.Ide.MDD","Microsoft.VisualStudio.Component.NuGet","Microsoft.CredentialProvider","Component.Microsoft.VisualStudio.LiveShare","Microsoft.VisualStudio.LiveShare","Microsoft.VisualStudio.Component.Debugger.JustInTime","Microsoft.VisualStudio.Debugger.ImmersiveActivateHelper.Msi","Microsoft.VisualStudio.Debugger.JustInTime","Microsoft.VisualStudio.Debugger.JustInTime.Msi","Microsoft.VisualStudio.Component.IntelliTrace.FrontEnd","Microsoft.IntelliTrace.DiagnosticsHubAgent.Targeted","Microsoft.IntelliTrace.Debugger","Microsoft.IntelliTrace.Debugger.Targeted","Microsoft.IntelliTrace.FrontEnd","Microsoft.DiagnosticsHub.Instrumentation","Microsoft.DiagnosticsHub.CpuSampling","Microsoft.DiagnosticsHub.CpuSampling.Targeted","Microsoft.PackageGroup.DiagnosticsHub.Platform","Microsoft.DiagnosticsHub.Collection.StopService.Uninstall","Microsoft.DiagnosticsHub.Runtime","Microsoft.DiagnosticsHub.Runtime.Targeted","Microsoft.DiagnosticsHub.Runtime.Resources","Microsoft.DiagnosticsHub.Collection","Microsoft.DiagnosticsHub.Collection.Service","Microsoft.DiagnosticsHub.Collection.StopService.Install","Microsoft.VisualStudio.Dsl.GraphObject","Microsoft.Net.4.TargetingPack","Microsoft.VisualStudio.VC.Ide.ResourceEditor","Microsoft.VisualStudio.VC.Ide.ResourceEditor.Resources","Microsoft.VisualStudio.NuGet.Licenses","Microsoft.WebTools.Shared","Microsoft.VisualStudio.WebToolsExtensions.DotNet.Core.ItemTemplates","Microsoft.VisualStudio.Component.TextTemplating","Microsoft.VisualStudio.TextTemplating.MSBuild","Microsoft.VisualStudio.TextTemplating.Integration","Microsoft.VisualStudio.TextTemplating.Core","Microsoft.VisualStudio.TextTemplating.Integration.Resources","Microsoft.VisualStudio.ProTools","sqlsysclrtypes","sqlsysclrtypes","SQLCommon","Microsoft.VisualStudio.ProTools.Resources","Microsoft.VisualStudio.NuGet.Core","Microsoft.VisualStudio.PackageGroup.TestTools.CodeCoverage","Microsoft.VisualStudio.PackageGroup.IntelliTrace.Core","Microsoft.IntelliTrace.Core","Microsoft.IntelliTrace.Core.Concord","Microsoft.IntelliTrace.Core.Targeted","Microsoft.IntelliTrace.ProfilerProxy.Msi.x64","Microsoft.IntelliTrace.ProfilerProxy.Msi","Microsoft.VisualStudio.TestTools.DynamicCodeCoverage","Microsoft.VisualStudio.TestTools.CodeCoverage.Msi","Microsoft.VisualStudio.TestTools.CodeCoverage","Microsoft.Icecap.Analysis","Microsoft.Icecap.Analysis.Targeted","Microsoft.Icecap.Analysis.Resources","Microsoft.Icecap.Analysis.Resources.Targeted","Microsoft.Icecap.Collection.Msi","Microsoft.Icecap.Collection.Msi.Targeted","Microsoft.Icecap.Collection.Msi.Resources","Microsoft.Icecap.Collection.Msi.Resources.Targeted","Microsoft.VisualStudio.PackageGroup.TestTools.Core","Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.V2.CLI","Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI","Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.V1.CLI","Microsoft.VisualStudio.TestTools.TestPlatform.V1.CLI","Microsoft.VisualStudio.TestTools.Pex.Common","Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.Legacy","Microsoft.VisualStudio.PackageGroup.MinShell.Interop","Microsoft.VisualStudio.TestTools.TP.Legacy.Tips.Msi","Microsoft.VisualStudio.TestTools.TP.Legacy.Tips.Common","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Tips","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Tips.Resources","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.TestTools","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Remote","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Professional","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Core.Premium","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Common","Microsoft.VisualStudio.TestTools.TP.Legacy.Common.Res","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Core","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Core.Resources","Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Agent","Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.IDE","Microsoft.VisualStudio.TestTools.TestWIExtension","Microsoft.VisualStudio.TestTools.TestPlatform.IDE","Microsoft.VisualStudio.PackageGroup.TestTools.DataCollectors","Microsoft.VisualStudio.TestTools.NE.Msi.Targeted","Microsoft.VisualStudio.TestTools.NetworkEmulation","Microsoft.VisualStudio.TestTools.DataCollectors","Microsoft.VisualCpp.CRT.ClickOnce.Msi","Microsoft.VisualStudio.WebTools.WSP.FSA","Microsoft.VisualStudio.WebTools.WSP.FSA.Resources","Microsoft.VisualStudio.Component.Static.Analysis.Tools","Microsoft.VisualCpp.Redist.14","Microsoft.VisualCpp.Redist.14","Microsoft.VisualStudio.StaticAnalysis","Microsoft.VisualStudio.StaticAnalysis.Resources","Microsoft.VisualStudio.PackageGroup.Community","Microsoft.VisualStudio.Community.Extra.Resources","Microsoft.VisualStudio.Community.Extra","Microsoft.VisualStudio.PackageGroup.Core","Microsoft.VisualStudio.CodeSense","Microsoft.VisualStudio.CodeSense.Community","Microsoft.VisualStudio.TestTools.TeamFoundationClient","Microsoft.VisualStudio.PackageGroup.Debugger.Core","Microsoft.VisualStudio.PackageGroup.Debugger.TimeTravel.Record","Microsoft.VisualStudio.Debugger.TimeTravel.Runtime","Microsoft.VisualStudio.Debugger.TimeTravel.Runtime","Microsoft.VisualStudio.Debugger.TimeTravel.Agent","Microsoft.VisualStudio.Debugger.TimeTravel.Record","Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost","Microsoft.VisualStudio.VC.Ide.Debugger","Microsoft.VisualStudio.VC.Ide.Debugger.Concord","Microsoft.VisualStudio.VC.Ide.Debugger.Concord.Resources","Microsoft.VisualStudio.VC.Ide.Debugger.Resources","Microsoft.VisualStudio.VC.Ide.Common","Microsoft.VisualStudio.VC.Ide.Common.Resources","Microsoft.VisualStudio.Debugger.Parallel","Microsoft.VisualStudio.Debugger.Parallel.Resources","Microsoft.VisualStudio.Debugger.CollectionAgents","Microsoft.VisualStudio.Debugger.Managed","Microsoft.VisualStudio.Debugger.Concord.Managed","Microsoft.VisualStudio.Debugger.Concord.Managed.Resources","Microsoft.VisualStudio.Debugger.Managed.Resources","Microsoft.VisualStudio.Debugger.Remote","Microsoft.VisualStudio.Debugger.Concord.Remote","Microsoft.VisualStudio.Debugger.Concord.Remote.Resources","Microsoft.VisualStudio.Debugger.Remote","Microsoft.VisualStudio.Debugger.Concord.Remote","Microsoft.VisualStudio.Debugger.Concord.Remote.Resources","Microsoft.VisualStudio.Debugger.Remote.Resources","Microsoft.VisualStudio.Debugger.Remote.Resources","Microsoft.VisualStudio.Debugger","Microsoft.VisualStudio.Debugger.Package.DiagHub.Client.VSx86","Microsoft.VisualStudio.Debugger.Remote.DiagHub.Client","Microsoft.VisualStudio.Debugger.Remote.DiagHub.Client","Microsoft.VisualStudio.VC.MSVCDis","Microsoft.VisualStudio.ScriptedHost","Microsoft.VisualStudio.ScriptedHost.Targeted","Microsoft.VisualStudio.ScriptedHost.Resources","Microsoft.IntelliTrace.DiagnosticsHub","Microsoft.VisualStudio.Debugger.Concord","Microsoft.VisualStudio.Debugger.Concord.Resources","Microsoft.VisualStudio.Debugger.Resources","Microsoft.PackageGroup.ClientDiagnostics","Microsoft.VisualStudio.AppResponsiveness","Microsoft.VisualStudio.AppResponsiveness.Targeted","Microsoft.VisualStudio.AppResponsiveness.Resources","Microsoft.VisualStudio.ClientDiagnostics","Microsoft.VisualStudio.ClientDiagnostics.Targeted","Microsoft.VisualStudio.ClientDiagnostics.Resources","Microsoft.VisualStudio.PackageGroup.ProfessionalCore","Microsoft.VisualStudio.Professional","Microsoft.VisualStudio.Professional.Msi","Microsoft.VisualStudio.PackageGroup.EnterpriseCore","Microsoft.VisualStudio.Enterprise.Msi","Microsoft.VisualStudio.Enterprise","Microsoft.ShDocVw","Microsoft.VisualStudio.PackageGroup.CommunityCore","Microsoft.VisualStudio.ProjectSystem.Full","Microsoft.VisualStudio.ProjectSystem","Microsoft.VisualStudio.Community.x86","Microsoft.VisualStudio.Community.x64","Microsoft.VisualStudio.Community","Microsoft.IntelliTrace.CollectorCab","Microsoft.VisualStudio.Community.Resources","Microsoft.VisualStudio.WebSiteProject.DTE","Microsoft.MSHtml","Microsoft.VisualStudio.Platform.CallHierarchy","Microsoft.VisualStudio.Community.Msi.Resources","Microsoft.VisualStudio.Community.Msi","Microsoft.VisualStudio.Devenv.Msi","Microsoft.VisualStudio.MinShell.Interop.Msi","Microsoft.VisualStudio.Editors","Microsoft.VisualStudio.Net.Eula.Resources","Microsoft.CodeAnalysis.ExpressionEvaluator","Microsoft.CodeAnalysis.VisualStudio.Setup","Microsoft.Component.MSBuild","Microsoft.NuGet.Build.Tasks","Microsoft.VisualStudio.Component.Roslyn.Compiler","Microsoft.CodeAnalysis.Compilers","Microsoft.VisualStudio.Workload.CoreEditor","Microsoft.VisualStudio.Component.CoreEditor","Microsoft.VisualStudio.PackageGroup.CoreEditor","Microsoft.VisualCpp.Tools.Common.UtilsPrereq","Microsoft.VisualCpp.Tools.Common.Utils","Microsoft.VisualCpp.Tools.Common.Utils.Resources","Microsoft.VisualStudio.PackageGroup.VsDevCmd","Microsoft.VisualStudio.VsDevCmd.Ext.NetFxSdk","Microsoft.VisualStudio.VsDevCmd.Core.WinSdk","Microsoft.VisualStudio.VsDevCmd.Core.DotNet","Microsoft.VisualStudio.VC.DevCmd","Microsoft.VisualStudio.VC.DevCmd.Resources","Microsoft.VisualStudio.VirtualTree","Microsoft.VisualStudio.PackageGroup.Progression","Microsoft.VisualStudio.PerformanceProvider","Microsoft.VisualStudio.GraphModel","Microsoft.VisualStudio.GraphProvider","Microsoft.DiaSymReader","Microsoft.Build.Dependencies","Microsoft.Build.FileTracker.Msi","Microsoft.Build","Microsoft.VisualStudio.TextMateGrammars","Microsoft.VisualStudio.PackageGroup.TeamExplorer.Common","Microsoft.VisualStudio.TeamExplorer","Microsoft.ServiceHub","Microsoft.VisualStudio.ProjectServices","Microsoft.VisualStudio.SLNX.VSIX","Microsoft.VisualStudio.FileHandler.Msi","Microsoft.VisualStudio.FileHandler.Msi","Microsoft.VisualStudio.PackageGroup.MinShell","Microsoft.VisualStudio.MinShell.Interop","Microsoft.VisualStudio.Log","Microsoft.VisualStudio.Log.Targeted","Microsoft.VisualStudio.Log.Resources","Microsoft.VisualStudio.Finalizer","Microsoft.VisualStudio.Devenv","Microsoft.VisualStudio.Devenv.Resources","Microsoft.VisualStudio.CoreEditor","Microsoft.VisualStudio.Platform.NavigateTo","Microsoft.VisualStudio.Connected","Microsoft.VisualStudio.Connected.Resources","Microsoft.VisualStudio.MinShell","Microsoft.VisualStudio.Setup.Configuration","Microsoft.VisualStudio.Platform.Search","Microsoft.VisualStudio.MinShell.Platform","Microsoft.VisualStudio.MinShell.Platform.Resources","Microsoft.VisualStudio.MefHosting","Microsoft.VisualStudio.MefHosting.Resources","Microsoft.VisualStudio.Initializer","Microsoft.VisualStudio.ExtensionManager","Microsoft.VisualStudio.Platform.Editor","Microsoft.VisualStudio.MinShell.x86","Microsoft.VisualStudio.NativeImageSupport","Microsoft.VisualStudio.MinShell.Msi","Microsoft.VisualStudio.MinShell.Msi.Resources","Microsoft.VisualStudio.LanguageServer","Microsoft.VisualStudio.Devenv.Config","Microsoft.VisualStudio.MinShell.Resources","Microsoft.Net.PackageGroup.4.7.2.Redist","Microsoft.VisualStudio.Branding.Enterprise"]}]
diff --git a/test/fixtures/VS_2022_BuildTools_arm64_only.txt b/test/fixtures/VS_2022_BuildTools_arm64_only.txt
new file mode 100644
index 0000000000..39e4590f34
--- /dev/null
+++ b/test/fixtures/VS_2022_BuildTools_arm64_only.txt
@@ -0,0 +1,423 @@
+[
+ {
+ "path": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools",
+ "version": "17.11.35222.181",
+ "packages": [
+ "Microsoft.VisualStudio.Component.VC.Tools.ARM64",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.ARM64.v143",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.ARM64",
+ "Microsoft.VS.VC.vcvars.arm64.Shortcuts",
+ "Microsoft.VisualCpp.CA.Ext.Hostx64.TargetARM64",
+ "Microsoft.VC.14.41.17.11.CA.Ext.Hostx64.TargetARM64.base",
+ "Microsoft.VC.14.41.17.11.CA.Ext.Hostx64.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.Hostx86.TargetARM64",
+ "Microsoft.VC.14.41.17.11.CA.Ext.Hostx86.TargetARM64.base",
+ "Microsoft.VC.14.41.17.11.CA.Ext.Hostx86.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.HostARM64.TargetARM64",
+ "Microsoft.VC.14.41.17.11.CA.Ext.HostARM64.TargetARM64.base",
+ "Microsoft.VC.14.41.17.11.CA.Ext.HostARM64.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.Tools.Hostx86.Targetarm64",
+ "Microsoft.VC.14.41.17.11.Tools.Hostx86.Targetarm64.base",
+ "Microsoft.VC.14.41.17.11.Tools.HostX86.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.Tools.HostARM64.TargetARM64",
+ "Microsoft.VC.14.41.17.11.Tools.HostARM64.TargetARM64.base",
+ "Microsoft.VC.14.41.17.11.Tools.HostARM64.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.CRT.Redist.ARM64.OneCore.Desktop",
+ "Microsoft.VC.14.40.17.10.CRT.Redist.ARM64.OneCore.Desktop.base",
+ "Microsoft.VisualCpp.CRT.Redist.ARM64",
+ "Microsoft.VC.14.40.17.10.CRT.Redist.ARM64.base",
+ "Microsoft.VisualCpp.CRT.ARM64.OneCore.Desktop",
+ "Microsoft.VC.14.41.17.11.CRT.ARM64.OneCore.Desktop.base",
+ "Microsoft.VC.14.41.17.11.CRT.ARM64.OneCore.Desktop.debug.base",
+ "Microsoft.VisualCpp.CRT.ARM64.Store",
+ "Microsoft.VC.14.41.17.11.CRT.ARM64.Store.base",
+ "Microsoft.VisualCpp.CRT.ARM64.Desktop",
+ "Microsoft.VC.14.41.17.11.CRT.ARM64.Desktop.base",
+ "Microsoft.VC.14.41.17.11.CRT.ARM64.Desktop.debug.base",
+ "Microsoft.VisualStudio.PackageGroup.VC.Tools.x64.ARM64",
+ "Microsoft.VisualCpp.Tools.Core",
+ "Microsoft.VisualCpp.PGO.ARM64",
+ "Microsoft.VC.14.41.17.11.PGO.ARM64.base",
+ "Microsoft.VisualCpp.Premium.Tools.Hostx86.Targetarm64",
+ "Microsoft.VC.14.41.17.11.Premium.Tools.Hostx86.Targetarm64.base",
+ "Microsoft.VC.14.41.17.11.Prem.HostX86.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX64.TargetARM64",
+ "Microsoft.VC.14.41.17.11.Premium.Tools.HostX64.TargetARM64.base",
+ "Microsoft.VC.14.41.17.11.Prem.HostX64.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.ARM64.Base",
+ "Microsoft.VC.14.41.17.11.Premium.Tools.ARM64.Base.base",
+ "Microsoft.VisualCpp.Tools.HostX64.TargetARM64",
+ "Microsoft.VC.14.41.17.11.Tools.HostX64.TargetARM64.base",
+ "Microsoft.VC.14.41.17.11.Tools.HostX64.TargetARM64.Res.base",
+ "Microsoft.VC.14.41.17.11.Props.ARM64",
+ "Microsoft.VisualStudio.TestTools.TestWIExtension",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.IDE",
+ "Microsoft.VisualStudio.VC.Ide.Pro",
+ "Microsoft.VisualStudio.VC.Ide.Pro.Resources",
+ "Microsoft.VisualStudio.VC.Templates.General",
+ "Microsoft.VisualStudio.VC.Templates.General.Resources",
+ "Microsoft.VisualStudio.VC.Items.Pro",
+ "Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Reduced",
+ "Microsoft.VisualStudio.VC.Ide.MDD",
+ "Microsoft.VisualStudio.PackageGroup.Core",
+ "Microsoft.VisualStudio.CodeSense.Community",
+ "Microsoft.VisualStudio.TestTools.TeamFoundationClient",
+ "Microsoft.PackageGroup.ClientDiagnostics",
+ "Microsoft.VisualStudio.AppResponsiveness",
+ "Microsoft.VisualStudio.AppResponsiveness.Targeted",
+ "Microsoft.VisualStudio.AppResponsiveness.Resources",
+ "Microsoft.VisualStudio.ClientDiagnostics",
+ "Microsoft.VisualStudio.ClientDiagnostics.Targeted",
+ "Microsoft.VisualStudio.Component.VC.Llvm.Clang",
+ "Microsoft.VisualStudio.VC.Llvm.Clang",
+ "Microsoft.VisualStudio.ClientDiagnostics.Resources",
+ "Microsoft.VisualStudio.PackageGroup.CommunityCore",
+ "Microsoft.VisualStudio.ProjectSystem.Full",
+ "Microsoft.VisualStudio.Product.BuildTools",
+ "Microsoft.VisualStudio.VC.Ide.Linux.ConnectionManager",
+ "Microsoft.VisualStudio.VC.Ide.Linux.ConnectionManager.Resources",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.Core",
+ "Microsoft.VC.14.41.17.11.Props.x86",
+ "Microsoft.VC.14.41.17.11.Props",
+ "Microsoft.VisualCpp.RuntimeDebug.14",
+ "Microsoft.VisualCpp.RuntimeDebug.14.ARM64",
+ "Microsoft.VC.14.41.17.11.Tools.HostX86.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Tools.Core.Resources",
+ "Microsoft.VisualCpp.Tools.Core.x86",
+ "Microsoft.VC.14.41.17.11.Tools.Core.Props",
+ "Microsoft.VisualCpp.Tools.Common.Utils",
+ "Microsoft.VisualCpp.Tools.Common.Utils.Resources",
+ "Microsoft.VisualCpp.DIA.SDK",
+ "Microsoft.VisualCpp.Servicing.DIASDK",
+ "Microsoft.VisualCpp.CRT.x86.Desktop",
+ "Microsoft.VisualStudio.Workload.VCTools",
+ "Microsoft.VC.14.41.17.11.CRT.x64.Desktop.base",
+ "Microsoft.VisualCpp.CRT.Source",
+ "Microsoft.VC.14.41.17.11.CRT.Source.base",
+ "Microsoft.VisualCpp.CRT.Headers",
+ "Microsoft.VC.14.41.17.11.CRT.Headers.Resources",
+ "Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset",
+ "Microsoft.VisualCpp.CRT.Redist.Resources",
+ "Microsoft.VC.14.41.17.11.CRT.Headers.Resources.base",
+ "Microsoft.VisualStudio.VC.MSBuild.Llvm",
+ "Microsoft.VisualStudio.VC.MSBuild.Llvm.Resources",
+ "Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.Native",
+ "Microsoft.VisualStudio.VC.Templates.UnitTest",
+ "Microsoft.VisualStudio.VC.Templates.UnitTest.Resources",
+ "Microsoft.VisualCpp.ATL.ARM64",
+ "Microsoft.VC.14.41.17.11.ATL.ARM64.base",
+ "Microsoft.VisualStudio.Community.VB.Targeted",
+ "Microsoft.VisualStudio.Community.VB.Neutral",
+ "Microsoft.VisualStudio.Community.CSharp.Targeted",
+ "Microsoft.VisualStudio.Community.CSharp.Neutral",
+ "Microsoft.VisualStudio.Community.ProductArch.TargetedExtra",
+ "Microsoft.VisualStudio.VC.MSBuild.Base",
+ "Microsoft.VisualStudio.VC.MSBuild.Base.Resources",
+ "Microsoft.VisualStudio.VC.Templates.Desktop",
+ "Microsoft.VisualStudio.Component.VC.CoreIde",
+ "Microsoft.VisualCpp.ATL.ARM64.Spectre",
+ "Microsoft.VisualStudio.Component.VC.ATL.ARM64",
+ "Microsoft.VisualCpp.CRT.x86.Store",
+ "Microsoft.VC.14.41.17.11.ATL.ARM64.Spectre.base",
+ "Microsoft.VC.14.41.17.11.CRT.x86.Store.base",
+ "Microsoft.VC.14.41.17.11.Props.ARM64.Spectre",
+ "Microsoft.VisualStudio.Component.VC.ATL.ARM64.Spectre",
+ "Microsoft.VisualStudio.Community.x64",
+ "Microsoft.VisualStudio.Community.ProductArch.Targeted",
+ "Microsoft.VisualStudio.Community.ProductArch.NeutralExtra",
+ "Microsoft.IntelliTrace.CollectorCab",
+ "Microsoft.VisualStudio.Community.VB.Resources.Targeted",
+ "Microsoft.VisualStudio.Community.VB.Resources.Neutral",
+ "Microsoft.VisualStudio.Community.CSharp.Resources.Targeted",
+ "Microsoft.VisualStudio.Community.CSharp.Resources.Neutral",
+ "Microsoft.VisualStudio.Community.ProductArch.Resources.Targeted",
+ "Microsoft.VisualStudio.Community.ProductArch.Resources.NeutralExtra",
+ "Microsoft.VisualStudio.Community.ProductArch.Resources.Neutral",
+ "Microsoft.VisualStudio.WebSiteProject.DTE",
+ "Microsoft.VisualStudio.Diagnostics.AspNetHelper",
+ "Microsoft.MSHtml",
+ "Microsoft.VisualStudio.Platform.CallHierarchy",
+ "Microsoft.VisualStudio.Community.ProductArch.Neutral",
+ "Microsoft.VisualStudio.Community.Msi.Resources",
+ "Microsoft.VisualStudio.Community.Msi",
+ "Microsoft.VisualStudio.Community.Shared.Msi",
+ "Microsoft.VisualStudio.MinShell.Interop.Msi",
+ "Microsoft.VisualStudio.MinShell.Interop.Shared.Msi",
+ "Microsoft.VisualStudio.PackageGroup.CoreEditor",
+ "Microsoft.VisualStudio.ScriptedHost",
+ "Microsoft.VisualStudio.ScriptedHost.Targeted",
+ "Microsoft.VisualStudio.VirtualTree",
+ "Microsoft.VisualStudio.PackageGroup.Progression",
+ "Microsoft.VisualStudio.PerformanceProvider",
+ "Microsoft.VisualStudio.GraphModel",
+ "Microsoft.VisualStudio.GraphProvider",
+ "Microsoft.VisualStudio.TextMateGrammars",
+ "Microsoft.VisualStudio.Platform.Markdown",
+ "Microsoft.VisualStudio.PackageGroup.TeamExplorer.Common",
+ "Microsoft.VisualStudio.PackageGroup.ServiceHub",
+ "Microsoft.ServiceHub.Node",
+ "Microsoft.ServiceHub.Managed",
+ "Microsoft.VisualStudio.OpenFolder.VSIX",
+ "Microsoft.VisualStudio.FileHandler.Msi",
+ "Microsoft.VisualStudio.FileHandler.Msi",
+ "Microsoft.VisualStudio.PackageGroup.MinShell",
+ "Microsoft.VisualStudio.MinShell.Msi",
+ "Microsoft.VisualStudio.MinShell.Shared.Msi",
+ "Microsoft.VisualStudio.MinShell.Msi.Resources",
+ "Microsoft.VisualStudio.MinShell.Interop",
+ "Microsoft.VisualStudio.Log",
+ "Microsoft.VisualStudio.Log.Targeted",
+ "Microsoft.VisualStudio.Log.Resources",
+ "Microsoft.VisualStudio.Finalizer",
+ "Microsoft.VisualStudio.CoreEditor",
+ "Microsoft.VisualStudio.Platform.NavigateTo",
+ "Microsoft.VisualStudio.Connected",
+ "Microsoft.VisualStudio.Identity",
+ "Microsoft.Developer.IdentityServiceGS",
+ "SQLitePCLRaw",
+ "SQLitePCLRaw.Targeted",
+ "Microsoft.VisualStudio.Connected.Auto",
+ "Microsoft.VisualStudio.Connected.Auto.Resources",
+ "Microsoft.VisualStudio.Connected.Resources",
+ "Microsoft.VisualStudio.VC.Ide.x64",
+ "Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Express",
+ "Microsoft.VisualStudio.PackageGroup.Debugger.Script",
+ "Microsoft.VisualStudio.Debugger.Script",
+ "Microsoft.VisualStudio.Debugger.Script.Resources",
+ "Microsoft.VisualStudio.Debugger.Script.Remote",
+ "Microsoft.WebView2",
+ "Microsoft.VisualStudio.Debugger.Script.Remote",
+ "Microsoft.VisualStudio.Debugger.Script.Remote.Resources",
+ "Microsoft.VisualStudio.Debugger.Script.Remote.Resources",
+ "Microsoft.VisualStudio.VC.Ide.WinXPlus",
+ "Microsoft.VisualStudio.VC.Ide.Dskx",
+ "Microsoft.VisualStudio.VC.Ide.Dskx.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Base",
+ "Microsoft.VisualStudio.VC.Ide.LanguageService",
+ "Microsoft.VisualStudio.VC.Copilot.Setup",
+ "Microsoft.VisualStudio.VC.Ide.VCPkgDatabase",
+ "Microsoft.VisualStudio.VC.Ide.ResourceEditor",
+ "Microsoft.VisualStudio.VC.Ide.ResourceEditor.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Core",
+ "Microsoft.VisualStudio.VisualC.Utilities",
+ "Microsoft.VisualStudio.VisualC.Utilities.Resources",
+ "Microsoft.VisualStudio.VC.Ide.ProjectSystem",
+ "Microsoft.VisualStudio.VC.Ide.ProjectSystem.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine",
+ "Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine.Resources",
+ "Microsoft.VisualStudio.VC.Ide.LanguageService.Resources",
+ "Microsoft.VisualStudio.VC.Llvm.Base",
+ "CoreEditorFonts",
+ "Microsoft.VisualStudio.VC.Ide.Base.Resources",
+ "Microsoft.VisualStudio.Component.TextTemplating",
+ "Microsoft.VisualStudio.PackageGroup.Debugger.Core",
+ "Microsoft.VisualStudio.Debugger.BrokeredServices",
+ "Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost",
+ "Microsoft.VisualStudio.Debugger.AzureAttach",
+ "Microsoft.VisualStudio.Web.Azure.Common",
+ "Microsoft.WebTools.Shared",
+ "Microsoft.WebTools.DotNet.Core.ItemTemplates",
+ "Microsoft.VisualStudio.PackageGroup.Debugger.TimeTravel.Replay",
+ "Microsoft.VisualStudio.VC.Ide.Debugger",
+ "Microsoft.VisualStudio.VC.Ide.Debugger.Concord",
+ "Microsoft.VisualStudio.VC.Ide.Debugger.Concord.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Debugger.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Common",
+ "Microsoft.VisualStudio.VC.Ide.Common.Resources",
+ "Microsoft.VisualStudio.Debugger.CollectionAgents",
+ "Microsoft.VisualStudio.Debugger.Parallel",
+ "Microsoft.VisualStudio.Debugger.Parallel.Resources",
+ "Microsoft.VisualStudio.Debugger.Managed",
+ "Microsoft.DiaSymReader",
+ "Microsoft.CodeAnalysis.ExpressionEvaluator",
+ "Microsoft.VisualStudio.Debugger.Concord.Managed",
+ "Microsoft.VisualStudio.Debugger.Concord.Managed.Resources",
+ "Microsoft.VisualStudio.Debugger.Managed.Resources",
+ "Microsoft.VisualStudio.Debugger.TargetComposition",
+ "Microsoft.VisualStudio.Debugger.TargetComposition.Remote.arm64",
+ "Microsoft.VisualStudio.Debugger.TargetComposition.Remote",
+ "Microsoft.VisualStudio.Debugger.TargetComposition.Remote",
+ "Microsoft.VisualStudio.Debugger.Remote",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.Resources",
+ "Microsoft.VisualStudio.VC.Ide.LanguageService.Dependencies",
+ "Microsoft.VisualStudio.Debugger.Remote",
+ "Microsoft.VisualStudio.Debugger.Remote.ARM64",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.ARM64",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.Resources.ARM64",
+ "Microsoft.VisualStudio.Debugger.Remote.ARM",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.ARM",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.Resources.ARM",
+ "Microsoft.VisualStudio.Debugger.Remote.Resources.ARM",
+ "Microsoft.VisualStudio.Debugger.Remote.Resources.ARM64",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.Resources",
+ "Microsoft.VisualStudio.Debugger.Remote.Resources",
+ "Microsoft.VisualStudio.Debugger.Remote.Resources",
+ "Microsoft.VisualStudio.Debugger",
+ "Microsoft.VisualStudio.AzureSDK",
+ "Microsoft.VisualStudio.Editors",
+ "Microsoft.VisualStudio.VC.MSVCDis",
+ "Microsoft.IntelliTrace.DiagnosticsHub",
+ "Microsoft.VisualStudio.MinShell",
+ "Microsoft.VisualStudio.Copilot.Contracts",
+ "Microsoft.VisualStudio.Licensing",
+ "Microsoft.VisualStudio.IdentityDependencies",
+ "Microsoft.VisualStudio.GitHubProtocolHandler.Msi",
+ "Microsoft.VisualStudio.VsWebProtocolSelector.Msi",
+ "Microsoft.VisualStudio.Extensibility.Container",
+ "Microsoft.VisualStudio.LanguageServer",
+ "Microsoft.VisualStudio.MefHosting",
+ "Microsoft.VisualStudio.Initializer",
+ "Microsoft.VisualStudio.ExtensionManager",
+ "Microsoft.VisualStudio.ExtensionManager.Auto",
+ "Microsoft.VisualStudio.Platform.Editor",
+ "Microsoft.VisualStudio.MinShell.Targeted",
+ "Microsoft.VisualStudio.Devenv.Config",
+ "Microsoft.VisualStudio.MinShell.Resources",
+ "Microsoft.VisualStudio.UIInternal.Guide",
+ "Microsoft.VisualStudio.UIInternal",
+ "Microsoft.VisualStudio.UIInternal.Resources",
+ "Microsoft.VisualStudio.CoreDotNet",
+ "Microsoft.VisualStudio.MinShell.Auto",
+ "Microsoft.VisualStudio.MinShell.Auto.Resources",
+ "Microsoft.VisualStudio.Debugger.Concord",
+ "Microsoft.VisualStudio.Debugger.Concord.Resources",
+ "Microsoft.VisualStudio.Debugger.Resources",
+ "Microsoft.DiaSymReader.PortablePdb",
+ "Microsoft.VisualStudio.PerfLib",
+ "Microsoft.VisualStudio.Debugger.Package.DiagHub.Client",
+ "Microsoft.VisualStudio.Debugger.Remote.DiagnosticsHub.Client",
+ "Microsoft.VisualStudio.Debugger.Remote.DiagnosticsHub.Client",
+ "Microsoft.VisualStudio.Debugger.Remote.DiagnosticsHub.Client",
+ "Microsoft.VisualStudio.TextTemplating.MSBuild",
+ "Microsoft.VisualStudio.TextTemplating.Integration",
+ "Microsoft.VisualStudio.TextTemplating.Core",
+ "Microsoft.VisualStudio.PackageGroup.Roslyn.LanguageServices",
+ "Microsoft.CodeAnalysis.VisualStudio.Setup",
+ "Microsoft.VisualStudio.TextTemplating.Integration.Resources",
+ "Microsoft.VC.14.41.17.11.Props.IFC",
+ "Microsoft.VisualStudio.Component.VC.ASAN",
+ "Microsoft.VisualCpp.ASAN.X86",
+ "Microsoft.VC.14.41.17.11.ASAN.X86.base",
+ "Microsoft.VC.14.41.17.11.ASAN.X64.base",
+ "Microsoft.VC.14.41.17.11.ASAN.Headers.base",
+ "Microsoft.VC.14.41.17.11.Props.ATLMFC",
+ "Microsoft.VisualCpp.ATL.Source",
+ "Microsoft.VC.14.41.17.11.ATL.Source.base",
+ "Microsoft.VisualCpp.ATL.Headers",
+ "Microsoft.VC.14.41.17.11.ATL.Headers.base",
+ "Microsoft.VC.14.41.17.11.Servicing.ATL",
+ "Microsoft.VisualStudio.Component.TestTools.BuildTools",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.BuildTools",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.CodeCoverage",
+ "Microsoft.VisualStudio.TestTools.DynamicCodeCoverage",
+ "Microsoft.CodeCoverage.Console.Targeted",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.V1.CLI",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.V2.CLI",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI",
+ "Microsoft.VisualStudio.Component.Windows11SDK.22621",
+ "Microsoft.VisualStudio.VC.UnitTest.Desktop.Build.Core",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.V1.CPP",
+ "Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
+ "Microsoft.VC.14.41.17.11.CA.Rulesets.base",
+ "Microsoft.VC.14.41.17.11.Servicing.CARulesets",
+ "Microsoft.VC.14.41.17.11.Servicing.CAExtensions",
+ "Microsoft.VC.14.41.17.11.Tools.HostX64.TargetX86.base",
+ "Microsoft.VC.14.41.17.11.Tools.HostX64.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Tools.HostX64.TargetX64",
+ "Microsoft.VC.14.41.17.11.Tools.HostX64.TargetX64.base",
+ "Microsoft.VC.14.41.17.11.Tools.HostX64.TargetX64.Res.base",
+ "Microsoft.VisualCpp.Tools.HostARM64.TargetX86",
+ "Microsoft.VC.14.41.17.11.Tools.HostARM64.TargetX86.base",
+ "Microsoft.VC.14.41.17.11.Tools.HostARM64.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX86",
+ "Microsoft.VC.14.41.17.11.Premium.Tools.HostX86.TargetX86.base",
+ "Microsoft.VisualStudio.Component.VC.Modules.ARM64",
+ "Microsoft.VC.14.41.17.11.Prem.HostX86.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX64",
+ "Microsoft.VC.14.41.17.11.Premium.Tools.HostX64.TargetX64.base",
+ "Microsoft.VC.14.41.17.11.Prem.HostX64.TargetX64.Res.base",
+ "Microsoft.Build.Arm64",
+ "Microsoft.Build.UnGAC",
+ "Microsoft.VisualStudio.VC.Icons",
+ "Microsoft.VisualCpp.CRT.x86.OneCore.Desktop",
+ "Microsoft.VC.14.41.17.11.CRT.x86.OneCore.Desktop.base",
+ "Microsoft.VisualCpp.CRT.x64.Store",
+ "Microsoft.VC.14.41.17.11.CRT.x64.Store.base",
+ "Microsoft.VisualStudio.InstrumentationEngine.ARM64",
+ "Microsoft.VisualStudio.InstrumentationEngine",
+ "Microsoft.VisualCpp.CRT.x64.OneCore.Desktop",
+ "Microsoft.VC.14.41.17.11.CRT.x64.OneCore.Desktop.base",
+ "Microsoft.VC.14.41.17.11.Tools.HostX86.TargetX64.base",
+ "Microsoft.VC.14.41.17.11.Props.x64",
+ "Microsoft.VC.14.41.17.11.Tools.Hostx86.Targetx64.Res.base",
+ "Win11SDK_10.0.22621",
+ "Microsoft.VisualCpp.Tools.HostX86.TargetX86",
+ "Microsoft.VC.14.41.17.11.Tools.HostX86.TargetX86.base",
+ "Microsoft.VC.14.41.17.11.Servicing.Compilers",
+ "Microsoft.VisualCpp.Redist.14.Latest",
+ "Microsoft.VisualCpp.Redist.14.Latest",
+ "Microsoft.VisualStudio.LiveShareApi",
+ "Microsoft.VisualStudio.ProjectSystem.Query",
+ "Microsoft.VisualStudio.Component.VC.Tools.ARM64EC",
+ "Microsoft.VisualCpp.CRT.ARM64EC.Store",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.ARM64EC.v143",
+ "Microsoft.VC.14.41.17.11.CRT.ARM64EC.Store.base",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.ARM64EC",
+ "Microsoft.VC.14.41.17.11.CRT.x86.Desktop.base",
+ "Microsoft.VisualCpp.CRT.x64.Desktop",
+ "Microsoft.VisualStudio.ProjectSystem",
+ "Microsoft.VisualStudio.Community.x86",
+ "Microsoft.VisualCpp.IFC.arm64",
+ "Microsoft.VisualCpp.Redist.14",
+ "Microsoft.VisualCpp.Redist.14",
+ "Microsoft.VisualCpp.Servicing.Redist",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.IDE",
+ "Microsoft.VC.14.41.17.11.CRT.Headers.base",
+ "Microsoft.VC.14.41.17.11.Servicing.CrtHeaders",
+ "Microsoft.VC.14.41.17.11.Servicing",
+ "Microsoft.VisualStudio.Component.VC.CoreBuildTools",
+ "Microsoft.VisualStudio.VC.vcvars",
+ "Microsoft.VS.VC.vcvars.x86.Shortcuts",
+ "Microsoft.Windows.UniversalCRT.Redistributable.Msi",
+ "Microsoft.VS.VC.vcvars.x64.Shortcuts",
+ "Microsoft.VS.VC.vcvars.arm64_x64.Shortcuts",
+ "Microsoft.VisualStudio.Component.Windows10SDK",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.x86.v143",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.X86",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.X64.v143",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.X64",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.ARM.v143",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.ARM",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.Base",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.Base.Resources",
+ "Microsoft.VisualStudio.Workload.MSBuildTools",
+ "Microsoft.VisualStudio.Component.CoreBuildTools",
+ "Microsoft.VisualStudio.Setup.Configuration",
+ "Microsoft.VisualStudio.PackageGroup.Setup.Common",
+ "Microsoft.VisualStudio.Setup.WMIProvider",
+ "Microsoft.VisualStudio.Setup.Configuration.Interop",
+ "Microsoft.VisualStudio.PackageGroup.VsDevCmd",
+ "Microsoft.VisualStudio.VsDevCmd.Ext.NetFxSdk",
+ "Microsoft.VisualStudio.VsDevCmd.Core.WinSdk",
+ "Microsoft.VisualStudio.VsDevCmd.Core.DotNet",
+ "Microsoft.VisualStudio.VC.DevCmd",
+ "Microsoft.VisualStudio.VC.DevCmd.Resources",
+ "Microsoft.VisualStudio.BuildTools.Resources",
+ "Microsoft.VisualStudio.Net.Eula.Resources",
+ "Microsoft.Build.Dependencies",
+ "Microsoft.NuGet.Build.Tasks.Setup",
+ "Microsoft.Build.FileTracker.Msi",
+ "Microsoft.Component.MSBuild",
+ "Microsoft.PythonTools.BuildCore.Vsix",
+ "Microsoft.VisualStudio.Component.Roslyn.Compiler",
+ "Microsoft.CodeAnalysis.Compilers",
+ "Microsoft.VisualStudio.NativeImageSupport",
+ "Microsoft.Build",
+ "Microsoft.VisualStudio.PackageGroup.NuGet",
+ "Microsoft.VisualStudio.NuGet.BuildTools"
+ ]
+ }
+]
diff --git a/test/fixtures/VS_2022_Community_workload.txt b/test/fixtures/VS_2022_Community_workload.txt
new file mode 100644
index 0000000000..7cd20f8598
--- /dev/null
+++ b/test/fixtures/VS_2022_Community_workload.txt
@@ -0,0 +1,569 @@
+[
+ {
+ "path": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community",
+ "version": "17.4.33213.308",
+ "packages": [
+ "Microsoft.VisualStudio.Product.Community",
+ "Microsoft.VisualStudio.PackageGroup.LiveShare.VSCore",
+ "Microsoft.VisualStudio.LiveShare.VSCore",
+ "Microsoft.VisualStudio.Workload.NativeDesktop",
+ "Microsoft.VisualStudio.Component.VC.ASAN",
+ "Microsoft.VisualCpp.ASAN.X86",
+ "Microsoft.VC.14.34.17.4.ASAN.X86.base",
+ "Microsoft.VC.14.34.17.4.ASAN.X64.base",
+ "Microsoft.VC.14.34.17.4.ASAN.Headers.base",
+ "Microsoft.VisualStudio.VC.IDE.Project.Factories",
+ "Microsoft.VisualStudio.Component.VC.TestAdapterForGoogleTest",
+ "Microsoft.VisualStudio.VC.Ide.TestAdapterForGoogleTest",
+ "Microsoft.VisualStudio.Component.VC.TestAdapterForBoostTest",
+ "Microsoft.VisualStudio.VC.Ide.TestAdapterForBoostTest",
+ "Microsoft.VisualStudio.Component.VC.CMake.Project",
+ "Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions.CMake",
+ "Microsoft.VisualStudio.VC.CMake",
+ "Microsoft.VisualStudio.VC.CMake.Project",
+ "Microsoft.VisualStudio.VC.CMake.Client",
+ "Microsoft.VisualStudio.VC.ExternalBuildFramework",
+ "Microsoft.VisualStudio.Component.VC.DiagnosticTools",
+ "Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.Native",
+ "Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
+ "Microsoft.VisualStudio.VC.Templates.UnitTest",
+ "Microsoft.VisualStudio.VC.UnitTest.Desktop.Build.Core",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.V1.CPP",
+ "Microsoft.VisualStudio.VC.Templates.UnitTest.Resources",
+ "Microsoft.VisualStudio.VC.Templates.Desktop",
+ "Microsoft.VisualStudio.Component.Graphics",
+ "Microsoft.VisualStudio.Graphics.Viewers",
+ "Microsoft.VisualStudio.Graphics.Viewers.Resources",
+ "Microsoft.VisualStudio.Component.VC.ATL.ARM64",
+ "Microsoft.VisualCpp.ATL.ARM64",
+ "Microsoft.VC.14.34.17.4.ATL.ARM64.base",
+ "Microsoft.VisualStudio.Component.VC.ATL",
+ "Microsoft.VisualStudio.VC.Ide.ATL",
+ "Microsoft.VisualStudio.VC.Ide.ATL.Resources",
+ "Microsoft.VisualCpp.ATL.X86",
+ "Microsoft.VC.14.34.17.4.ATL.X86.base",
+ "Microsoft.VisualCpp.ATL.X64",
+ "Microsoft.VC.14.34.17.4.ATL.X64.base",
+ "Microsoft.VC.14.34.17.4.Props.ATLMFC",
+ "Microsoft.VisualCpp.ATL.Source",
+ "Microsoft.VC.14.34.17.4.ATL.Source.base",
+ "Microsoft.VisualCpp.ATL.Headers",
+ "Microsoft.VC.14.34.17.4.ATL.Headers.base",
+ "Microsoft.VC.14.34.17.4.Servicing.ATL",
+ "Microsoft.VisualStudio.Component.VC.Tools.ARM64",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.ARM64.v143",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.ARM64",
+ "Microsoft.VS.VC.vcvars.arm64.Shortcuts",
+ "Microsoft.VisualCpp.CA.Ext.Hostx64.TargetARM64",
+ "Microsoft.VC.14.34.17.4.CA.Ext.Hostx64.TargetARM64.base",
+ "Microsoft.VC.14.34.17.4.CA.Ext.Hostx64.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.Hostx86.TargetARM64",
+ "Microsoft.VC.14.34.17.4.CA.Ext.Hostx86.TargetARM64.base",
+ "Microsoft.VC.14.34.17.4.CA.Ext.Hostx86.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.HostARM64.TargetARM64",
+ "Microsoft.VC.14.34.17.4.CA.Ext.HostARM64.TargetARM64.base",
+ "Microsoft.VC.14.34.17.4.CA.Ext.HostARM64.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.Tools.Hostx86.Targetarm64",
+ "Microsoft.VC.14.34.17.4.Tools.Hostx86.Targetarm64.base",
+ "Microsoft.VC.14.34.17.4.Tools.HostX86.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.Tools.HostARM64.TargetARM64",
+ "Microsoft.VC.14.34.17.4.Tools.HostARM64.TargetARM64.base",
+ "Microsoft.VC.14.34.17.4.Tools.HostARM64.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.CRT.Redist.ARM64.OneCore.Desktop",
+ "Microsoft.VC.14.34.17.4.CRT.Redist.ARM64.OneCore.Desktop.base",
+ "Microsoft.VisualCpp.CRT.Redist.ARM64",
+ "Microsoft.VC.14.34.17.4.CRT.Redist.ARM64.base",
+ "Microsoft.VisualCpp.CRT.ARM64.OneCore.Desktop",
+ "Microsoft.VC.14.34.17.4.CRT.ARM64.OneCore.Desktop.base",
+ "Microsoft.VC.14.34.17.4.CRT.ARM64.OneCore.Desktop.debug.base",
+ "Microsoft.VisualCpp.CRT.ARM64.Store",
+ "Microsoft.VC.14.34.17.4.CRT.ARM64.Store.base",
+ "Microsoft.VisualCpp.CRT.ARM64.Desktop",
+ "Microsoft.VC.14.34.17.4.CRT.ARM64.Desktop.base",
+ "Microsoft.VC.14.34.17.4.CRT.ARM64.Desktop.debug.base",
+ "Microsoft.VisualStudio.PackageGroup.VC.Tools.x64.ARM64",
+ "Microsoft.VisualCpp.Tools.Core",
+ "Microsoft.VisualCpp.PGO.ARM64",
+ "Microsoft.VC.14.34.17.4.PGO.ARM64.base",
+ "Microsoft.VisualCpp.Premium.Tools.Hostx86.Targetarm64",
+ "Microsoft.VC.14.34.17.4.Premium.Tools.Hostx86.Targetarm64.base",
+ "Microsoft.VC.14.34.17.4.Prem.HostX86.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX64.TargetARM64",
+ "Microsoft.VC.14.34.17.4.Premium.Tools.HostX64.TargetARM64.base",
+ "Microsoft.VC.14.34.17.4.Prem.HostX64.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.ARM64.Base",
+ "Microsoft.VC.14.34.17.4.Premium.Tools.ARM64.Base.base",
+ "Microsoft.VisualCpp.Tools.HostX64.TargetARM64",
+ "Microsoft.VC.14.34.17.4.Tools.HostX64.TargetARM64.base",
+ "Microsoft.VC.14.34.17.4.Props.ARM64",
+ "Microsoft.VC.14.34.17.4.Tools.HostX64.TargetARM64.Res.base",
+ "Microsoft.VisualStudio.Component.VC.Tools.ARM64EC",
+ "Microsoft.VisualStudio.Component.Windows11SDK.22621",
+ "Win11SDK_10.0.22621",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.ARM64EC.v143",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.ARM64EC",
+ "Microsoft.VisualCpp.CRT.ARM64EC.Store",
+ "Microsoft.VC.14.34.17.4.CRT.ARM64EC.Store.base",
+ "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ "Microsoft.VisualCpp.CodeAnalysis.Extensions",
+ "Microsoft.VisualCpp.CA.Ext.HostARM64.Targetx64",
+ "Microsoft.VC.14.34.17.4.CA.Ext.HostARM64.Targetx64.base",
+ "Microsoft.VC.14.34.17.4.CA.Ext.HostARM64.Targetx64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.HostARM64.Targetx86",
+ "Microsoft.VC.14.34.17.4.CA.Ext.HostARM64.Targetx86.base",
+ "Microsoft.VC.14.34.17.4.CA.Ext.HostARM64.Targetx86.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.Hostx86.Targetx64",
+ "Microsoft.VC.14.34.17.4.CA.Ext.Hostx86.Targetx64.base",
+ "Microsoft.VC.14.34.17.4.CA.Ext.Hostx86.Targetx64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.Hostx86.Targetx86",
+ "Microsoft.VC.14.34.17.4.CA.Ext.Hostx86.Targetx86.base",
+ "Microsoft.VC.14.34.17.4.CA.Ext.Hostx86.Targetx86.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.Hostx64.Targetx64",
+ "Microsoft.VC.14.34.17.4.CA.Ext.Hostx64.Targetx64.base",
+ "Microsoft.VC.14.34.17.4.CA.Ext.Hostx64.Targetx64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.Hostx64.Targetx86",
+ "Microsoft.VC.14.34.17.4.CA.Ext.Hostx64.Targetx86.base",
+ "Microsoft.VC.14.34.17.4.Servicing.CAExtensions",
+ "Microsoft.VC.14.34.17.4.CA.Ext.Hostx64.Targetx86.Res.base",
+ "Microsoft.VisualCpp.Tools.HostX64.TargetX86",
+ "Microsoft.VC.14.34.17.4.Tools.HostX64.TargetX86.base",
+ "Microsoft.VC.14.34.17.4.Tools.HostX64.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Tools.HostX64.TargetX64",
+ "Microsoft.VC.14.34.17.4.Tools.HostX64.TargetX64.base",
+ "Microsoft.VC.14.34.17.4.Tools.HostX64.TargetX64.Res.base",
+ "Microsoft.VisualCpp.Tools.HostARM64.TargetX86",
+ "Microsoft.VC.14.34.17.4.Tools.HostARM64.TargetX86.base",
+ "Microsoft.VisualCpp.RuntimeDebug.14",
+ "Microsoft.VC.14.34.17.4.Tools.HostARM64.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Tools.HostARM64.TargetX64",
+ "Microsoft.VC.14.34.17.4.Tools.HostARM64.TargetX64.base",
+ "Microsoft.VisualCpp.RuntimeDebug.14.ARM64",
+ "Microsoft.VisualCpp.Redist.14.Latest",
+ "Microsoft.VisualCpp.Redist.14.Latest",
+ "Microsoft.VC.14.34.17.4.Tools.HostARM64.Targetx64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX64",
+ "Microsoft.VC.14.34.17.4.Premium.Tools.HostX86.TargetX64.base",
+ "Microsoft.VC.14.34.17.4.Prem.Hostx86.Targetx64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX86",
+ "Microsoft.VC.14.34.17.4.Premium.Tools.HostX86.TargetX86.base",
+ "Microsoft.VC.14.34.17.4.Prem.HostX86.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostARM64.TargetX86",
+ "Microsoft.VC.14.34.17.4.Premium.Tools.HostARM64.TargetX86.base",
+ "Microsoft.VC.14.34.17.4.Prem.HostARM64.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostARM64.TargetX64",
+ "Microsoft.VC.14.34.17.4.Premium.Tools.HostARM64.TargetX64.base",
+ "Microsoft.VC.14.34.17.4.Prem.HostARM64.Targetx64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX86",
+ "Microsoft.VC.14.34.17.4.Premium.Tools.HostX64.TargetX86.base",
+ "Microsoft.VC.14.34.17.4.Prem.HostX64.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX64",
+ "Microsoft.VC.14.34.17.4.Premium.Tools.HostX64.TargetX64.base",
+ "Microsoft.VC.14.34.17.4.Prem.HostX64.TargetX64.Res.base",
+ "Microsoft.VisualCpp.PGO.X86",
+ "Microsoft.VC.14.34.17.4.PGO.X86.base",
+ "Microsoft.VisualCpp.PGO.X64",
+ "Microsoft.VC.14.34.17.4.PGO.X64.base",
+ "Microsoft.VisualCpp.PGO.Headers",
+ "Microsoft.VC.14.34.17.4.PGO.Headers.base",
+ "Microsoft.VisualCpp.CRT.x86.Store",
+ "Microsoft.VC.14.34.17.4.CRT.x86.Store.base",
+ "Microsoft.VisualCpp.CRT.x86.OneCore.Desktop",
+ "Microsoft.VC.14.34.17.4.CRT.x86.OneCore.Desktop.base",
+ "Microsoft.VisualCpp.CRT.x64.Store",
+ "Microsoft.VC.14.34.17.4.CRT.x64.Store.base",
+ "Microsoft.VisualCpp.CRT.x64.OneCore.Desktop",
+ "Microsoft.VC.14.34.17.4.CRT.x64.OneCore.Desktop.base",
+ "Microsoft.VisualCpp.CRT.Redist.x86.OneCore.Desktop",
+ "Microsoft.VC.14.34.17.4.CRT.Redist.x86.OneCore.Desktop.base",
+ "Microsoft.VisualCpp.CRT.Redist.x64.OneCore.Desktop",
+ "Microsoft.VC.14.34.17.4.CRT.Redist.x64.OneCore.Desktop.base",
+ "Microsoft.VisualStudio.PackageGroup.VC.Tools.x86",
+ "Microsoft.VisualCpp.Tools.Hostx86.Targetx64.Res",
+ "Microsoft.VisualCpp.Tools.HostX86.TargetX64",
+ "Microsoft.VC.14.34.17.4.Tools.HostX86.TargetX64.base",
+ "Microsoft.VC.14.34.17.4.Props.x64",
+ "Microsoft.VC.14.34.17.4.Tools.Hostx86.Targetx64.Res.base",
+ "Microsoft.VisualCpp.Tools.HostX86.TargetX86.Res",
+ "Microsoft.VisualCpp.Tools.HostX86.TargetX86",
+ "Microsoft.VC.14.34.17.4.Tools.HostX86.TargetX86.base",
+ "Microsoft.VC.14.34.17.4.Servicing.Compilers",
+ "Microsoft.VC.14.34.17.4.Props.x86",
+ "Microsoft.VC.14.34.17.4.Props",
+ "Microsoft.VC.14.34.17.4.Tools.HostX86.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Tools.Core.Resources",
+ "Microsoft.VisualCpp.Tools.Core.x86",
+ "Microsoft.VC.14.34.17.4.Tools.Core.Props",
+ "Microsoft.VisualCpp.DIA.SDK",
+ "Microsoft.VisualCpp.Servicing.DIASDK",
+ "Microsoft.VisualCpp.CRT.x86.Desktop",
+ "Microsoft.VC.14.34.17.4.CRT.x86.Desktop.base",
+ "Microsoft.VisualCpp.CRT.x64.Desktop",
+ "Microsoft.VC.14.34.17.4.CRT.x64.Desktop.base",
+ "Microsoft.VisualCpp.CRT.Source",
+ "Microsoft.VC.14.34.17.4.CRT.Source.base",
+ "Microsoft.VisualCpp.CRT.Redist.X86",
+ "Microsoft.VC.14.34.17.4.CRT.Redist.X86.base",
+ "Microsoft.VisualCpp.CRT.Redist.X64",
+ "Microsoft.VisualCpp.CRT.Redist.Resources",
+ "Microsoft.VC.14.34.17.4.CRT.Redist.X64.base",
+ "Microsoft.VisualCpp.CRT.Headers",
+ "Microsoft.VC.14.34.17.4.CRT.Headers.base",
+ "Microsoft.VC.14.34.17.4.Servicing.CrtHeaders",
+ "Microsoft.VC.14.34.17.4.Servicing",
+ "Microsoft.VisualStudio.Component.VC.CoreIde",
+ "Microsoft.VisualStudio.VC.Ide.Pro",
+ "Microsoft.VisualStudio.VC.Ide.Pro.Resources",
+ "Microsoft.VisualStudio.VC.Templates.General",
+ "Microsoft.VisualStudio.VC.Templates.General.Resources",
+ "Microsoft.VisualStudio.VC.Items.Pro",
+ "Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Reduced",
+ "Microsoft.VisualStudio.VC.Ide.x64",
+ "Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Express",
+ "Microsoft.VisualStudio.VC.vcvars",
+ "Microsoft.VS.VC.vcvars.x86.Shortcuts",
+ "Microsoft.VS.VC.vcvars.x64.Shortcuts",
+ "Microsoft.VS.VC.vcvars.arm64_x64.Shortcuts",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.X64.v143",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.X64",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.ARM.v143",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.ARM",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.x86.v143",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.X86",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.Base",
+ "Microsoft.VisualStudio.VC.MSBuild.v170.Base.Resources",
+ "Microsoft.VisualStudio.VC.Ide.WinXPlus",
+ "Microsoft.VisualStudio.VC.Ide.Dskx",
+ "Microsoft.VisualStudio.VC.Ide.Dskx.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Base",
+ "Microsoft.VisualStudio.VC.Ide.LanguageService",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.Scripts",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.PythonDistro",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.10",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.9",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.8",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.7",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.6",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.5",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.4",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.3",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.2",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.1",
+ "Microsoft.VisualStudio.VC.Ide.VCPkgDatabase",
+ "Microsoft.VisualStudio.VC.Ide.Core",
+ "Microsoft.VisualStudio.VC.Ide.ProjectSystem",
+ "Microsoft.VisualStudio.VC.Ide.ProjectSystem.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine",
+ "Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine.Resources",
+ "Microsoft.VisualStudio.VC.Ide.LanguageService.Resources",
+ "Microsoft.VisualStudio.VC.Llvm.Base",
+ "Microsoft.VisualStudio.VC.Ide.Base.Resources",
+ "Microsoft.Net.PackageGroup.4.8.1.Redist",
+ "Microsoft.VisualStudio.Component.IntelliCode",
+ "Microsoft.VisualStudio.IntelliCode.CSharp",
+ "Microsoft.VisualStudio.IntelliCode",
+ "Component.Microsoft.VisualStudio.LiveShare.2022",
+ "Microsoft.VisualStudio.Component.Debugger.JustInTime",
+ "Microsoft.VisualStudio.Debugger.ImmersiveActivateHelper.Msi",
+ "Microsoft.VisualStudio.Debugger.JustInTime",
+ "Microsoft.VisualStudio.Debugger.JustInTime.Msi",
+ "Microsoft.VisualStudio.LiveShare.2022",
+ "Microsoft.Icecap.Analysis",
+ "Microsoft.Icecap.Analysis.Resources",
+ "Microsoft.Icecap.Analysis.Resources.Targeted",
+ "Microsoft.Icecap.Collection.Msi",
+ "Microsoft.Icecap.Collection.Msi.Targeted",
+ "Microsoft.Icecap.Collection.Msi.Resources",
+ "Microsoft.Icecap.Collection.Msi.Resources.Targeted",
+ "Microsoft.DiagnosticsHub.Instrumentation",
+ "Microsoft.DiagnosticsHub.Instrumentation.Targeted",
+ "Microsoft.DiagnosticsHub.CpuSampling",
+ "Microsoft.DiagnosticsHub.CpuSampling.Targeted",
+ "Microsoft.PackageGroup.DiagnosticsHub.Platform",
+ "Microsoft.VisualStudio.InstrumentationEngine.ARM64",
+ "Microsoft.VisualStudio.InstrumentationEngine",
+ "Microsoft.DiagnosticsHub.Runtime.ExternalDependencies",
+ "SQLiteCore",
+ "SQLiteCore.Targeted",
+ "Microsoft.DiagnosticsHub.Runtime.ExternalDependencies.Targeted",
+ "Microsoft.DiagnosticsHub.Runtime",
+ "Microsoft.DiagnosticsHub.Runtime.Targeted",
+ "Microsoft.DiagnosticsHub.Collection.ExternalDependencies.arm64",
+ "Microsoft.DiagnosticsHub.Collection",
+ "Microsoft.DiagnosticsHub.Collection.Service",
+ "Microsoft.VisualStudio.VC.Ide.MDD",
+ "Microsoft.VisualStudio.VC.Ide.Linux.ConnectionManager",
+ "Microsoft.VisualStudio.VisualC.Utilities",
+ "Microsoft.VisualStudio.VisualC.Utilities.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Linux.ConnectionManager.Resources",
+ "Microsoft.VisualStudio.VC.Ide.ResourceEditor",
+ "Microsoft.VisualStudio.VC.Ide.ResourceEditor.Resources",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.Core",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.V2.CLI",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI",
+ "Microsoft.VisualStudio.TestTools.Pex.Common",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.V1.CLI",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.Legacy",
+ "Microsoft.VisualStudio.PackageGroup.MinShell.Interop",
+ "Microsoft.VisualStudio.TestTools.TP.Legacy.Tips.Msi",
+ "Microsoft.VisualStudio.TestTools.TP.Legacy.Tips.Common",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Tips",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Tips.Resources",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.TestSettings",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Professional",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Common",
+ "Microsoft.VisualStudio.TestTools.TP.Legacy.Common.Res",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Core",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Core.Resources",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Agent",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.IDE",
+ "Microsoft.VisualStudio.Cache.Service",
+ "Microsoft.VisualStudio.TestTools.TestWIExtension",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.V1.CLI",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.IDE",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.CodeCoverage",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.DataCollectors",
+ "Microsoft.VisualStudio.Component.NuGet",
+ "Microsoft.CredentialProvider",
+ "Microsoft.VisualStudio.NuGet.Licenses",
+ "Microsoft.VisualStudio.Component.TextTemplating",
+ "Microsoft.VisualStudio.TextTemplating.MSBuild",
+ "Microsoft.VisualStudio.TextTemplating.Integration",
+ "Microsoft.VisualStudio.TextTemplating.Core",
+ "Microsoft.VisualStudio.TextTemplating.Integration.Resources",
+ "Microsoft.VisualCpp.CRT.ClickOnce.Msi",
+ "Microsoft.VisualStudio.Component.Roslyn.LanguageServices",
+ "Microsoft.VisualStudio.InteractiveWindow",
+ "Microsoft.DiaSymReader.Native",
+ "Microsoft.VisualCpp.Redist.14",
+ "Microsoft.VisualCpp.Redist.14",
+ "Microsoft.VisualCpp.Servicing.Redist",
+ "Microsoft.VisualStudio.PackageGroup.StaticAnalysis",
+ "Microsoft.VisualStudio.StaticAnalysis.IDE",
+ "Microsoft.VisualStudio.StaticAnalysis.IDE.Resources",
+ "Microsoft.VisualStudio.StaticAnalysis.FxCop.Resources",
+ "Microsoft.VisualStudio.StaticAnalysis.auxil",
+ "Microsoft.VisualStudio.StaticAnalysis.auxil.Resources",
+ "Roslyn.VisualStudio.Setup.ServiceHub",
+ "Microsoft.Component.MSBuild",
+ "Microsoft.NuGet.Build.Tasks.Setup",
+ "Microsoft.VisualStudio.Component.Roslyn.Compiler",
+ "Microsoft.CodeAnalysis.Compilers",
+ "Microsoft.VisualStudio.Component.JavaScript.TypeScript",
+ "Microsoft.VisualStudio.JavaScript.ProjectSystem",
+ "Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions",
+ "Microsoft.VisualStudio.ProTools",
+ "sqlsysclrtypes",
+ "SQLCommon",
+ "Microsoft.VisualStudio.ProTools.Resources",
+ "Microsoft.VisualStudio.Web.Scaffolding",
+ "Microsoft.VisualStudio.WebToolsExtensions",
+ "Microsoft.VisualStudio.ConnectedServices.Core",
+ "Microsoft.VisualStudio.WebTools",
+ "Microsoft.VisualStudio.WebToolsExtensions.MSBuild",
+ "Microsoft.VisualStudio.WebTools.Resources",
+ "Microsoft.VisualStudio.WebTools.WSP.FSA",
+ "Microsoft.VisualStudio.WebTools.WSP.FSA.Resources",
+ "Microsoft.VisualStudio.PackageGroup.Debugger.Script",
+ "Microsoft.VisualStudio.Component.TypeScript.TSServer",
+ "Microsoft.VisualStudio.Package.TypeScript.TSServer",
+ "Microsoft.VisualStudio.PackageGroup.JavaScript.Language",
+ "Microsoft.VisualStudio.Package.NodeJs",
+ "TypeScript.Build",
+ "TypeScript.LanguageService",
+ "TypeScript.Tools",
+ "Microsoft.VisualStudio.PackageGroup.Community",
+ "Microsoft.VisualStudio.Community.VB.x86",
+ "Microsoft.VisualStudio.Community.VB.x64",
+ "Microsoft.VisualStudio.PackageGroup.Core",
+ "Microsoft.VisualStudio.CodeSense.Community",
+ "Microsoft.VisualStudio.TestTools.TeamFoundationClient",
+ "Microsoft.VisualStudio.PackageGroup.Debugger.Core",
+ "Microsoft.VisualStudio.Debugger.BrokeredServices",
+ "Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost",
+ "Microsoft.VisualStudio.Debugger.AzureAttach",
+ "Microsoft.VisualStudio.Web.Azure.Common",
+ "Microsoft.WebTools.Shared",
+ "Microsoft.WebTools.DotNet.Core.ItemTemplates",
+ "Microsoft.VisualStudio.PackageGroup.Debugger.TimeTravel.Replay",
+ "Microsoft.VisualStudio.VC.Ide.Debugger",
+ "Microsoft.VisualStudio.VC.Ide.Debugger.Concord",
+ "Microsoft.VisualStudio.VC.Ide.Debugger.Concord.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Debugger.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Common",
+ "Microsoft.VisualStudio.VC.Ide.Common.Resources",
+ "Microsoft.VisualStudio.Debugger.CollectionAgents",
+ "Microsoft.VisualStudio.Debugger.Parallel",
+ "Microsoft.VisualStudio.Debugger.Parallel.Resources",
+ "Microsoft.VisualStudio.Debugger.Managed",
+ "Microsoft.CodeAnalysis.ExpressionEvaluator",
+ "Microsoft.CodeAnalysis.VisualStudio.Setup",
+ "Microsoft.VisualStudio.Debugger.Concord.Managed",
+ "Microsoft.VisualStudio.Debugger.Concord.Managed.Resources",
+ "Microsoft.VisualStudio.Debugger.Managed.Resources",
+ "Microsoft.VisualStudio.Debugger.TargetComposition",
+ "Microsoft.VisualStudio.Debugger.TargetComposition.Remote.arm64",
+ "Microsoft.VisualStudio.Debugger.TargetComposition.Remote",
+ "Microsoft.VisualStudio.Debugger.TargetComposition.Remote",
+ "Microsoft.VisualStudio.Debugger.Remote",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.Resources",
+ "Microsoft.VisualStudio.Debugger.Remote",
+ "Microsoft.VisualStudio.Debugger.Remote.ARM64",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.ARM64",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.Resources.ARM64",
+ "Microsoft.VisualStudio.Debugger.Remote.ARM",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.ARM",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.Resources.ARM",
+ "Microsoft.VisualStudio.Debugger.Remote.Resources.ARM",
+ "Microsoft.VisualStudio.Debugger.Remote.Resources.ARM64",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.Resources",
+ "Microsoft.VisualStudio.Debugger.Remote.Resources",
+ "Microsoft.VisualStudio.Debugger.Remote.Resources",
+ "Microsoft.VisualStudio.Debugger",
+ "Microsoft.VisualStudio.VC.MSVCDis",
+ "Microsoft.IntelliTrace.DiagnosticsHub",
+ "Microsoft.VisualStudio.Debugger.Concord",
+ "Microsoft.VisualStudio.Debugger.Concord.Resources",
+ "Microsoft.VisualStudio.Debugger.Resources",
+ "Microsoft.VisualStudio.Debugger.Package.DiagHub.Client",
+ "Microsoft.VisualStudio.Debugger.Remote.DiagnosticsHub.Client",
+ "Microsoft.VisualStudio.Debugger.Remote.DiagnosticsHub.Client",
+ "Microsoft.VisualStudio.Debugger.Remote.DiagnosticsHub.Client",
+ "Microsoft.PackageGroup.ClientDiagnostics",
+ "Microsoft.VisualStudio.AppResponsiveness",
+ "Microsoft.VisualStudio.AppResponsiveness.Targeted",
+ "Microsoft.VisualStudio.AppResponsiveness.Resources",
+ "Microsoft.VisualStudio.ClientDiagnostics",
+ "Microsoft.VisualStudio.ClientDiagnostics.Targeted",
+ "Microsoft.VisualStudio.ClientDiagnostics.Resources",
+ "Microsoft.VisualStudio.PackageGroup.CommunityCore",
+ "Microsoft.VisualStudio.ProjectSystem.Full",
+ "Microsoft.VisualStudio.LiveShareApi",
+ "Microsoft.VisualStudio.ProjectSystem.Query",
+ "Microsoft.VisualStudio.ProjectSystem",
+ "Microsoft.VisualStudio.Community.x86",
+ "Microsoft.VisualStudio.Community.x64",
+ "Microsoft.VisualStudio.Community.Msi.Resources",
+ "Microsoft.VisualStudio.Community.Msi",
+ "Microsoft.VisualStudio.Community.Shared.Msi",
+ "Microsoft.VisualStudio.Devenv.Msi",
+ "Microsoft.VisualStudio.Devenv.Shared.Msi",
+ "Microsoft.VisualStudio.MinShell.Interop.Msi",
+ "Microsoft.VisualStudio.MinShell.Interop.Shared.Msi",
+ "Microsoft.VisualStudio.Editors",
+ "Microsoft.VisualStudio.Workload.CoreEditor",
+ "Microsoft.VisualStudio.Component.CoreEditor",
+ "Microsoft.VisualStudio.PackageGroup.CoreEditor",
+ "Microsoft.WebView2",
+ "Microsoft.VisualStudio.ScriptedHost",
+ "Microsoft.VisualStudio.ScriptedHost.Targeted",
+ "Microsoft.VisualCpp.Tools.Common.UtilsPrereq",
+ "Microsoft.VisualCpp.Tools.Common.Utils",
+ "Microsoft.VisualCpp.Tools.Common.Utils.Resources",
+ "Microsoft.VisualStudio.PackageGroup.VsDevCmd",
+ "Microsoft.VisualStudio.VsDevCmd.Ext.NetFxSdk",
+ "Microsoft.VisualStudio.VsDevCmd.Core.WinSdk",
+ "Microsoft.VisualStudio.VsDevCmd.Core.DotNet",
+ "Microsoft.VisualStudio.VC.DevCmd",
+ "Microsoft.VisualStudio.VC.DevCmd.Resources",
+ "Microsoft.VisualStudio.VirtualTree",
+ "Microsoft.DiaSymReader",
+ "Microsoft.Build.Dependencies",
+ "Microsoft.Build.FileTracker.Msi",
+ "Microsoft.Build",
+ "Microsoft.VisualStudio.PackageGroup.NuGet",
+ "Microsoft.DataAI.NuGetRecommender",
+ "Microsoft.VisualStudio.NuGet.Core",
+ "Microsoft.Build.Arm64",
+ "Microsoft.Build.UnGAC",
+ "Microsoft.VisualStudio.TextMateGrammars",
+ "Microsoft.VisualStudio.Platform.Markdown",
+ "Microsoft.VisualStudio.Platform.CrossRepositorySearch",
+ "Microsoft.VisualStudio.PackageGroup.TeamExplorer.Common",
+ "Microsoft.VisualStudio.TeamExplorer",
+ "Microsoft.VisualStudio.PackageGroup.ServiceHub",
+ "Microsoft.ServiceHub.Node",
+ "Microsoft.ServiceHub.Managed",
+ "Microsoft.ServiceHub.arm64",
+ "Microsoft.VisualStudio.ProjectServices",
+ "Microsoft.VisualStudio.OpenFolder.VSIX",
+ "Microsoft.VisualStudio.FileHandler.Msi",
+ "Microsoft.VisualStudio.FileHandler.Msi",
+ "Microsoft.VisualStudio.PackageGroup.MinShell",
+ "Microsoft.VisualStudio.MinShell.Msi",
+ "Microsoft.VisualStudio.MinShell.Shared.Msi",
+ "Microsoft.VisualStudio.MinShell.Msi.Resources",
+ "Microsoft.VisualStudio.MinShell.Interop",
+ "CoreEditorFonts",
+ "Microsoft.VisualStudio.Log",
+ "Microsoft.VisualStudio.Log.Targeted",
+ "Microsoft.VisualStudio.Log.Resources",
+ "Microsoft.VisualStudio.Finalizer",
+ "Microsoft.VisualStudio.Devenv",
+ "Microsoft.VisualStudio.Devenv.Resources",
+ "Microsoft.VisualStudio.CoreEditor",
+ "Microsoft.VisualStudio.Navigation.RichCodeNav",
+ "Microsoft.VisualStudio.Platform.NavigateTo",
+ "Microsoft.VisualStudio.Connected",
+ "SQLitePCLRaw",
+ "SQLitePCLRaw.Targeted",
+ "Microsoft.VisualStudio.Connected.Auto",
+ "Microsoft.VisualStudio.Connected.Auto.Resources",
+ "Microsoft.VisualStudio.AzureSDK",
+ "Microsoft.VisualStudio.PerfLib",
+ "Microsoft.VisualStudio.Connected.Resources",
+ "Microsoft.Net.PackageGroup.4.8.Redist",
+ "Microsoft.VisualStudio.PackageGroup.Progression",
+ "Microsoft.VisualStudio.PerformanceProvider",
+ "Microsoft.VisualStudio.GraphModel",
+ "Microsoft.VisualStudio.GraphProvider",
+ "Microsoft.VisualStudio.Community.VB.Targeted",
+ "Microsoft.VisualStudio.Community.VB.Neutral",
+ "Microsoft.VisualStudio.Community.CSharp.Targeted",
+ "Microsoft.VisualStudio.Community.CSharp.Neutral",
+ "Microsoft.VisualStudio.Community.ProductArch.TargetedExtra",
+ "Microsoft.VisualStudio.Community.ProductArch.Targeted",
+ "Microsoft.VisualStudio.Community.ProductArch.NeutralExtra",
+ "Microsoft.DiaSymReader.PortablePdb",
+ "Microsoft.IntelliTrace.CollectorCab",
+ "Microsoft.VisualStudio.Community.VB.Resources.Targeted",
+ "Microsoft.VisualStudio.Community.VB.Resources.Neutral",
+ "Microsoft.VisualStudio.Community.CSharp.Resources.Targeted",
+ "Microsoft.VisualStudio.Community.CSharp.Resources.Neutral",
+ "Microsoft.VisualStudio.Community.ProductArch.Resources.Targeted",
+ "Microsoft.VisualStudio.Community.ProductArch.Resources.NeutralExtra",
+ "Microsoft.VisualStudio.Net.Eula.Resources",
+ "Microsoft.VisualStudio.Community.ProductArch.Resources.Neutral",
+ "Microsoft.VisualStudio.WebSiteProject.DTE",
+ "Microsoft.VisualStudio.Diagnostics.AspNetHelper",
+ "Microsoft.VisualStudio.Diagnostics.AspNetHelper.Standard",
+ "Microsoft.MSHtml",
+ "Microsoft.VisualStudio.Platform.CallHierarchy",
+ "Microsoft.VisualStudio.Community.ProductArch.Neutral",
+ "Microsoft.VisualStudio.MinShell",
+ "Microsoft.VisualStudio.VsWebProtocolSelector.Msi",
+ "Microsoft.Net.6.WindowsDesktop.Runtime",
+ "Microsoft.Net.6.Runtime",
+ "Microsoft.VisualStudio.PackageGroup.Setup.Common",
+ "Microsoft.VisualStudio.Setup.WMIProvider",
+ "Microsoft.VisualStudio.Setup.Configuration.Interop",
+ "Microsoft.VisualStudio.Setup.Configuration",
+ "Microsoft.VisualStudio.Extensibility.Container",
+ "Microsoft.VisualStudio.LanguageServer",
+ "Microsoft.VisualStudio.Platform.Terminal",
+ "Microsoft.VisualStudio.MefHosting",
+ "Microsoft.VisualStudio.Initializer",
+ "Microsoft.VisualStudio.ExtensionManager",
+ "Microsoft.VisualStudio.Platform.Editor",
+ "Microsoft.VisualStudio.MinShell.Targeted",
+ "Microsoft.VisualStudio.NativeImageSupport",
+ "Microsoft.VisualStudio.Devenv.Config",
+ "Microsoft.VisualStudio.MinShell.Resources.arm64",
+ "Microsoft.VisualStudio.MinShell.Auto",
+ "Microsoft.VisualStudio.MinShell.Auto.Resources",
+ "Microsoft.VisualStudio.Branding.Community"
+ ]
+ }
+]
diff --git a/test/fixtures/VS_2026_Community_workload.txt b/test/fixtures/VS_2026_Community_workload.txt
new file mode 100644
index 0000000000..adfaf08a70
--- /dev/null
+++ b/test/fixtures/VS_2026_Community_workload.txt
@@ -0,0 +1,568 @@
+[
+ {
+ "path": "C:\\Program Files\\Microsoft Visual Studio\\2026\\Community",
+ "version": "18.0.1000.100",
+ "packages": [
+ "Microsoft.VisualStudio.Product.Community",
+ "Microsoft.VisualStudio.PackageGroup.LiveShare.VSCore",
+ "Microsoft.VisualStudio.LiveShare.VSCore",
+ "Microsoft.VisualStudio.Workload.NativeDesktop",
+ "Microsoft.VisualStudio.Component.VC.ASAN",
+ "Microsoft.VisualCpp.ASAN.X86",
+ "Microsoft.VC.14.50.18.0.ASAN.X86.base",
+ "Microsoft.VC.14.50.18.0.ASAN.X64.base",
+ "Microsoft.VC.14.50.18.0.ASAN.Headers.base",
+ "Microsoft.VisualStudio.VC.IDE.Project.Factories",
+ "Microsoft.VisualStudio.Component.VC.TestAdapterForGoogleTest",
+ "Microsoft.VisualStudio.VC.Ide.TestAdapterForGoogleTest",
+ "Microsoft.VisualStudio.Component.VC.TestAdapterForBoostTest",
+ "Microsoft.VisualStudio.VC.Ide.TestAdapterForBoostTest",
+ "Microsoft.VisualStudio.Component.VC.CMake.Project",
+ "Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions.CMake",
+ "Microsoft.VisualStudio.VC.CMake",
+ "Microsoft.VisualStudio.VC.CMake.Project",
+ "Microsoft.VisualStudio.VC.CMake.Client",
+ "Microsoft.VisualStudio.VC.ExternalBuildFramework",
+ "Microsoft.VisualStudio.Component.VC.DiagnosticTools",
+ "Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.Native",
+ "Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
+ "Microsoft.VisualStudio.VC.Templates.UnitTest",
+ "Microsoft.VisualStudio.VC.UnitTest.Desktop.Build.Core",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.V1.CPP",
+ "Microsoft.VisualStudio.VC.Templates.UnitTest.Resources",
+ "Microsoft.VisualStudio.VC.Templates.Desktop",
+ "Microsoft.VisualStudio.Component.Graphics",
+ "Microsoft.VisualStudio.Graphics.Viewers",
+ "Microsoft.VisualStudio.Graphics.Viewers.Resources",
+ "Microsoft.VisualStudio.Component.VC.ATL.ARM64",
+ "Microsoft.VisualCpp.ATL.ARM64",
+ "Microsoft.VC.14.50.18.0.ATL.ARM64.base",
+ "Microsoft.VisualStudio.Component.VC.ATL",
+ "Microsoft.VisualStudio.VC.Ide.ATL",
+ "Microsoft.VisualStudio.VC.Ide.ATL.Resources",
+ "Microsoft.VisualCpp.ATL.X86",
+ "Microsoft.VC.14.50.18.0.ATL.X86.base",
+ "Microsoft.VisualCpp.ATL.X64",
+ "Microsoft.VC.14.50.18.0.ATL.X64.base",
+ "Microsoft.VC.14.50.18.0.Props.ATLMFC",
+ "Microsoft.VisualCpp.ATL.Source",
+ "Microsoft.VC.14.50.18.0.ATL.Source.base",
+ "Microsoft.VisualCpp.ATL.Headers",
+ "Microsoft.VC.14.50.18.0.ATL.Headers.base",
+ "Microsoft.VC.14.50.18.0.Servicing.ATL",
+ "Microsoft.VisualStudio.Component.VC.Tools.ARM64",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.ARM64.v145",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.ARM64",
+ "Microsoft.VS.VC.vcvars.arm64.Shortcuts",
+ "Microsoft.VisualCpp.CA.Ext.Hostx64.TargetARM64",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx64.TargetARM64.base",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx64.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.Hostx86.TargetARM64",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx86.TargetARM64.base",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx86.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.HostARM64.TargetARM64",
+ "Microsoft.VC.14.50.18.0.CA.Ext.HostARM64.TargetARM64.base",
+ "Microsoft.VC.14.50.18.0.CA.Ext.HostARM64.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.Tools.Hostx86.Targetarm64",
+ "Microsoft.VC.14.50.18.0.Tools.Hostx86.Targetarm64.base",
+ "Microsoft.VC.14.50.18.0.Tools.HostX86.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.Tools.HostARM64.TargetARM64",
+ "Microsoft.VC.14.50.18.0.Tools.HostARM64.TargetARM64.base",
+ "Microsoft.VC.14.50.18.0.Tools.HostARM64.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.CRT.Redist.ARM64.OneCore.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.Redist.ARM64.OneCore.Desktop.base",
+ "Microsoft.VisualCpp.CRT.Redist.ARM64",
+ "Microsoft.VC.14.50.18.0.CRT.Redist.ARM64.base",
+ "Microsoft.VisualCpp.CRT.ARM64.OneCore.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.ARM64.OneCore.Desktop.base",
+ "Microsoft.VC.14.50.18.0.CRT.ARM64.OneCore.Desktop.debug.base",
+ "Microsoft.VisualCpp.CRT.ARM64.Store",
+ "Microsoft.VC.14.50.18.0.CRT.ARM64.Store.base",
+ "Microsoft.VisualCpp.CRT.ARM64.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.ARM64.Desktop.base",
+ "Microsoft.VC.14.50.18.0.CRT.ARM64.Desktop.debug.base",
+ "Microsoft.VisualStudio.PackageGroup.VC.Tools.x64.ARM64",
+ "Microsoft.VisualCpp.Tools.Core",
+ "Microsoft.VisualCpp.PGO.ARM64",
+ "Microsoft.VC.14.50.18.0.PGO.ARM64.base",
+ "Microsoft.VisualCpp.Premium.Tools.Hostx86.Targetarm64",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.Hostx86.Targetarm64.base",
+ "Microsoft.VC.14.50.18.0.Prem.HostX86.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX64.TargetARM64",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.HostX64.TargetARM64.base",
+ "Microsoft.VC.14.50.18.0.Prem.HostX64.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.ARM64.Base",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.ARM64.Base.base",
+ "Microsoft.VisualCpp.Tools.HostX64.TargetARM64",
+ "Microsoft.VC.14.50.18.0.Tools.HostX64.TargetARM64.base",
+ "Microsoft.VC.14.50.18.0.Props.ARM64",
+ "Microsoft.VC.14.50.18.0.Tools.HostX64.TargetARM64.Res.base",
+ "Microsoft.VisualStudio.Component.VC.Tools.ARM64EC",
+ "Microsoft.VisualStudio.Component.Windows11SDK.26100",
+ "Win11SDK_10.0.26100",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.ARM64EC.v145",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.ARM64EC",
+ "Microsoft.VisualCpp.CRT.ARM64EC.Store",
+ "Microsoft.VC.14.50.18.0.CRT.ARM64EC.Store.base",
+ "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ "Microsoft.VisualCpp.CodeAnalysis.Extensions",
+ "Microsoft.VisualCpp.CA.Ext.HostARM64.Targetx64",
+ "Microsoft.VC.14.50.18.0.CA.Ext.HostARM64.Targetx64.base",
+ "Microsoft.VC.14.50.18.0.CA.Ext.HostARM64.Targetx64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.HostARM64.Targetx86",
+ "Microsoft.VC.14.50.18.0.CA.Ext.HostARM64.Targetx86.base",
+ "Microsoft.VC.14.50.18.0.CA.Ext.HostARM64.Targetx86.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.Hostx86.Targetx64",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx86.Targetx64.base",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx86.Targetx64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.Hostx86.Targetx86",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx86.Targetx86.base",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx86.Targetx86.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.Hostx64.Targetx64",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx64.Targetx64.base",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx64.Targetx64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.Hostx64.Targetx86",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx64.Targetx86.base",
+ "Microsoft.VC.14.50.18.0.Servicing.CAExtensions",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx64.Targetx86.Res.base",
+ "Microsoft.VisualCpp.Tools.HostX64.TargetX86",
+ "Microsoft.VC.14.50.18.0.Tools.HostX64.TargetX86.base",
+ "Microsoft.VC.14.50.18.0.Tools.HostX64.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Tools.HostX64.TargetX64",
+ "Microsoft.VC.14.50.18.0.Tools.HostX64.TargetX64.base",
+ "Microsoft.VC.14.50.18.0.Tools.HostX64.TargetX64.Res.base",
+ "Microsoft.VisualCpp.Tools.HostARM64.TargetX86",
+ "Microsoft.VC.14.50.18.0.Tools.HostARM64.TargetX86.base",
+ "Microsoft.VisualCpp.RuntimeDebug.14",
+ "Microsoft.VC.14.50.18.0.Tools.HostARM64.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Tools.HostARM64.TargetX64",
+ "Microsoft.VC.14.50.18.0.Tools.HostARM64.TargetX64.base",
+ "Microsoft.VisualCpp.RuntimeDebug.14.ARM64",
+ "Microsoft.VisualCpp.Redist.14.Latest",
+ "Microsoft.VisualCpp.Redist.14.Latest",
+ "Microsoft.VC.14.50.18.0.Tools.HostARM64.Targetx64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX64",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.HostX86.TargetX64.base",
+ "Microsoft.VC.14.50.18.0.Prem.Hostx86.Targetx64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX86",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.HostX86.TargetX86.base",
+ "Microsoft.VC.14.50.18.0.Prem.HostX86.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostARM64.TargetX86",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.HostARM64.TargetX86.base",
+ "Microsoft.VC.14.50.18.0.Prem.HostARM64.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostARM64.TargetX64",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.HostARM64.TargetX64.base",
+ "Microsoft.VC.14.50.18.0.Prem.HostARM64.Targetx64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX86",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.HostX64.TargetX86.base",
+ "Microsoft.VC.14.50.18.0.Prem.HostX64.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX64",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.HostX64.TargetX64.base",
+ "Microsoft.VC.14.50.18.0.Prem.HostX64.TargetX64.Res.base",
+ "Microsoft.VisualCpp.PGO.X86",
+ "Microsoft.VC.14.50.18.0.PGO.X86.base",
+ "Microsoft.VisualCpp.PGO.X64",
+ "Microsoft.VC.14.50.18.0.PGO.X64.base",
+ "Microsoft.VisualCpp.PGO.Headers",
+ "Microsoft.VC.14.50.18.0.PGO.Headers.base",
+ "Microsoft.VisualCpp.CRT.x86.Store",
+ "Microsoft.VC.14.50.18.0.CRT.x86.Store.base",
+ "Microsoft.VisualCpp.CRT.x86.OneCore.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.x86.OneCore.Desktop.base",
+ "Microsoft.VisualCpp.CRT.x64.Store",
+ "Microsoft.VC.14.50.18.0.CRT.x64.Store.base",
+ "Microsoft.VisualCpp.CRT.x64.OneCore.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.x64.OneCore.Desktop.base",
+ "Microsoft.VisualCpp.CRT.Redist.x86.OneCore.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.Redist.x86.OneCore.Desktop.base",
+ "Microsoft.VisualCpp.CRT.Redist.x64.OneCore.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.Redist.x64.OneCore.Desktop.base",
+ "Microsoft.VisualStudio.PackageGroup.VC.Tools.x86",
+ "Microsoft.VisualCpp.Tools.Hostx86.Targetx64.Res",
+ "Microsoft.VisualCpp.Tools.HostX86.TargetX64",
+ "Microsoft.VC.14.50.18.0.Tools.HostX86.TargetX64.base",
+ "Microsoft.VC.14.50.18.0.Props.x64",
+ "Microsoft.VC.14.50.18.0.Tools.Hostx86.Targetx64.Res.base",
+ "Microsoft.VisualCpp.Tools.HostX86.TargetX86.Res",
+ "Microsoft.VisualCpp.Tools.HostX86.TargetX86",
+ "Microsoft.VC.14.50.18.0.Tools.HostX86.TargetX86.base",
+ "Microsoft.VC.14.50.18.0.Servicing.Compilers",
+ "Microsoft.VC.14.50.18.0.Props.x86",
+ "Microsoft.VC.14.50.18.0.Props",
+ "Microsoft.VC.14.50.18.0.Tools.HostX86.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Tools.Core.Resources",
+ "Microsoft.VisualCpp.Tools.Core.x86",
+ "Microsoft.VC.14.50.18.0.Tools.Core.Props",
+ "Microsoft.VisualCpp.DIA.SDK",
+ "Microsoft.VisualCpp.Servicing.DIASDK",
+ "Microsoft.VisualCpp.CRT.x86.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.x86.Desktop.base",
+ "Microsoft.VisualCpp.CRT.x64.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.x64.Desktop.base",
+ "Microsoft.VisualCpp.CRT.Source",
+ "Microsoft.VC.14.50.18.0.CRT.Source.base",
+ "Microsoft.VisualCpp.CRT.Redist.X86",
+ "Microsoft.VC.14.50.18.0.CRT.Redist.X86.base",
+ "Microsoft.VisualCpp.CRT.Redist.X64",
+ "Microsoft.VisualCpp.CRT.Redist.Resources",
+ "Microsoft.VC.14.50.18.0.CRT.Redist.X64.base",
+ "Microsoft.VisualCpp.CRT.Headers",
+ "Microsoft.VC.14.50.18.0.CRT.Headers.base",
+ "Microsoft.VC.14.50.18.0.Servicing.CrtHeaders",
+ "Microsoft.VC.14.50.18.0.Servicing",
+ "Microsoft.VisualStudio.Component.VC.CoreIde",
+ "Microsoft.VisualStudio.VC.Ide.Pro",
+ "Microsoft.VisualStudio.VC.Ide.Pro.Resources",
+ "Microsoft.VisualStudio.VC.Templates.General",
+ "Microsoft.VisualStudio.VC.Templates.General.Resources",
+ "Microsoft.VisualStudio.VC.Items.Pro",
+ "Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Reduced",
+ "Microsoft.VisualStudio.VC.Ide.x64",
+ "Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Express",
+ "Microsoft.VisualStudio.VC.vcvars",
+ "Microsoft.VS.VC.vcvars.x86.Shortcuts",
+ "Microsoft.VS.VC.vcvars.x64.Shortcuts",
+ "Microsoft.VS.VC.vcvars.arm64_x64.Shortcuts",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.X64.v145",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.X64",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.ARM.v145",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.ARM",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.x86.v145",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.X86",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.Base",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.Base.Resources",
+ "Microsoft.VisualStudio.VC.Ide.WinXPlus",
+ "Microsoft.VisualStudio.VC.Ide.Dskx",
+ "Microsoft.VisualStudio.VC.Ide.Dskx.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Base",
+ "Microsoft.VisualStudio.VC.Ide.LanguageService",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.Scripts",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.PythonDistro",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.10",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.9",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.8",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.7",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.6",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.5",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.4",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.3",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.2",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.1",
+ "Microsoft.VisualStudio.VC.Ide.VCPkgDatabase",
+ "Microsoft.VisualStudio.VC.Ide.Core",
+ "Microsoft.VisualStudio.VC.Ide.ProjectSystem",
+ "Microsoft.VisualStudio.VC.Ide.ProjectSystem.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine",
+ "Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine.Resources",
+ "Microsoft.VisualStudio.VC.Ide.LanguageService.Resources",
+ "Microsoft.VisualStudio.VC.Llvm.Base",
+ "Microsoft.VisualStudio.VC.Ide.Base.Resources",
+ "Microsoft.Net.PackageGroup.4.8.1.Redist",
+ "Microsoft.VisualStudio.Component.IntelliCode",
+ "Microsoft.VisualStudio.IntelliCode.CSharp",
+ "Microsoft.VisualStudio.IntelliCode",
+ "Component.Microsoft.VisualStudio.LiveShare.2026",
+ "Microsoft.VisualStudio.Component.Debugger.JustInTime",
+ "Microsoft.VisualStudio.Debugger.ImmersiveActivateHelper.Msi",
+ "Microsoft.VisualStudio.Debugger.JustInTime",
+ "Microsoft.VisualStudio.Debugger.JustInTime.Msi",
+ "Microsoft.VisualStudio.LiveShare.2026",
+ "Microsoft.Icecap.Analysis",
+ "Microsoft.Icecap.Analysis.Resources",
+ "Microsoft.Icecap.Analysis.Resources.Targeted",
+ "Microsoft.Icecap.Collection.Msi",
+ "Microsoft.Icecap.Collection.Msi.Targeted",
+ "Microsoft.Icecap.Collection.Msi.Resources",
+ "Microsoft.Icecap.Collection.Msi.Resources.Targeted",
+ "Microsoft.DiagnosticsHub.Instrumentation",
+ "Microsoft.DiagnosticsHub.Instrumentation.Targeted",
+ "Microsoft.DiagnosticsHub.CpuSampling",
+ "Microsoft.DiagnosticsHub.CpuSampling.Targeted",
+ "Microsoft.PackageGroup.DiagnosticsHub.Platform",
+ "Microsoft.VisualStudio.InstrumentationEngine.ARM64",
+ "Microsoft.VisualStudio.InstrumentationEngine",
+ "Microsoft.DiagnosticsHub.Runtime.ExternalDependencies",
+ "SQLiteCore",
+ "SQLiteCore.Targeted",
+ "Microsoft.DiagnosticsHub.Runtime.ExternalDependencies.Targeted",
+ "Microsoft.DiagnosticsHub.Runtime",
+ "Microsoft.DiagnosticsHub.Runtime.Targeted",
+ "Microsoft.DiagnosticsHub.Collection.ExternalDependencies.arm64",
+ "Microsoft.DiagnosticsHub.Collection",
+ "Microsoft.DiagnosticsHub.Collection.Service",
+ "Microsoft.VisualStudio.VC.Ide.MDD",
+ "Microsoft.VisualStudio.VC.Ide.Linux.ConnectionManager",
+ "Microsoft.VisualStudio.VisualC.Utilities",
+ "Microsoft.VisualStudio.VisualC.Utilities.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Linux.ConnectionManager.Resources",
+ "Microsoft.VisualStudio.VC.Ide.ResourceEditor",
+ "Microsoft.VisualStudio.VC.Ide.ResourceEditor.Resources",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.Core",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.V2.CLI",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI",
+ "Microsoft.VisualStudio.TestTools.Pex.Common",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.V1.CLI",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.Legacy",
+ "Microsoft.VisualStudio.PackageGroup.MinShell.Interop",
+ "Microsoft.VisualStudio.TestTools.TP.Legacy.Tips.Msi",
+ "Microsoft.VisualStudio.TestTools.TP.Legacy.Tips.Common",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Tips",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Tips.Resources",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.TestSettings",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Professional",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Common",
+ "Microsoft.VisualStudio.TestTools.TP.Legacy.Common.Res",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Core",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Core.Resources",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Agent",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.IDE",
+ "Microsoft.VisualStudio.Cache.Service",
+ "Microsoft.VisualStudio.TestTools.TestWIExtension",
+ "Microsoft.VisualStudio.TestTools.TestWIExtension.Res",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.V1.CLI",
+ "Microsoft.VisualStudio.TestTools.TP.V1.CLI.Resources",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.IDE",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.CodeCoverage",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.DataCollectors",
+ "Microsoft.VisualStudio.Component.NuGet",
+ "Microsoft.CredentialProvider",
+ "Microsoft.VisualStudio.NuGet.Licenses",
+ "Microsoft.VisualStudio.Component.TextTemplating",
+ "Microsoft.VisualStudio.TextTemplating.MSBuild",
+ "Microsoft.VisualStudio.TextTemplating.Integration",
+ "Microsoft.VisualStudio.TextTemplating.Core",
+ "Microsoft.VisualStudio.TextTemplating.Integration.Resources",
+ "Microsoft.VisualCpp.CRT.ClickOnce.Msi",
+ "Microsoft.VisualStudio.Component.Roslyn.LanguageServices",
+ "Microsoft.VisualStudio.InteractiveWindow",
+ "Microsoft.DiaSymReader.Native",
+ "Microsoft.VisualCpp.Redist.14",
+ "Microsoft.VisualCpp.Redist.14",
+ "Microsoft.VisualCpp.Servicing.Redist",
+ "Microsoft.VisualStudio.PackageGroup.StaticAnalysis",
+ "Microsoft.VisualStudio.StaticAnalysis.IDE",
+ "Microsoft.VisualStudio.StaticAnalysis.IDE.Resources",
+ "Microsoft.VisualStudio.StaticAnalysis.FxCop.Resources",
+ "Microsoft.VisualStudio.StaticAnalysis.auxil",
+ "Microsoft.VisualStudio.StaticAnalysis.auxil.Resources",
+ "Roslyn.VisualStudio.Setup.ServiceHub",
+ "Microsoft.Component.MSBuild",
+ "Microsoft.NuGet.Build.Tasks.Setup",
+ "Microsoft.VisualStudio.Component.Roslyn.Compiler",
+ "Microsoft.CodeAnalysis.Compilers",
+ "Microsoft.VisualStudio.Component.JavaScript.TypeScript",
+ "Microsoft.VisualStudio.JavaScript.ProjectSystem",
+ "Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions",
+ "Microsoft.VisualStudio.ProTools",
+ "sqlsysclrtypes",
+ "SQLCommon",
+ "Microsoft.VisualStudio.ProTools.Resources",
+ "Microsoft.VisualStudio.Web.Scaffolding",
+ "Microsoft.VisualStudio.WebToolsExtensions",
+ "Microsoft.VisualStudio.ConnectedServices.Core",
+ "Microsoft.VisualStudio.WebTools",
+ "Microsoft.VisualStudio.WebToolsExtensions.MSBuild",
+ "Microsoft.VisualStudio.WebTools.Resources",
+ "Microsoft.VisualStudio.WebTools.WSP.FSA",
+ "Microsoft.VisualStudio.WebTools.WSP.FSA.Resources",
+ "Microsoft.VisualStudio.PackageGroup.Debugger.Script",
+ "Microsoft.VisualStudio.Component.TypeScript.TSServer",
+ "Microsoft.VisualStudio.Package.TypeScript.TSServer",
+ "Microsoft.VisualStudio.PackageGroup.JavaScript.Language",
+ "Microsoft.VisualStudio.Package.NodeJs",
+ "TypeScript.Build",
+ "TypeScript.LanguageService",
+ "TypeScript.Tools",
+ "Microsoft.VisualStudio.PackageGroup.Community",
+ "Microsoft.VisualStudio.Community.VB.x86",
+ "Microsoft.VisualStudio.Community.VB.x64",
+ "Microsoft.VisualStudio.PackageGroup.Core",
+ "Microsoft.VisualStudio.CodeSense.Community",
+ "Microsoft.VisualStudio.TestTools.TeamFoundationClient",
+ "Microsoft.VisualStudio.PackageGroup.Debugger.Core",
+ "Microsoft.VisualStudio.Debugger.BrokeredServices",
+ "Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost",
+ "Microsoft.VisualStudio.Debugger.AzureAttach",
+ "Microsoft.VisualStudio.Web.Azure.Common",
+ "Microsoft.WebTools.Shared",
+ "Microsoft.WebTools.DotNet.Core.ItemTemplates",
+ "Microsoft.VisualStudio.PackageGroup.Debugger.TimeTravel.Replay",
+ "Microsoft.VisualStudio.VC.Ide.Debugger",
+ "Microsoft.VisualStudio.VC.Ide.Debugger.Concord",
+ "Microsoft.VisualStudio.VC.Ide.Debugger.Concord.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Debugger.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Common",
+ "Microsoft.VisualStudio.VC.Ide.Common.Resources",
+ "Microsoft.VisualStudio.Debugger.CollectionAgents",
+ "Microsoft.VisualStudio.Debugger.Parallel",
+ "Microsoft.VisualStudio.Debugger.Parallel.Resources",
+ "Microsoft.VisualStudio.Debugger.Managed",
+ "Microsoft.CodeAnalysis.ExpressionEvaluator",
+ "Microsoft.CodeAnalysis.VisualStudio.Setup",
+ "Microsoft.VisualStudio.Debugger.Concord.Managed",
+ "Microsoft.VisualStudio.Debugger.Concord.Managed.Resources",
+ "Microsoft.VisualStudio.Debugger.Managed.Resources",
+ "Microsoft.VisualStudio.Debugger.TargetComposition",
+ "Microsoft.VisualStudio.Debugger.TargetComposition.Remote.arm64",
+ "Microsoft.VisualStudio.Debugger.TargetComposition.Remote",
+ "Microsoft.VisualStudio.Debugger.TargetComposition.Remote",
+ "Microsoft.VisualStudio.Debugger.Remote",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.Resources",
+ "Microsoft.VisualStudio.Debugger.Remote",
+ "Microsoft.VisualStudio.Debugger.Remote.ARM64",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.ARM64",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.Resources.ARM64",
+ "Microsoft.VisualStudio.Debugger.Remote.Resources.ARM",
+ "Microsoft.VisualStudio.Debugger.Remote.Resources.ARM64",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.Resources",
+ "Microsoft.VisualStudio.Debugger.Remote.Resources",
+ "Microsoft.VisualStudio.Debugger.Remote.Resources",
+ "Microsoft.VisualStudio.Debugger",
+ "Microsoft.VisualStudio.VC.MSVCDis",
+ "Microsoft.IntelliTrace.DiagnosticsHub",
+ "Microsoft.VisualStudio.Debugger.Concord",
+ "Microsoft.VisualStudio.Debugger.Concord.Resources",
+ "Microsoft.VisualStudio.Debugger.Resources",
+ "Microsoft.VisualStudio.Debugger.Package.DiagHub.Client",
+ "Microsoft.VisualStudio.Debugger.Remote.DiagnosticsHub.Client",
+ "Microsoft.VisualStudio.Debugger.Remote.DiagnosticsHub.Client",
+ "Microsoft.VisualStudio.Debugger.Remote.DiagnosticsHub.Client",
+ "Microsoft.PackageGroup.ClientDiagnostics",
+ "Microsoft.VisualStudio.AppResponsiveness",
+ "Microsoft.VisualStudio.AppResponsiveness.Targeted",
+ "Microsoft.VisualStudio.AppResponsiveness.Resources",
+ "Microsoft.VisualStudio.ClientDiagnostics",
+ "Microsoft.VisualStudio.ClientDiagnostics.Targeted",
+ "Microsoft.VisualStudio.ClientDiagnostics.Resources",
+ "Microsoft.VisualStudio.PackageGroup.CommunityCore",
+ "Microsoft.VisualStudio.ProjectSystem.Full",
+ "Microsoft.VisualStudio.LiveShareApi",
+ "Microsoft.VisualStudio.ProjectSystem.Query",
+ "Microsoft.VisualStudio.ProjectSystem",
+ "Microsoft.VisualStudio.Community.x86",
+ "Microsoft.VisualStudio.Community.x64",
+ "Microsoft.VisualStudio.Community.Msi.Resources",
+ "Microsoft.VisualStudio.Community.Msi",
+ "Microsoft.VisualStudio.Community.Shared.Msi",
+ "Microsoft.VisualStudio.Devenv.Msi",
+ "Microsoft.VisualStudio.Devenv.Shared.Msi",
+ "Microsoft.VisualStudio.MinShell.Interop.Msi",
+ "Microsoft.VisualStudio.MinShell.Interop.Shared.Msi",
+ "Microsoft.VisualStudio.Editors",
+ "Microsoft.VisualStudio.Workload.CoreEditor",
+ "Microsoft.VisualStudio.Component.CoreEditor",
+ "Microsoft.VisualStudio.PackageGroup.CoreEditor",
+ "Microsoft.WebView2",
+ "Microsoft.VisualStudio.ScriptedHost",
+ "Microsoft.VisualStudio.ScriptedHost.Targeted",
+ "Microsoft.VisualCpp.Tools.Common.UtilsPrereq",
+ "Microsoft.VisualCpp.Tools.Common.Utils",
+ "Microsoft.VisualCpp.Tools.Common.Utils.Resources",
+ "Microsoft.VisualStudio.PackageGroup.VsDevCmd",
+ "Microsoft.VisualStudio.VsDevCmd.Ext.NetFxSdk",
+ "Microsoft.VisualStudio.VsDevCmd.Core.WinSdk",
+ "Microsoft.VisualStudio.VsDevCmd.Core.DotNet",
+ "Microsoft.VisualStudio.VC.DevCmd",
+ "Microsoft.VisualStudio.VC.DevCmd.Resources",
+ "Microsoft.VisualStudio.VirtualTree",
+ "Microsoft.DiaSymReader",
+ "Microsoft.Build.Dependencies",
+ "Microsoft.Build.FileTracker.Msi",
+ "Microsoft.Build",
+ "Microsoft.VisualStudio.PackageGroup.NuGet",
+ "Microsoft.DataAI.NuGetRecommender",
+ "Microsoft.VisualStudio.NuGet.Core",
+ "Microsoft.Build.Arm64",
+ "Microsoft.Build.UnGAC",
+ "Microsoft.VisualStudio.TextMateGrammars",
+ "Microsoft.VisualStudio.Platform.Markdown",
+ "Microsoft.VisualStudio.Platform.CrossRepositorySearch",
+ "Microsoft.VisualStudio.PackageGroup.TeamExplorer.Common",
+ "Microsoft.VisualStudio.TeamExplorer",
+ "Microsoft.VisualStudio.PackageGroup.ServiceHub",
+ "Microsoft.ServiceHub.Node",
+ "Microsoft.ServiceHub.Managed",
+ "Microsoft.ServiceHub.arm64",
+ "Microsoft.VisualStudio.ProjectServices",
+ "Microsoft.VisualStudio.OpenFolder.VSIX",
+ "Microsoft.VisualStudio.FileHandler.Msi",
+ "Microsoft.VisualStudio.FileHandler.Msi",
+ "Microsoft.VisualStudio.PackageGroup.MinShell",
+ "Microsoft.VisualStudio.MinShell.Msi",
+ "Microsoft.VisualStudio.MinShell.Shared.Msi",
+ "Microsoft.VisualStudio.MinShell.Msi.Resources",
+ "Microsoft.VisualStudio.MinShell.Interop",
+ "CoreEditorFonts",
+ "Microsoft.VisualStudio.Log",
+ "Microsoft.VisualStudio.Log.Targeted",
+ "Microsoft.VisualStudio.Log.Resources",
+ "Microsoft.VisualStudio.Finalizer",
+ "Microsoft.VisualStudio.Devenv",
+ "Microsoft.VisualStudio.Devenv.Resources",
+ "Microsoft.VisualStudio.CoreEditor",
+ "Microsoft.VisualStudio.Navigation.RichCodeNav",
+ "Microsoft.VisualStudio.Platform.NavigateTo",
+ "Microsoft.VisualStudio.Connected",
+ "SQLitePCLRaw",
+ "SQLitePCLRaw.Targeted",
+ "Microsoft.VisualStudio.Connected.Auto",
+ "Microsoft.VisualStudio.Connected.Auto.Resources",
+ "Microsoft.VisualStudio.AzureSDK",
+ "Microsoft.VisualStudio.PerfLib",
+ "Microsoft.VisualStudio.Connected.Resources",
+ "Microsoft.Net.PackageGroup.4.8.Redist",
+ "Microsoft.VisualStudio.PackageGroup.Progression",
+ "Microsoft.VisualStudio.PerformanceProvider",
+ "Microsoft.VisualStudio.GraphModel",
+ "Microsoft.VisualStudio.GraphProvider",
+ "Microsoft.VisualStudio.Community.VB.Targeted",
+ "Microsoft.VisualStudio.Community.VB.Neutral",
+ "Microsoft.VisualStudio.Community.CSharp.Targeted",
+ "Microsoft.VisualStudio.Community.CSharp.Neutral",
+ "Microsoft.VisualStudio.Community.ProductArch.TargetedExtra",
+ "Microsoft.VisualStudio.Community.ProductArch.Targeted",
+ "Microsoft.VisualStudio.Community.ProductArch.NeutralExtra",
+ "Microsoft.DiaSymReader.PortablePdb",
+ "Microsoft.IntelliTrace.CollectorCab",
+ "Microsoft.VisualStudio.Community.VB.Resources.Targeted",
+ "Microsoft.VisualStudio.Community.VB.Resources.Neutral",
+ "Microsoft.VisualStudio.Community.CSharp.Resources.Targeted",
+ "Microsoft.VisualStudio.Community.CSharp.Resources.Neutral",
+ "Microsoft.VisualStudio.Community.ProductArch.Resources.Targeted",
+ "Microsoft.VisualStudio.Community.ProductArch.Resources.NeutralExtra",
+ "Microsoft.VisualStudio.Net.Eula.Resources",
+ "Microsoft.VisualStudio.Community.ProductArch.Resources.Neutral",
+ "Microsoft.VisualStudio.WebSiteProject.DTE",
+ "Microsoft.VisualStudio.Diagnostics.AspNetHelper",
+ "Microsoft.VisualStudio.Diagnostics.AspNetHelper.Standard",
+ "Microsoft.MSHtml",
+ "Microsoft.VisualStudio.Platform.CallHierarchy",
+ "Microsoft.VisualStudio.Community.ProductArch.Neutral",
+ "Microsoft.VisualStudio.MinShell",
+ "Microsoft.VisualStudio.VsWebProtocolSelector.Msi",
+ "Microsoft.Net.8.0.WindowsDesktop.Runtime",
+ "Microsoft.Net.8.0.Runtime",
+ "Microsoft.VisualStudio.PackageGroup.Setup.Common",
+ "Microsoft.VisualStudio.Setup.WMIProvider",
+ "Microsoft.VisualStudio.Setup.Configuration.Interop",
+ "Microsoft.VisualStudio.Setup.Configuration",
+ "Microsoft.VisualStudio.Extensibility.Container",
+ "Microsoft.VisualStudio.LanguageServer",
+ "Microsoft.VisualStudio.Platform.Terminal",
+ "Microsoft.VisualStudio.MefHosting",
+ "Microsoft.VisualStudio.Initializer",
+ "Microsoft.VisualStudio.ExtensionManager",
+ "Microsoft.VisualStudio.Platform.Editor",
+ "Microsoft.VisualStudio.MinShell.Targeted",
+ "Microsoft.VisualStudio.NativeImageSupport",
+ "Microsoft.VisualStudio.Devenv.Config",
+ "Microsoft.VisualStudio.MinShell.Resources.arm64",
+ "Microsoft.VisualStudio.MinShell.Auto",
+ "Microsoft.VisualStudio.MinShell.Auto.Resources",
+ "Microsoft.VisualStudio.Branding.Community"
+ ]
+ }
+]
diff --git a/test/fixtures/VS_2026_Insiders_workload.txt b/test/fixtures/VS_2026_Insiders_workload.txt
new file mode 100644
index 0000000000..4e20abb101
--- /dev/null
+++ b/test/fixtures/VS_2026_Insiders_workload.txt
@@ -0,0 +1,568 @@
+[
+ {
+ "path": "C:\\Program Files\\Microsoft Visual Studio\\18\\Insiders",
+ "version": "18.3.11206.111",
+ "packages": [
+ "Microsoft.VisualStudio.Product.Community",
+ "Microsoft.VisualStudio.PackageGroup.LiveShare.VSCore",
+ "Microsoft.VisualStudio.LiveShare.VSCore",
+ "Microsoft.VisualStudio.Workload.NativeDesktop",
+ "Microsoft.VisualStudio.Component.VC.ASAN",
+ "Microsoft.VisualCpp.ASAN.X86",
+ "Microsoft.VC.14.50.18.0.ASAN.X86.base",
+ "Microsoft.VC.14.50.18.0.ASAN.X64.base",
+ "Microsoft.VC.14.50.18.0.ASAN.Headers.base",
+ "Microsoft.VisualStudio.VC.IDE.Project.Factories",
+ "Microsoft.VisualStudio.Component.VC.TestAdapterForGoogleTest",
+ "Microsoft.VisualStudio.VC.Ide.TestAdapterForGoogleTest",
+ "Microsoft.VisualStudio.Component.VC.TestAdapterForBoostTest",
+ "Microsoft.VisualStudio.VC.Ide.TestAdapterForBoostTest",
+ "Microsoft.VisualStudio.Component.VC.CMake.Project",
+ "Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions.CMake",
+ "Microsoft.VisualStudio.VC.CMake",
+ "Microsoft.VisualStudio.VC.CMake.Project",
+ "Microsoft.VisualStudio.VC.CMake.Client",
+ "Microsoft.VisualStudio.VC.ExternalBuildFramework",
+ "Microsoft.VisualStudio.Component.VC.DiagnosticTools",
+ "Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.Native",
+ "Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
+ "Microsoft.VisualStudio.VC.Templates.UnitTest",
+ "Microsoft.VisualStudio.VC.UnitTest.Desktop.Build.Core",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.V1.CPP",
+ "Microsoft.VisualStudio.VC.Templates.UnitTest.Resources",
+ "Microsoft.VisualStudio.VC.Templates.Desktop",
+ "Microsoft.VisualStudio.Component.Graphics",
+ "Microsoft.VisualStudio.Graphics.Viewers",
+ "Microsoft.VisualStudio.Graphics.Viewers.Resources",
+ "Microsoft.VisualStudio.Component.VC.ATL.ARM64",
+ "Microsoft.VisualCpp.ATL.ARM64",
+ "Microsoft.VC.14.50.18.0.ATL.ARM64.base",
+ "Microsoft.VisualStudio.Component.VC.ATL",
+ "Microsoft.VisualStudio.VC.Ide.ATL",
+ "Microsoft.VisualStudio.VC.Ide.ATL.Resources",
+ "Microsoft.VisualCpp.ATL.X86",
+ "Microsoft.VC.14.50.18.0.ATL.X86.base",
+ "Microsoft.VisualCpp.ATL.X64",
+ "Microsoft.VC.14.50.18.0.ATL.X64.base",
+ "Microsoft.VC.14.50.18.0.Props.ATLMFC",
+ "Microsoft.VisualCpp.ATL.Source",
+ "Microsoft.VC.14.50.18.0.ATL.Source.base",
+ "Microsoft.VisualCpp.ATL.Headers",
+ "Microsoft.VC.14.50.18.0.ATL.Headers.base",
+ "Microsoft.VC.14.50.18.0.Servicing.ATL",
+ "Microsoft.VisualStudio.Component.VC.Tools.ARM64",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.ARM64.v145",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.ARM64",
+ "Microsoft.VS.VC.vcvars.arm64.Shortcuts",
+ "Microsoft.VisualCpp.CA.Ext.Hostx64.TargetARM64",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx64.TargetARM64.base",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx64.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.Hostx86.TargetARM64",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx86.TargetARM64.base",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx86.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.HostARM64.TargetARM64",
+ "Microsoft.VC.14.50.18.0.CA.Ext.HostARM64.TargetARM64.base",
+ "Microsoft.VC.14.50.18.0.CA.Ext.HostARM64.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.Tools.Hostx86.Targetarm64",
+ "Microsoft.VC.14.50.18.0.Tools.Hostx86.Targetarm64.base",
+ "Microsoft.VC.14.50.18.0.Tools.HostX86.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.Tools.HostARM64.TargetARM64",
+ "Microsoft.VC.14.50.18.0.Tools.HostARM64.TargetARM64.base",
+ "Microsoft.VC.14.50.18.0.Tools.HostARM64.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.CRT.Redist.ARM64.OneCore.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.Redist.ARM64.OneCore.Desktop.base",
+ "Microsoft.VisualCpp.CRT.Redist.ARM64",
+ "Microsoft.VC.14.50.18.0.CRT.Redist.ARM64.base",
+ "Microsoft.VisualCpp.CRT.ARM64.OneCore.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.ARM64.OneCore.Desktop.base",
+ "Microsoft.VC.14.50.18.0.CRT.ARM64.OneCore.Desktop.debug.base",
+ "Microsoft.VisualCpp.CRT.ARM64.Store",
+ "Microsoft.VC.14.50.18.0.CRT.ARM64.Store.base",
+ "Microsoft.VisualCpp.CRT.ARM64.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.ARM64.Desktop.base",
+ "Microsoft.VC.14.50.18.0.CRT.ARM64.Desktop.debug.base",
+ "Microsoft.VisualStudio.PackageGroup.VC.Tools.x64.ARM64",
+ "Microsoft.VisualCpp.Tools.Core",
+ "Microsoft.VisualCpp.PGO.ARM64",
+ "Microsoft.VC.14.50.18.0.PGO.ARM64.base",
+ "Microsoft.VisualCpp.Premium.Tools.Hostx86.Targetarm64",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.Hostx86.Targetarm64.base",
+ "Microsoft.VC.14.50.18.0.Prem.HostX86.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX64.TargetARM64",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.HostX64.TargetARM64.base",
+ "Microsoft.VC.14.50.18.0.Prem.HostX64.TargetARM64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.ARM64.Base",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.ARM64.Base.base",
+ "Microsoft.VisualCpp.Tools.HostX64.TargetARM64",
+ "Microsoft.VC.14.50.18.0.Tools.HostX64.TargetARM64.base",
+ "Microsoft.VC.14.50.18.0.Props.ARM64",
+ "Microsoft.VC.14.50.18.0.Tools.HostX64.TargetARM64.Res.base",
+ "Microsoft.VisualStudio.Component.VC.Tools.ARM64EC",
+ "Microsoft.VisualStudio.Component.Windows11SDK.26100",
+ "Win11SDK_10.0.26100",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.ARM64EC.v145",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.ARM64EC",
+ "Microsoft.VisualCpp.CRT.ARM64EC.Store",
+ "Microsoft.VC.14.50.18.0.CRT.ARM64EC.Store.base",
+ "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ "Microsoft.VisualCpp.CodeAnalysis.Extensions",
+ "Microsoft.VisualCpp.CA.Ext.HostARM64.Targetx64",
+ "Microsoft.VC.14.50.18.0.CA.Ext.HostARM64.Targetx64.base",
+ "Microsoft.VC.14.50.18.0.CA.Ext.HostARM64.Targetx64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.HostARM64.Targetx86",
+ "Microsoft.VC.14.50.18.0.CA.Ext.HostARM64.Targetx86.base",
+ "Microsoft.VC.14.50.18.0.CA.Ext.HostARM64.Targetx86.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.Hostx86.Targetx64",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx86.Targetx64.base",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx86.Targetx64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.Hostx86.Targetx86",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx86.Targetx86.base",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx86.Targetx86.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.Hostx64.Targetx64",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx64.Targetx64.base",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx64.Targetx64.Res.base",
+ "Microsoft.VisualCpp.CA.Ext.Hostx64.Targetx86",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx64.Targetx86.base",
+ "Microsoft.VC.14.50.18.0.Servicing.CAExtensions",
+ "Microsoft.VC.14.50.18.0.CA.Ext.Hostx64.Targetx86.Res.base",
+ "Microsoft.VisualCpp.Tools.HostX64.TargetX86",
+ "Microsoft.VC.14.50.18.0.Tools.HostX64.TargetX86.base",
+ "Microsoft.VC.14.50.18.0.Tools.HostX64.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Tools.HostX64.TargetX64",
+ "Microsoft.VC.14.50.18.0.Tools.HostX64.TargetX64.base",
+ "Microsoft.VC.14.50.18.0.Tools.HostX64.TargetX64.Res.base",
+ "Microsoft.VisualCpp.Tools.HostARM64.TargetX86",
+ "Microsoft.VC.14.50.18.0.Tools.HostARM64.TargetX86.base",
+ "Microsoft.VisualCpp.RuntimeDebug.14",
+ "Microsoft.VC.14.50.18.0.Tools.HostARM64.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Tools.HostARM64.TargetX64",
+ "Microsoft.VC.14.50.18.0.Tools.HostARM64.TargetX64.base",
+ "Microsoft.VisualCpp.RuntimeDebug.14.ARM64",
+ "Microsoft.VisualCpp.Redist.14.Latest",
+ "Microsoft.VisualCpp.Redist.14.Latest",
+ "Microsoft.VC.14.50.18.0.Tools.HostARM64.Targetx64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX64",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.HostX86.TargetX64.base",
+ "Microsoft.VC.14.50.18.0.Prem.Hostx86.Targetx64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX86.TargetX86",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.HostX86.TargetX86.base",
+ "Microsoft.VC.14.50.18.0.Prem.HostX86.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostARM64.TargetX86",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.HostARM64.TargetX86.base",
+ "Microsoft.VC.14.50.18.0.Prem.HostARM64.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostARM64.TargetX64",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.HostARM64.TargetX64.base",
+ "Microsoft.VC.14.50.18.0.Prem.HostARM64.Targetx64.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX86",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.HostX64.TargetX86.base",
+ "Microsoft.VC.14.50.18.0.Prem.HostX64.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Premium.Tools.HostX64.TargetX64",
+ "Microsoft.VC.14.50.18.0.Premium.Tools.HostX64.TargetX64.base",
+ "Microsoft.VC.14.50.18.0.Prem.HostX64.TargetX64.Res.base",
+ "Microsoft.VisualCpp.PGO.X86",
+ "Microsoft.VC.14.50.18.0.PGO.X86.base",
+ "Microsoft.VisualCpp.PGO.X64",
+ "Microsoft.VC.14.50.18.0.PGO.X64.base",
+ "Microsoft.VisualCpp.PGO.Headers",
+ "Microsoft.VC.14.50.18.0.PGO.Headers.base",
+ "Microsoft.VisualCpp.CRT.x86.Store",
+ "Microsoft.VC.14.50.18.0.CRT.x86.Store.base",
+ "Microsoft.VisualCpp.CRT.x86.OneCore.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.x86.OneCore.Desktop.base",
+ "Microsoft.VisualCpp.CRT.x64.Store",
+ "Microsoft.VC.14.50.18.0.CRT.x64.Store.base",
+ "Microsoft.VisualCpp.CRT.x64.OneCore.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.x64.OneCore.Desktop.base",
+ "Microsoft.VisualCpp.CRT.Redist.x86.OneCore.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.Redist.x86.OneCore.Desktop.base",
+ "Microsoft.VisualCpp.CRT.Redist.x64.OneCore.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.Redist.x64.OneCore.Desktop.base",
+ "Microsoft.VisualStudio.PackageGroup.VC.Tools.x86",
+ "Microsoft.VisualCpp.Tools.Hostx86.Targetx64.Res",
+ "Microsoft.VisualCpp.Tools.HostX86.TargetX64",
+ "Microsoft.VC.14.50.18.0.Tools.HostX86.TargetX64.base",
+ "Microsoft.VC.14.50.18.0.Props.x64",
+ "Microsoft.VC.14.50.18.0.Tools.Hostx86.Targetx64.Res.base",
+ "Microsoft.VisualCpp.Tools.HostX86.TargetX86.Res",
+ "Microsoft.VisualCpp.Tools.HostX86.TargetX86",
+ "Microsoft.VC.14.50.18.0.Tools.HostX86.TargetX86.base",
+ "Microsoft.VC.14.50.18.0.Servicing.Compilers",
+ "Microsoft.VC.14.50.18.0.Props.x86",
+ "Microsoft.VC.14.50.18.0.Props",
+ "Microsoft.VC.14.50.18.0.Tools.HostX86.TargetX86.Res.base",
+ "Microsoft.VisualCpp.Tools.Core.Resources",
+ "Microsoft.VisualCpp.Tools.Core.x86",
+ "Microsoft.VC.14.50.18.0.Tools.Core.Props",
+ "Microsoft.VisualCpp.DIA.SDK",
+ "Microsoft.VisualCpp.Servicing.DIASDK",
+ "Microsoft.VisualCpp.CRT.x86.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.x86.Desktop.base",
+ "Microsoft.VisualCpp.CRT.x64.Desktop",
+ "Microsoft.VC.14.50.18.0.CRT.x64.Desktop.base",
+ "Microsoft.VisualCpp.CRT.Source",
+ "Microsoft.VC.14.50.18.0.CRT.Source.base",
+ "Microsoft.VisualCpp.CRT.Redist.X86",
+ "Microsoft.VC.14.50.18.0.CRT.Redist.X86.base",
+ "Microsoft.VisualCpp.CRT.Redist.X64",
+ "Microsoft.VisualCpp.CRT.Redist.Resources",
+ "Microsoft.VC.14.50.18.0.CRT.Redist.X64.base",
+ "Microsoft.VisualCpp.CRT.Headers",
+ "Microsoft.VC.14.50.18.0.CRT.Headers.base",
+ "Microsoft.VC.14.50.18.0.Servicing.CrtHeaders",
+ "Microsoft.VC.14.50.18.0.Servicing",
+ "Microsoft.VisualStudio.Component.VC.CoreIde",
+ "Microsoft.VisualStudio.VC.Ide.Pro",
+ "Microsoft.VisualStudio.VC.Ide.Pro.Resources",
+ "Microsoft.VisualStudio.VC.Templates.General",
+ "Microsoft.VisualStudio.VC.Templates.General.Resources",
+ "Microsoft.VisualStudio.VC.Items.Pro",
+ "Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Reduced",
+ "Microsoft.VisualStudio.VC.Ide.x64",
+ "Microsoft.VisualStudio.PackageGroup.VC.CoreIDE.Express",
+ "Microsoft.VisualStudio.VC.vcvars",
+ "Microsoft.VS.VC.vcvars.x86.Shortcuts",
+ "Microsoft.VS.VC.vcvars.x64.Shortcuts",
+ "Microsoft.VS.VC.vcvars.arm64_x64.Shortcuts",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.X64.v145",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.X64",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.ARM.v145",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.ARM",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.x86.v145",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.X86",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.Base",
+ "Microsoft.VisualStudio.VC.MSBuild.v180.Base.Resources",
+ "Microsoft.VisualStudio.VC.Ide.WinXPlus",
+ "Microsoft.VisualStudio.VC.Ide.Dskx",
+ "Microsoft.VisualStudio.VC.Ide.Dskx.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Base",
+ "Microsoft.VisualStudio.VC.Ide.LanguageService",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.Scripts",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.PythonDistro",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.10",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.9",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.8",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.7",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.6",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.5",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.4",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.3",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.2",
+ "Microsoft.VisualStudio.VC.Ide.SecurityIssueAnalysis.3rdPartyLibs.1",
+ "Microsoft.VisualStudio.VC.Ide.VCPkgDatabase",
+ "Microsoft.VisualStudio.VC.Ide.Core",
+ "Microsoft.VisualStudio.VC.Ide.ProjectSystem",
+ "Microsoft.VisualStudio.VC.Ide.ProjectSystem.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine",
+ "Microsoft.VisualStudio.VC.Ide.Core.VCProjectEngine.Resources",
+ "Microsoft.VisualStudio.VC.Ide.LanguageService.Resources",
+ "Microsoft.VisualStudio.VC.Llvm.Base",
+ "Microsoft.VisualStudio.VC.Ide.Base.Resources",
+ "Microsoft.Net.PackageGroup.4.8.1.Redist",
+ "Microsoft.VisualStudio.Component.IntelliCode",
+ "Microsoft.VisualStudio.IntelliCode.CSharp",
+ "Microsoft.VisualStudio.IntelliCode",
+ "Component.Microsoft.VisualStudio.LiveShare.2026",
+ "Microsoft.VisualStudio.Component.Debugger.JustInTime",
+ "Microsoft.VisualStudio.Debugger.ImmersiveActivateHelper.Msi",
+ "Microsoft.VisualStudio.Debugger.JustInTime",
+ "Microsoft.VisualStudio.Debugger.JustInTime.Msi",
+ "Microsoft.VisualStudio.LiveShare.2026",
+ "Microsoft.Icecap.Analysis",
+ "Microsoft.Icecap.Analysis.Resources",
+ "Microsoft.Icecap.Analysis.Resources.Targeted",
+ "Microsoft.Icecap.Collection.Msi",
+ "Microsoft.Icecap.Collection.Msi.Targeted",
+ "Microsoft.Icecap.Collection.Msi.Resources",
+ "Microsoft.Icecap.Collection.Msi.Resources.Targeted",
+ "Microsoft.DiagnosticsHub.Instrumentation",
+ "Microsoft.DiagnosticsHub.Instrumentation.Targeted",
+ "Microsoft.DiagnosticsHub.CpuSampling",
+ "Microsoft.DiagnosticsHub.CpuSampling.Targeted",
+ "Microsoft.PackageGroup.DiagnosticsHub.Platform",
+ "Microsoft.VisualStudio.InstrumentationEngine.ARM64",
+ "Microsoft.VisualStudio.InstrumentationEngine",
+ "Microsoft.DiagnosticsHub.Runtime.ExternalDependencies",
+ "SQLiteCore",
+ "SQLiteCore.Targeted",
+ "Microsoft.DiagnosticsHub.Runtime.ExternalDependencies.Targeted",
+ "Microsoft.DiagnosticsHub.Runtime",
+ "Microsoft.DiagnosticsHub.Runtime.Targeted",
+ "Microsoft.DiagnosticsHub.Collection.ExternalDependencies.arm64",
+ "Microsoft.DiagnosticsHub.Collection",
+ "Microsoft.DiagnosticsHub.Collection.Service",
+ "Microsoft.VisualStudio.VC.Ide.MDD",
+ "Microsoft.VisualStudio.VC.Ide.Linux.ConnectionManager",
+ "Microsoft.VisualStudio.VisualC.Utilities",
+ "Microsoft.VisualStudio.VisualC.Utilities.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Linux.ConnectionManager.Resources",
+ "Microsoft.VisualStudio.VC.Ide.ResourceEditor",
+ "Microsoft.VisualStudio.VC.Ide.ResourceEditor.Resources",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.Core",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.V2.CLI",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI",
+ "Microsoft.VisualStudio.TestTools.Pex.Common",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.V1.CLI",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.Legacy",
+ "Microsoft.VisualStudio.PackageGroup.MinShell.Interop",
+ "Microsoft.VisualStudio.TestTools.TP.Legacy.Tips.Msi",
+ "Microsoft.VisualStudio.TestTools.TP.Legacy.Tips.Common",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Tips",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Tips.Resources",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.TestSettings",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Professional",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Common",
+ "Microsoft.VisualStudio.TestTools.TP.Legacy.Common.Res",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Core",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Core.Resources",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.Legacy.Agent",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.TestPlatform.IDE",
+ "Microsoft.VisualStudio.Cache.Service",
+ "Microsoft.VisualStudio.TestTools.TestWIExtension",
+ "Microsoft.VisualStudio.TestTools.TestWIExtension.Res",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.V1.CLI",
+ "Microsoft.VisualStudio.TestTools.TP.V1.CLI.Resources",
+ "Microsoft.VisualStudio.TestTools.TestPlatform.IDE",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.CodeCoverage",
+ "Microsoft.VisualStudio.PackageGroup.TestTools.DataCollectors",
+ "Microsoft.VisualStudio.Component.NuGet",
+ "Microsoft.CredentialProvider",
+ "Microsoft.VisualStudio.NuGet.Licenses",
+ "Microsoft.VisualStudio.Component.TextTemplating",
+ "Microsoft.VisualStudio.TextTemplating.MSBuild",
+ "Microsoft.VisualStudio.TextTemplating.Integration",
+ "Microsoft.VisualStudio.TextTemplating.Core",
+ "Microsoft.VisualStudio.TextTemplating.Integration.Resources",
+ "Microsoft.VisualCpp.CRT.ClickOnce.Msi",
+ "Microsoft.VisualStudio.Component.Roslyn.LanguageServices",
+ "Microsoft.VisualStudio.InteractiveWindow",
+ "Microsoft.DiaSymReader.Native",
+ "Microsoft.VisualCpp.Redist.14",
+ "Microsoft.VisualCpp.Redist.14",
+ "Microsoft.VisualCpp.Servicing.Redist",
+ "Microsoft.VisualStudio.PackageGroup.StaticAnalysis",
+ "Microsoft.VisualStudio.StaticAnalysis.IDE",
+ "Microsoft.VisualStudio.StaticAnalysis.IDE.Resources",
+ "Microsoft.VisualStudio.StaticAnalysis.FxCop.Resources",
+ "Microsoft.VisualStudio.StaticAnalysis.auxil",
+ "Microsoft.VisualStudio.StaticAnalysis.auxil.Resources",
+ "Roslyn.VisualStudio.Setup.ServiceHub",
+ "Microsoft.Component.MSBuild",
+ "Microsoft.NuGet.Build.Tasks.Setup",
+ "Microsoft.VisualStudio.Component.Roslyn.Compiler",
+ "Microsoft.CodeAnalysis.Compilers",
+ "Microsoft.VisualStudio.Component.JavaScript.TypeScript",
+ "Microsoft.VisualStudio.JavaScript.ProjectSystem",
+ "Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions",
+ "Microsoft.VisualStudio.ProTools",
+ "sqlsysclrtypes",
+ "SQLCommon",
+ "Microsoft.VisualStudio.ProTools.Resources",
+ "Microsoft.VisualStudio.Web.Scaffolding",
+ "Microsoft.VisualStudio.WebToolsExtensions",
+ "Microsoft.VisualStudio.ConnectedServices.Core",
+ "Microsoft.VisualStudio.WebTools",
+ "Microsoft.VisualStudio.WebToolsExtensions.MSBuild",
+ "Microsoft.VisualStudio.WebTools.Resources",
+ "Microsoft.VisualStudio.WebTools.WSP.FSA",
+ "Microsoft.VisualStudio.WebTools.WSP.FSA.Resources",
+ "Microsoft.VisualStudio.PackageGroup.Debugger.Script",
+ "Microsoft.VisualStudio.Component.TypeScript.TSServer",
+ "Microsoft.VisualStudio.Package.TypeScript.TSServer",
+ "Microsoft.VisualStudio.PackageGroup.JavaScript.Language",
+ "Microsoft.VisualStudio.Package.NodeJs",
+ "TypeScript.Build",
+ "TypeScript.LanguageService",
+ "TypeScript.Tools",
+ "Microsoft.VisualStudio.PackageGroup.Community",
+ "Microsoft.VisualStudio.Community.VB.x86",
+ "Microsoft.VisualStudio.Community.VB.x64",
+ "Microsoft.VisualStudio.PackageGroup.Core",
+ "Microsoft.VisualStudio.CodeSense.Community",
+ "Microsoft.VisualStudio.TestTools.TeamFoundationClient",
+ "Microsoft.VisualStudio.PackageGroup.Debugger.Core",
+ "Microsoft.VisualStudio.Debugger.BrokeredServices",
+ "Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost",
+ "Microsoft.VisualStudio.Debugger.AzureAttach",
+ "Microsoft.VisualStudio.Web.Azure.Common",
+ "Microsoft.WebTools.Shared",
+ "Microsoft.WebTools.DotNet.Core.ItemTemplates",
+ "Microsoft.VisualStudio.PackageGroup.Debugger.TimeTravel.Replay",
+ "Microsoft.VisualStudio.VC.Ide.Debugger",
+ "Microsoft.VisualStudio.VC.Ide.Debugger.Concord",
+ "Microsoft.VisualStudio.VC.Ide.Debugger.Concord.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Debugger.Resources",
+ "Microsoft.VisualStudio.VC.Ide.Common",
+ "Microsoft.VisualStudio.VC.Ide.Common.Resources",
+ "Microsoft.VisualStudio.Debugger.CollectionAgents",
+ "Microsoft.VisualStudio.Debugger.Parallel",
+ "Microsoft.VisualStudio.Debugger.Parallel.Resources",
+ "Microsoft.VisualStudio.Debugger.Managed",
+ "Microsoft.CodeAnalysis.ExpressionEvaluator",
+ "Microsoft.CodeAnalysis.VisualStudio.Setup",
+ "Microsoft.VisualStudio.Debugger.Concord.Managed",
+ "Microsoft.VisualStudio.Debugger.Concord.Managed.Resources",
+ "Microsoft.VisualStudio.Debugger.Managed.Resources",
+ "Microsoft.VisualStudio.Debugger.TargetComposition",
+ "Microsoft.VisualStudio.Debugger.TargetComposition.Remote.arm64",
+ "Microsoft.VisualStudio.Debugger.TargetComposition.Remote",
+ "Microsoft.VisualStudio.Debugger.TargetComposition.Remote",
+ "Microsoft.VisualStudio.Debugger.Remote",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.Resources",
+ "Microsoft.VisualStudio.Debugger.Remote",
+ "Microsoft.VisualStudio.Debugger.Remote.ARM64",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.ARM64",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.Resources.ARM64",
+ "Microsoft.VisualStudio.Debugger.Remote.Resources.ARM",
+ "Microsoft.VisualStudio.Debugger.Remote.Resources.ARM64",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote",
+ "Microsoft.VisualStudio.Debugger.Concord.Remote.Resources",
+ "Microsoft.VisualStudio.Debugger.Remote.Resources",
+ "Microsoft.VisualStudio.Debugger.Remote.Resources",
+ "Microsoft.VisualStudio.Debugger",
+ "Microsoft.VisualStudio.VC.MSVCDis",
+ "Microsoft.IntelliTrace.DiagnosticsHub",
+ "Microsoft.VisualStudio.Debugger.Concord",
+ "Microsoft.VisualStudio.Debugger.Concord.Resources",
+ "Microsoft.VisualStudio.Debugger.Resources",
+ "Microsoft.VisualStudio.Debugger.Package.DiagHub.Client",
+ "Microsoft.VisualStudio.Debugger.Remote.DiagnosticsHub.Client",
+ "Microsoft.VisualStudio.Debugger.Remote.DiagnosticsHub.Client",
+ "Microsoft.VisualStudio.Debugger.Remote.DiagnosticsHub.Client",
+ "Microsoft.PackageGroup.ClientDiagnostics",
+ "Microsoft.VisualStudio.AppResponsiveness",
+ "Microsoft.VisualStudio.AppResponsiveness.Targeted",
+ "Microsoft.VisualStudio.AppResponsiveness.Resources",
+ "Microsoft.VisualStudio.ClientDiagnostics",
+ "Microsoft.VisualStudio.ClientDiagnostics.Targeted",
+ "Microsoft.VisualStudio.ClientDiagnostics.Resources",
+ "Microsoft.VisualStudio.PackageGroup.CommunityCore",
+ "Microsoft.VisualStudio.ProjectSystem.Full",
+ "Microsoft.VisualStudio.LiveShareApi",
+ "Microsoft.VisualStudio.ProjectSystem.Query",
+ "Microsoft.VisualStudio.ProjectSystem",
+ "Microsoft.VisualStudio.Community.x86",
+ "Microsoft.VisualStudio.Community.x64",
+ "Microsoft.VisualStudio.Community.Msi.Resources",
+ "Microsoft.VisualStudio.Community.Msi",
+ "Microsoft.VisualStudio.Community.Shared.Msi",
+ "Microsoft.VisualStudio.Devenv.Msi",
+ "Microsoft.VisualStudio.Devenv.Shared.Msi",
+ "Microsoft.VisualStudio.MinShell.Interop.Msi",
+ "Microsoft.VisualStudio.MinShell.Interop.Shared.Msi",
+ "Microsoft.VisualStudio.Editors",
+ "Microsoft.VisualStudio.Workload.CoreEditor",
+ "Microsoft.VisualStudio.Component.CoreEditor",
+ "Microsoft.VisualStudio.PackageGroup.CoreEditor",
+ "Microsoft.WebView2",
+ "Microsoft.VisualStudio.ScriptedHost",
+ "Microsoft.VisualStudio.ScriptedHost.Targeted",
+ "Microsoft.VisualCpp.Tools.Common.UtilsPrereq",
+ "Microsoft.VisualCpp.Tools.Common.Utils",
+ "Microsoft.VisualCpp.Tools.Common.Utils.Resources",
+ "Microsoft.VisualStudio.PackageGroup.VsDevCmd",
+ "Microsoft.VisualStudio.VsDevCmd.Ext.NetFxSdk",
+ "Microsoft.VisualStudio.VsDevCmd.Core.WinSdk",
+ "Microsoft.VisualStudio.VsDevCmd.Core.DotNet",
+ "Microsoft.VisualStudio.VC.DevCmd",
+ "Microsoft.VisualStudio.VC.DevCmd.Resources",
+ "Microsoft.VisualStudio.VirtualTree",
+ "Microsoft.DiaSymReader",
+ "Microsoft.Build.Dependencies",
+ "Microsoft.Build.FileTracker.Msi",
+ "Microsoft.Build",
+ "Microsoft.VisualStudio.PackageGroup.NuGet",
+ "Microsoft.DataAI.NuGetRecommender",
+ "Microsoft.VisualStudio.NuGet.Core",
+ "Microsoft.Build.Arm64",
+ "Microsoft.Build.UnGAC",
+ "Microsoft.VisualStudio.TextMateGrammars",
+ "Microsoft.VisualStudio.Platform.Markdown",
+ "Microsoft.VisualStudio.Platform.CrossRepositorySearch",
+ "Microsoft.VisualStudio.PackageGroup.TeamExplorer.Common",
+ "Microsoft.VisualStudio.TeamExplorer",
+ "Microsoft.VisualStudio.PackageGroup.ServiceHub",
+ "Microsoft.ServiceHub.Node",
+ "Microsoft.ServiceHub.Managed",
+ "Microsoft.ServiceHub.arm64",
+ "Microsoft.VisualStudio.ProjectServices",
+ "Microsoft.VisualStudio.OpenFolder.VSIX",
+ "Microsoft.VisualStudio.FileHandler.Msi",
+ "Microsoft.VisualStudio.FileHandler.Msi",
+ "Microsoft.VisualStudio.PackageGroup.MinShell",
+ "Microsoft.VisualStudio.MinShell.Msi",
+ "Microsoft.VisualStudio.MinShell.Shared.Msi",
+ "Microsoft.VisualStudio.MinShell.Msi.Resources",
+ "Microsoft.VisualStudio.MinShell.Interop",
+ "CoreEditorFonts",
+ "Microsoft.VisualStudio.Log",
+ "Microsoft.VisualStudio.Log.Targeted",
+ "Microsoft.VisualStudio.Log.Resources",
+ "Microsoft.VisualStudio.Finalizer",
+ "Microsoft.VisualStudio.Devenv",
+ "Microsoft.VisualStudio.Devenv.Resources",
+ "Microsoft.VisualStudio.CoreEditor",
+ "Microsoft.VisualStudio.Navigation.RichCodeNav",
+ "Microsoft.VisualStudio.Platform.NavigateTo",
+ "Microsoft.VisualStudio.Connected",
+ "SQLitePCLRaw",
+ "SQLitePCLRaw.Targeted",
+ "Microsoft.VisualStudio.Connected.Auto",
+ "Microsoft.VisualStudio.Connected.Auto.Resources",
+ "Microsoft.VisualStudio.AzureSDK",
+ "Microsoft.VisualStudio.PerfLib",
+ "Microsoft.VisualStudio.Connected.Resources",
+ "Microsoft.Net.PackageGroup.4.8.Redist",
+ "Microsoft.VisualStudio.PackageGroup.Progression",
+ "Microsoft.VisualStudio.PerformanceProvider",
+ "Microsoft.VisualStudio.GraphModel",
+ "Microsoft.VisualStudio.GraphProvider",
+ "Microsoft.VisualStudio.Community.VB.Targeted",
+ "Microsoft.VisualStudio.Community.VB.Neutral",
+ "Microsoft.VisualStudio.Community.CSharp.Targeted",
+ "Microsoft.VisualStudio.Community.CSharp.Neutral",
+ "Microsoft.VisualStudio.Community.ProductArch.TargetedExtra",
+ "Microsoft.VisualStudio.Community.ProductArch.Targeted",
+ "Microsoft.VisualStudio.Community.ProductArch.NeutralExtra",
+ "Microsoft.DiaSymReader.PortablePdb",
+ "Microsoft.IntelliTrace.CollectorCab",
+ "Microsoft.VisualStudio.Community.VB.Resources.Targeted",
+ "Microsoft.VisualStudio.Community.VB.Resources.Neutral",
+ "Microsoft.VisualStudio.Community.CSharp.Resources.Targeted",
+ "Microsoft.VisualStudio.Community.CSharp.Resources.Neutral",
+ "Microsoft.VisualStudio.Community.ProductArch.Resources.Targeted",
+ "Microsoft.VisualStudio.Community.ProductArch.Resources.NeutralExtra",
+ "Microsoft.VisualStudio.Net.Eula.Resources",
+ "Microsoft.VisualStudio.Community.ProductArch.Resources.Neutral",
+ "Microsoft.VisualStudio.WebSiteProject.DTE",
+ "Microsoft.VisualStudio.Diagnostics.AspNetHelper",
+ "Microsoft.VisualStudio.Diagnostics.AspNetHelper.Standard",
+ "Microsoft.MSHtml",
+ "Microsoft.VisualStudio.Platform.CallHierarchy",
+ "Microsoft.VisualStudio.Community.ProductArch.Neutral",
+ "Microsoft.VisualStudio.MinShell",
+ "Microsoft.VisualStudio.VsWebProtocolSelector.Msi",
+ "Microsoft.Net.8.0.WindowsDesktop.Runtime",
+ "Microsoft.Net.8.0.Runtime",
+ "Microsoft.VisualStudio.PackageGroup.Setup.Common",
+ "Microsoft.VisualStudio.Setup.WMIProvider",
+ "Microsoft.VisualStudio.Setup.Configuration.Interop",
+ "Microsoft.VisualStudio.Setup.Configuration",
+ "Microsoft.VisualStudio.Extensibility.Container",
+ "Microsoft.VisualStudio.LanguageServer",
+ "Microsoft.VisualStudio.Platform.Terminal",
+ "Microsoft.VisualStudio.MefHosting",
+ "Microsoft.VisualStudio.Initializer",
+ "Microsoft.VisualStudio.ExtensionManager",
+ "Microsoft.VisualStudio.Platform.Editor",
+ "Microsoft.VisualStudio.MinShell.Targeted",
+ "Microsoft.VisualStudio.NativeImageSupport",
+ "Microsoft.VisualStudio.Devenv.Config",
+ "Microsoft.VisualStudio.MinShell.Resources.arm64",
+ "Microsoft.VisualStudio.MinShell.Auto",
+ "Microsoft.VisualStudio.MinShell.Auto.Resources",
+ "Microsoft.VisualStudio.Branding.Community"
+ ]
+ }
+]
diff --git a/test/fixtures/ca-bundle.crt b/test/fixtures/ca-bundle.crt
deleted file mode 100644
index fb1dea98a7..0000000000
--- a/test/fixtures/ca-bundle.crt
+++ /dev/null
@@ -1,40 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDJjCCAg4CAhnOMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNVBAYTAlVTMQswCQYD
-VQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEZMBcGA1UECgwQU3Ryb25n
-TG9vcCwgSW5jLjESMBAGA1UECwwJU3Ryb25nT3BzMRowGAYDVQQDDBFjYS5zdHJv
-bmdsb29wLmNvbTAeFw0xNTEyMDgyMzM1MzNaFw00MzA0MjQyMzM1MzNaMBkxFzAV
-BgNVBAMMDnN0cm9uZ2xvb3AuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
-CgKCAQEAwOYI7OZ2FX/YjRgLZoDQlbPc5UZXU/j0e1wwiJNPtPEax9Y5Uoza0Pnt
-Ikzkc2SfvQ+IJrhXo385tI0W5juuqbHnE7UrjUuPjUX6NHevkxcs/flmjan5wnZM
-cPsGhH71WDuUEEflvZihf2Se2x+xgZtMhc5XGmVmRuZFYKvkgUhA2/w8/QrK+jPT
-n9QRJxZjWNh2RBdC1B7u4jffSmOSUljYFH1I2eTeY+Rdi6YUIYSU9gEoZxsv3Tia
-SomfMF5jt2Mouo6MzA+IhLvvFjcrcph1Qxgi9RkfdCMMd+Ipm9YWELkyG1bDRpQy
-0iyHD4gvVsAqz1Y2KdRSdc3Kt+nTqwIDAQABoxkwFzAVBgNVHREEDjAMhwQAAAAA
-hwR/AAABMA0GCSqGSIb3DQEBBQUAA4IBAQAhy4J0hML3NgmDRHdL5/iTucBe22Mf
-jJjg2aifD1S187dHm+Il4qZNO2plWwAhN0h704f+8wpsaALxUvBIu6nvlvcMP5PH
-jGN5JLe2Km3UaPvYOQU2SgacLilu+uBcIo2JSHLV6O7ziqUj5Gior6YxDLCtEZie
-Ea8aX5/YjuACtEMJ1JjRqjgkM66XAoUe0E8onOK3FgTIO3tGoTJwRp0zS50pFuP0
-PsZtT04ck6mmXEXXknNoAyBCvPypfms9OHqcUIW9fiQnrGbS/Ri4QSQYj0DtFk/1
-na4fY1gf3zTHxH8259b/TOOaPfTnCEsOQtjUrWNR4xhmVZ+HJy4yytUW
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDbzCCAlcCAmm6MA0GCSqGSIb3DQEBCwUAMH0xCzAJBgNVBAYTAlVTMQswCQYD
-VQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEZMBcGA1UECgwQU3Ryb25n
-TG9vcCwgSW5jLjESMBAGA1UECwwJU3Ryb25nT3BzMRowGAYDVQQDDBFjYS5zdHJv
-bmdsb29wLmNvbTAeFw0xNTEyMDgyMzM1MzNaFw00MzA0MjQyMzM1MzNaMH0xCzAJ
-BgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEZ
-MBcGA1UECgwQU3Ryb25nTG9vcCwgSW5jLjESMBAGA1UECwwJU3Ryb25nT3BzMRow
-GAYDVQQDDBFjYS5zdHJvbmdsb29wLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP
-ADCCAQoCggEBANfj86jkvvYDjHBgiqWhk9Cj+bqiMq3MqnV0CBO4iuK33Fo6XssE
-H+yVdXlIBFbFe6t655MdBVOR2Sfj7WqNh96vhu6PyDHiwcQlTaiLU6nhIed1J4Wv
-lvnJHFmp8Wbtx5AgLT4UYu03ftvXEl2DLi3vhSL2tRM1ebXHB/KPbRWkb25DPX0P
-foOHot3f2dgNe2x6kponf7E/QDmAu3s7Nlkfh+ryDhgGU7wocXEhXbprNqRqOGNo
-xbXgUI+/9XDxYT/7Gn5LF/fPjtN+aB0SKMnTsDhprVlZie83mlqJ46fOOrR+vrsQ
-mi/1m/TadrARtZoIExC/cQRdVM05EK4tUa8CAwEAATANBgkqhkiG9w0BAQsFAAOC
-AQEAQ7k5WhyhDTIGYCNzRnrMHWSzGqa1y4tJMW06wafJNRqTm1cthq1ibc6Hfq5a
-K10K0qMcgauRTfQ1MWrVCTW/KnJ1vkhiTOH+RvxapGn84gSaRmV6KZen0+gMsgae
-KEGe/3Hn+PmDVV+PTamHgPACfpTww38WHIe/7Ce9gHfG7MZ8cKHNZhDy0IAYPln+
-YRwMLd7JNQffHAbWb2CE1mcea4H/12U8JZW5tHCF6y9V+7IuDzqwIrLKcW3lG17n
-VUG6ODF/Ryqn3V5X+TL91YyXi6c34y34IpC7MQDV/67U7+5Bp5CfeDPWW2wVSrW+
-uGZtfEvhbNm6m2i4UNmpCXxUZQ==
------END CERTIFICATE-----
diff --git a/test/fixtures/ca.crt b/test/fixtures/ca.crt
deleted file mode 100644
index 9d2755a74f..0000000000
--- a/test/fixtures/ca.crt
+++ /dev/null
@@ -1,21 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDbzCCAlcCAmm6MA0GCSqGSIb3DQEBCwUAMH0xCzAJBgNVBAYTAlVTMQswCQYD
-VQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEZMBcGA1UECgwQU3Ryb25n
-TG9vcCwgSW5jLjESMBAGA1UECwwJU3Ryb25nT3BzMRowGAYDVQQDDBFjYS5zdHJv
-bmdsb29wLmNvbTAeFw0xNTEyMDgyMzM1MzNaFw00MzA0MjQyMzM1MzNaMH0xCzAJ
-BgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEZ
-MBcGA1UECgwQU3Ryb25nTG9vcCwgSW5jLjESMBAGA1UECwwJU3Ryb25nT3BzMRow
-GAYDVQQDDBFjYS5zdHJvbmdsb29wLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP
-ADCCAQoCggEBANfj86jkvvYDjHBgiqWhk9Cj+bqiMq3MqnV0CBO4iuK33Fo6XssE
-H+yVdXlIBFbFe6t655MdBVOR2Sfj7WqNh96vhu6PyDHiwcQlTaiLU6nhIed1J4Wv
-lvnJHFmp8Wbtx5AgLT4UYu03ftvXEl2DLi3vhSL2tRM1ebXHB/KPbRWkb25DPX0P
-foOHot3f2dgNe2x6kponf7E/QDmAu3s7Nlkfh+ryDhgGU7wocXEhXbprNqRqOGNo
-xbXgUI+/9XDxYT/7Gn5LF/fPjtN+aB0SKMnTsDhprVlZie83mlqJ46fOOrR+vrsQ
-mi/1m/TadrARtZoIExC/cQRdVM05EK4tUa8CAwEAATANBgkqhkiG9w0BAQsFAAOC
-AQEAQ7k5WhyhDTIGYCNzRnrMHWSzGqa1y4tJMW06wafJNRqTm1cthq1ibc6Hfq5a
-K10K0qMcgauRTfQ1MWrVCTW/KnJ1vkhiTOH+RvxapGn84gSaRmV6KZen0+gMsgae
-KEGe/3Hn+PmDVV+PTamHgPACfpTww38WHIe/7Ce9gHfG7MZ8cKHNZhDy0IAYPln+
-YRwMLd7JNQffHAbWb2CE1mcea4H/12U8JZW5tHCF6y9V+7IuDzqwIrLKcW3lG17n
-VUG6ODF/Ryqn3V5X+TL91YyXi6c34y34IpC7MQDV/67U7+5Bp5CfeDPWW2wVSrW+
-uGZtfEvhbNm6m2i4UNmpCXxUZQ==
------END CERTIFICATE-----
diff --git a/test/fixtures/certs.js b/test/fixtures/certs.js
new file mode 100644
index 0000000000..f03f1de0d7
--- /dev/null
+++ b/test/fixtures/certs.js
@@ -0,0 +1,151 @@
+module.exports['ca.key'] = `
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAtTbG0k2UFUyCdZuip0TTEtXRHh57qosegrpHPBreSNTxt7OT
+KfOUZp2rToTHeN9w0ZbV2eKRI5AuFx8Cmlm73/KIHKzSNTBATGMeeHnGaxvL/W/s
+KJdTDRNf7/qCXHQ+gsuEWWCFzOZuHmmAQa2IBX2HAQTqXJI8+2iJ9gytFfJLxjqy
+6O4u9ugZVHSyQJWs49tGRcWMlNm7EMStADFvJn3S11xe/kwIA2mSI/eddDnzL0Mx
+AkR9dQBL66xOABLL5v3QQdhipfHluX6HLbDd/1YsFTuOpgvLRlr72rTAFrQZCokV
+hXPiqstn5zJFW5arHakvMR0+OPaICF5feh/4qQIDAQABAoIBAHWg6exnWUF+GY0Y
+CrwDS/QFASpI5UNt7M809bqJQlMKjyEMmvF3YJQ/soxUWlsWx1f1TjmR/V6VX6W4
+hmsE5pRXDY13jTfja0lqacQQYAD02TRY63XpzIpHUlYnSWmUN2OVkgKmShQYW9C3
+8P4xE4Nk2TaLJ0oRzy3uzOb/kXcVaJfknBRUnOhuaTSs+w4l4pPXueYA7xuHgVsL
+Qq0S4kK+PmdwCMB7gzlAAQhCM3vQ1U4cjC9JIIKSmPy7BcvD0kBfVPIFQ2byGpA1
+VkWBLSyeig0YxA5oIshK5cLiDIfBIiCSEzm4AMhVhGf0tbGEwiPljxKjbarYUUIi
+ATMk83UCgYEA7kKeOveuPbMqxmT42swfa9OU5jLUjH+VExU0Kv3BbEjv/OGt0fac
+/cs1Ze3vnrtCHudVajocFjydb8B4c62DbA4/T+LcUw/HaMaORbOoICQidi/zZ1Lj
+gjg8Ip2WKXEhSAwqUpaFd6w16NZOxiTh+NDaRKywwbe8j57eDH4uR6MCgYEAwrTS
+q5ra6+WDGUFMs0y3GMbL8j14PGhxBQBYSTM//NysI+EM6eeKn1cV3BbphEw//jgE
+0pVokkjvLAQWWEG2dZyRxRE3YAMgOAIPx5zbJCim3iBVuoqY9ckLg2jF8Fqqubsb
+3Rf2/Xzn/rFqsXdhsjGcJpdN66T9aEjwEkAnc0MCgYA5cOYk4UGormFJo147oaqR
+nFjxhp+nn7qY9yu0kajoKk1xchct337J0Qv2nv5+DjdKrArzqT7MPaDXKFfhy5s7
+mdO5tr/XZp50rCnws/d8iDmmtLjB2EHxSw10avmg1B1p+UTa1F8pEuOMVt529r1j
+9zYoCFo02c8j8PEnoeQWcQKBgQCVBCuQZu5SSM/zTkTTnU0sy0lf1qflI9IMD92B
++JVqg8HDnAR0KF+x38a9MVP7ixgXCuy19t+XxfY269HmLjTlArWV671D4GCSPRGy
+plwZ6nr72ieCo3y57+q94jxL3jh3+bozlpnUG/q6tTKBLGs7JDjsWDSsuxOu8tO6
+RBttXQKBgB6LQFOTjDMfsFHKsnQXFUZId3GG/iLg3WCWxEo88T5Rq3JIR0zDpW8H
+cKhl/sPY+JVHsxizNCMPtp7Hn7GrB6D/v9LbO0jpG2U0BFiJ6zhiDopbP9B0EAW4
+5JJ+JGKRKoCxs3DmSVyns0gU4j4rVte97UWyVy5TZ8Acr/qrgOA1
+-----END RSA PRIVATE KEY-----
+`
+
+module.exports['ca.crt'] = `
+-----BEGIN CERTIFICATE-----
+MIIDmzCCAoOgAwIBAgIUDA0GrvcnG41XT6LYFeNwvq8YV1UwDQYJKoZIhvcNAQEL
+BQAwXTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRAwDgYDVQQKDAdOb2RlLmpz
+MREwDwYDVQQLDAhub2RlLWd5cDEcMBoGA1UEAwwTbm9kZS1neXAubm9kZWpzLm9y
+ZzAeFw0yMjA1MTEwNDIyMjRaFw00OTA5MjUwNDIyMjRaMF0xCzAJBgNVBAYTAlVT
+MQswCQYDVQQIDAJDQTEQMA4GA1UECgwHTm9kZS5qczERMA8GA1UECwwIbm9kZS1n
+eXAxHDAaBgNVBAMME25vZGUtZ3lwLm5vZGVqcy5vcmcwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQC1NsbSTZQVTIJ1m6KnRNMS1dEeHnuqix6Cukc8Gt5I
+1PG3s5Mp85RmnatOhMd433DRltXZ4pEjkC4XHwKaWbvf8ogcrNI1MEBMYx54ecZr
+G8v9b+wol1MNE1/v+oJcdD6Cy4RZYIXM5m4eaYBBrYgFfYcBBOpckjz7aIn2DK0V
+8kvGOrLo7i726BlUdLJAlazj20ZFxYyU2bsQxK0AMW8mfdLXXF7+TAgDaZIj9510
+OfMvQzECRH11AEvrrE4AEsvm/dBB2GKl8eW5foctsN3/ViwVO46mC8tGWvvatMAW
+tBkKiRWFc+Kqy2fnMkVblqsdqS8xHT449ogIXl96H/ipAgMBAAGjUzBRMB0GA1Ud
+DgQWBBT6LcYYABEOAMv4hI/5bC82rGlD/DAfBgNVHSMEGDAWgBT6LcYYABEOAMv4
+hI/5bC82rGlD/DAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA9
+D+qoKw0njub+NaFRS2DFbSiKb5JKTxVjU5aNusFONFLSXBuRpnYyjjkXpJy8JMWz
+g8GFDEPP6kiSb8xaPNrFcUzb4PFzJabNTuaLJpBpd2gNBj5AeYwwpRa2DPv/b4yw
+y2mfULuCWS09ZAguI2OcaARlAsFxYN0IuQ6pN1AvGFGee67ve9l2VF/hhwEi4lCk
+MM0CWlP6COJ8TX7X0MTtexVOgo9m3hBuTSYEZClYFIdSOk10xkPl8Y3Iz/x6mzfK
+Uu2l2ZtYvSdAX1CQMds3ZWt0ChNNEjOKPv4g2QSDhGkiqrmi4wUS81g68wKqOpqn
+GbN8uKxIfyMjqZKaujPR
+-----END CERTIFICATE-----
+`
+
+module.exports['server.key'] = `
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDpxqKqzq8PuOuo
+M09X1gdh6V1nMmJ6Oqk/d8ZSAHhGBTjB5BBILtrxJPAdsNCRc8gPCtSuAzu9r0ct
+D2znJjto8HO3a0s/4mlqMQ8iSvfH0c8nGLPxRXg0C9tR0VOyqJrqAPn9X6utxVQj
+H2jLjZngZllhqlxIUxYuXO7MZfC2MQ4DRsp6GaJTftcOxTxevMdT6lK0yl3mDn0W
+DyV9aWR5oCVXKIRK7jmF9bi9ETHwJHffFrduaaKYLQ/UOEtNek/pJsz0DNO0ePkm
+DAt0GQkX3hmKd0VRgGEtaO+MAGbrDucPuDylMkC1GJm1lq6+6rkyQRNoAd8/yOJD
+GihBJmLPAgMBAAECggEBAL3+WM/3IGHnyWavJMnfQaq6rdWkJlLugAT8BCs7BITr
+04AJKY5wvjID8j4/KJM+BRbsl4NBT3lPDcq6YajO8rPL0E/+nG60RTYv3vvg79Xv
+V6uPsRbifdnW1Q1+0cY+r4CFAKeC7JVS7ZmJ+nKMh8XPiM8OVOfW1w0hLFbkdqiq
+UetA5H0oiJr0rFWh9Gfzc0avpmawXmZJqK8lKWB1rt2teu3bQWqn20dGr2IyCipz
+KlGh3QImYogjqWEqPyowPGxnq8KWQ40Pjx61dy6cqYrw77yxLHGR8Az+TlRQZf/K
+BeT3UkXCU5BTyC3PKZJpcYB/GMB8GBKRdNK+8H3OXskCgYEA963tUnLoiCwJjxvv
+gIiMY5mIUAMnTDsCxSYekfkDvt4qdttn/+Y8/5/lXMSlCt+WZcXH0V8gmVOMqfHB
+QRXWpi0uJnl9sUllb80Mw5PnS317UlJIbkro3crVUhIbrn9tKVFP1GO9xszNda8E
+MkHV8FGmSaihXGnA8KEkTKg1CisCgYEA8aEjATIpDXyxQM4M9B1S0jI/nHOrg2lr
+dMKQWJVruuvwrrZl6CFNIm2/PBXyHjCXqROXs7aXkE3vfRSypYNKBZl7BX08c0nE
+koMVXwhGbSbUXjFx5bcUKaG/2eOK+ZV4ivNQokq1iIK+WKINAfP69ewzD7EAgbBg
+KY7owSxka+0CgYB4Urh+W3B35tzl9y5NBQkewdGk/UM0F17rI++p/o1BRnDeuQw3
+F0T+8lDc1nNPavuHiaPfJRWTJzGoxdeapN9Yb46CBnd3jy6GN9lBkjLFS7qDbZHe
+cunaBdXIPx/Pj/waHHRpu+LQF2KhD1s8hxtF2oSsOA3b9UxUGhSmYPkTbQKBgEP8
+muTTQEnTM+yQDYUCWzNZgBx9T10CZIHN3N+P62gEywvdtn7CH/n39z7ozd9AvOuN
+37lpPuwTgbcoA7weXM2Gid7ZhhDKSM0QpQrAQVClBEwcjXedM8cjA+BC7e+b5vbx
+z1ZavwlSAEzgC9jo1Uws0ZEwtHvJLMWEuGjiHL9hAoGAWEbj2cxhpCKTK3C8Qf+C
+BscBwAZKmUasdWQBUzRFpR1UAO3fFBatjMPwrFYnt+ZgiCXTGFcoEa7mjoP/r5Mh
+j1nRCoPQVcbmB1B9AtiaEa1AA+BqYF1r2aErbcsGrCV5OHU7TYFk1dep1SAQP/0W
+9MQ+1Af5ttYSFYlJLnWJo4M=
+-----END PRIVATE KEY-----
+`
+
+module.exports['server.crt'] = `
+-----BEGIN CERTIFICATE-----
+MIIDPjCCAiagAwIBAgIJALrth9K8D7HIMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJDQTEQMA4GA1UECgwHTm9kZS5qczERMA8GA1UECwwI
+bm9kZS1neXAxHDAaBgNVBAMME25vZGUtZ3lwLm5vZGVqcy5vcmcwHhcNMjMwOTI4
+MDUxODM2WhcNMzMwOTI1MDUxODM2WjBgMQswCQYDVQQGEwJVUzETMBEGA1UECAwK
+Q2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEQMA4GA1UECgwHTm9k
+ZS5qczESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEA6caiqs6vD7jrqDNPV9YHYeldZzJiejqpP3fGUgB4RgU4weQQSC7a
+8STwHbDQkXPIDwrUrgM7va9HLQ9s5yY7aPBzt2tLP+JpajEPIkr3x9HPJxiz8UV4
+NAvbUdFTsqia6gD5/V+rrcVUIx9oy42Z4GZZYapcSFMWLlzuzGXwtjEOA0bKehmi
+U37XDsU8XrzHU+pStMpd5g59Fg8lfWlkeaAlVyiESu45hfW4vREx8CR33xa3bmmi
+mC0P1DhLTXpP6SbM9AzTtHj5JgwLdBkJF94ZindFUYBhLWjvjABm6w7nD7g8pTJA
+tRiZtZauvuq5MkETaAHfP8jiQxooQSZizwIDAQABMA0GCSqGSIb3DQEBCwUAA4IB
+AQBwgEyrqJOV8SC7PVTtEOqfSyrM7lJjVcmwXEIFPVCPxXnDtLS9+OaQe9ybjOR/
+Bi/AvZK4gwsV9G5Bvbl0/sphYEKYLEpP76jhdETcBwhaEgK3itumoREeriut4bZI
+OM6b1O45CoD67Lm87CUwLOdcNzPu4k7mat+xog5aFwaQuRjLBmmZcjl41QjVr9ti
+La4PCMh7NwVMtHRqbYvgq785PsKAh+j4FSX1sj9NRzRPoJJ2qsre1Qn5tL/i6ovj
+6s+3GxOQ5I1UzJX22PZFu003a582ha1CEFM0VaeDzzwbGNcV5SP+g2nw55zx9YRR
+Rg8nGmjRuRtbs+/XAre2eQ5p
+-----END CERTIFICATE-----
+`
+
+module.exports['ca-bundle.crt'] = `
+-----BEGIN CERTIFICATE-----
+MIIDJjCCAg4CAhnOMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNVBAYTAlVTMQswCQYD
+VQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEZMBcGA1UECgwQU3Ryb25n
+TG9vcCwgSW5jLjESMBAGA1UECwwJU3Ryb25nT3BzMRowGAYDVQQDDBFjYS5zdHJv
+bmdsb29wLmNvbTAeFw0xNTEyMDgyMzM1MzNaFw00MzA0MjQyMzM1MzNaMBkxFzAV
+BgNVBAMMDnN0cm9uZ2xvb3AuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAwOYI7OZ2FX/YjRgLZoDQlbPc5UZXU/j0e1wwiJNPtPEax9Y5Uoza0Pnt
+Ikzkc2SfvQ+IJrhXo385tI0W5juuqbHnE7UrjUuPjUX6NHevkxcs/flmjan5wnZM
+cPsGhH71WDuUEEflvZihf2Se2x+xgZtMhc5XGmVmRuZFYKvkgUhA2/w8/QrK+jPT
+n9QRJxZjWNh2RBdC1B7u4jffSmOSUljYFH1I2eTeY+Rdi6YUIYSU9gEoZxsv3Tia
+SomfMF5jt2Mouo6MzA+IhLvvFjcrcph1Qxgi9RkfdCMMd+Ipm9YWELkyG1bDRpQy
+0iyHD4gvVsAqz1Y2KdRSdc3Kt+nTqwIDAQABoxkwFzAVBgNVHREEDjAMhwQAAAAA
+hwR/AAABMA0GCSqGSIb3DQEBBQUAA4IBAQAhy4J0hML3NgmDRHdL5/iTucBe22Mf
+jJjg2aifD1S187dHm+Il4qZNO2plWwAhN0h704f+8wpsaALxUvBIu6nvlvcMP5PH
+jGN5JLe2Km3UaPvYOQU2SgacLilu+uBcIo2JSHLV6O7ziqUj5Gior6YxDLCtEZie
+Ea8aX5/YjuACtEMJ1JjRqjgkM66XAoUe0E8onOK3FgTIO3tGoTJwRp0zS50pFuP0
+PsZtT04ck6mmXEXXknNoAyBCvPypfms9OHqcUIW9fiQnrGbS/Ri4QSQYj0DtFk/1
+na4fY1gf3zTHxH8259b/TOOaPfTnCEsOQtjUrWNR4xhmVZ+HJy4yytUW
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDbzCCAlcCAmm6MA0GCSqGSIb3DQEBCwUAMH0xCzAJBgNVBAYTAlVTMQswCQYD
+VQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEZMBcGA1UECgwQU3Ryb25n
+TG9vcCwgSW5jLjESMBAGA1UECwwJU3Ryb25nT3BzMRowGAYDVQQDDBFjYS5zdHJv
+bmdsb29wLmNvbTAeFw0xNTEyMDgyMzM1MzNaFw00MzA0MjQyMzM1MzNaMH0xCzAJ
+BgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEZ
+MBcGA1UECgwQU3Ryb25nTG9vcCwgSW5jLjESMBAGA1UECwwJU3Ryb25nT3BzMRow
+GAYDVQQDDBFjYS5zdHJvbmdsb29wLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBANfj86jkvvYDjHBgiqWhk9Cj+bqiMq3MqnV0CBO4iuK33Fo6XssE
+H+yVdXlIBFbFe6t655MdBVOR2Sfj7WqNh96vhu6PyDHiwcQlTaiLU6nhIed1J4Wv
+lvnJHFmp8Wbtx5AgLT4UYu03ftvXEl2DLi3vhSL2tRM1ebXHB/KPbRWkb25DPX0P
+foOHot3f2dgNe2x6kponf7E/QDmAu3s7Nlkfh+ryDhgGU7wocXEhXbprNqRqOGNo
+xbXgUI+/9XDxYT/7Gn5LF/fPjtN+aB0SKMnTsDhprVlZie83mlqJ46fOOrR+vrsQ
+mi/1m/TadrARtZoIExC/cQRdVM05EK4tUa8CAwEAATANBgkqhkiG9w0BAQsFAAOC
+AQEAQ7k5WhyhDTIGYCNzRnrMHWSzGqa1y4tJMW06wafJNRqTm1cthq1ibc6Hfq5a
+K10K0qMcgauRTfQ1MWrVCTW/KnJ1vkhiTOH+RvxapGn84gSaRmV6KZen0+gMsgae
+KEGe/3Hn+PmDVV+PTamHgPACfpTww38WHIe/7Ce9gHfG7MZ8cKHNZhDy0IAYPln+
+YRwMLd7JNQffHAbWb2CE1mcea4H/12U8JZW5tHCF6y9V+7IuDzqwIrLKcW3lG17n
+VUG6ODF/Ryqn3V5X+TL91YyXi6c34y34IpC7MQDV/67U7+5Bp5CfeDPWW2wVSrW+
+uGZtfEvhbNm6m2i4UNmpCXxUZQ==
+-----END CERTIFICATE-----
+`
diff --git a/test/fixtures/nodedir/include/node/config.gypi b/test/fixtures/nodedir/include/node/config.gypi
new file mode 100644
index 0000000000..e767534082
--- /dev/null
+++ b/test/fixtures/nodedir/include/node/config.gypi
@@ -0,0 +1,6 @@
+# Test configuration
+{
+ 'variables': {
+ 'build_with_electron': true
+ }
+}
diff --git a/test/fixtures/server.crt b/test/fixtures/server.crt
deleted file mode 100644
index fe13bb96c5..0000000000
--- a/test/fixtures/server.crt
+++ /dev/null
@@ -1,19 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDJjCCAg4CAhnOMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNVBAYTAlVTMQswCQYD
-VQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEZMBcGA1UECgwQU3Ryb25n
-TG9vcCwgSW5jLjESMBAGA1UECwwJU3Ryb25nT3BzMRowGAYDVQQDDBFjYS5zdHJv
-bmdsb29wLmNvbTAeFw0xNTEyMDgyMzM1MzNaFw00MzA0MjQyMzM1MzNaMBkxFzAV
-BgNVBAMMDnN0cm9uZ2xvb3AuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
-CgKCAQEAwOYI7OZ2FX/YjRgLZoDQlbPc5UZXU/j0e1wwiJNPtPEax9Y5Uoza0Pnt
-Ikzkc2SfvQ+IJrhXo385tI0W5juuqbHnE7UrjUuPjUX6NHevkxcs/flmjan5wnZM
-cPsGhH71WDuUEEflvZihf2Se2x+xgZtMhc5XGmVmRuZFYKvkgUhA2/w8/QrK+jPT
-n9QRJxZjWNh2RBdC1B7u4jffSmOSUljYFH1I2eTeY+Rdi6YUIYSU9gEoZxsv3Tia
-SomfMF5jt2Mouo6MzA+IhLvvFjcrcph1Qxgi9RkfdCMMd+Ipm9YWELkyG1bDRpQy
-0iyHD4gvVsAqz1Y2KdRSdc3Kt+nTqwIDAQABoxkwFzAVBgNVHREEDjAMhwQAAAAA
-hwR/AAABMA0GCSqGSIb3DQEBBQUAA4IBAQAhy4J0hML3NgmDRHdL5/iTucBe22Mf
-jJjg2aifD1S187dHm+Il4qZNO2plWwAhN0h704f+8wpsaALxUvBIu6nvlvcMP5PH
-jGN5JLe2Km3UaPvYOQU2SgacLilu+uBcIo2JSHLV6O7ziqUj5Gior6YxDLCtEZie
-Ea8aX5/YjuACtEMJ1JjRqjgkM66XAoUe0E8onOK3FgTIO3tGoTJwRp0zS50pFuP0
-PsZtT04ck6mmXEXXknNoAyBCvPypfms9OHqcUIW9fiQnrGbS/Ri4QSQYj0DtFk/1
-na4fY1gf3zTHxH8259b/TOOaPfTnCEsOQtjUrWNR4xhmVZ+HJy4yytUW
------END CERTIFICATE-----
diff --git a/test/fixtures/server.key b/test/fixtures/server.key
deleted file mode 100644
index f8227f4c0c..0000000000
--- a/test/fixtures/server.key
+++ /dev/null
@@ -1,28 +0,0 @@
------BEGIN PRIVATE KEY-----
-MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDA5gjs5nYVf9iN
-GAtmgNCVs9zlRldT+PR7XDCIk0+08RrH1jlSjNrQ+e0iTORzZJ+9D4gmuFejfzm0
-jRbmO66psecTtSuNS4+NRfo0d6+TFyz9+WaNqfnCdkxw+waEfvVYO5QQR+W9mKF/
-ZJ7bH7GBm0yFzlcaZWZG5kVgq+SBSEDb/Dz9Csr6M9Of1BEnFmNY2HZEF0LUHu7i
-N99KY5JSWNgUfUjZ5N5j5F2LphQhhJT2AShnGy/dOJpKiZ8wXmO3Yyi6jozMD4iE
-u+8WNytymHVDGCL1GR90Iwx34imb1hYQuTIbVsNGlDLSLIcPiC9WwCrPVjYp1FJ1
-zcq36dOrAgMBAAECggEACg60Xm2xsHNG/ixHw+NpfLSxCr89JGKxlJD88tIDcOK1
-S8AOoxA3BHhTddteeenALmJV7fbkkuC6SICmtgBcnfppmuxyRd6vsGT6o6ut2tR1
-gxRy1WYMYKg8WhOshlH8RspscODeyKDhorvDUJd5cNGBDuTwQ68PwxiUe3La6iac
-EVQoKohg9EmRIhMF1i8I00zXE8p3XENrlTc491ipc+gLPIP5vtqHyQztEUkZHkWd
-dXbs+n1hGCr+4FxrphGYEW80HINzmume7dGChr8nvF4ZZcuWW13DJuNim6pQno1i
-hM8VdXm8XphLh0XEGI5OCfu/CetkBILZRXKltZk6AQKBgQDoBqJzRlp7regYNU4q
-usfS+43tPNaJ0o4DIzcLawqpmK/B/cZStzHl14Sm62BVkKV6cnWAJPeLkENPMFoV
-7Q7wLZBJxpPzqXkpeiDkKN4Wovca891Rffne5Sz6IDB5mOxMjfKIEPd5RkmB5Lkp
-qQLwm3YJ2AJcLagG/Gi1DFDRAQKBgQDU1G9T43Mjke6TXG0u7gCSb+VwyDRsrvJA
-u2vy6+MANRc1EEF31YLmTKOU5XxUmhtIu7TUbgPoNi0HuRFXx4Zul3BPlAosLMJv
-kNQbA/9d0YQAfSgTsploN5CX65dLZ4ejIzVgDZREzpIBWTze6YZTA2DT5iOIet84
-DD5DujY4qwKBgG0PuUo/9oYOD3tZiv1wwD5+uY6auykbTF9TLStzzBY9y9d+hrsY
-mx6zOAoRtz1g+TdeF7b9KVJzo//T9XQ68nuYnyreaWrt7SK+4jj8sK+pOEd1+0Cz
-20CXLpX/jWmKpP+y9R5aA0kA7cpdjV90rwoTuN8Vpr5XQ5TNDhaTzGUBAoGABYig
-fGXlkH8y3NICZL37ddNC+/O4qTrDQbudyusnM9ItkEuj6CG9DY/gkPaGjQyUuQdo
-ZD2YDGmcMh81vDqL3ERDv03yFcP0KkJxwWIRObdA32JhsGFsa7FGKS0O+f7vH+bC
-dITl3gQg97gCRSl9PJtR4TCSq/HF7Acld01YK5ECgYEAwLFB5JIuxrowJe74cCMP
-n5Rwuc8vWdOsg+ytvQTv0/hVCdzcaLet6YvagnWTWaU7PUwTFxZs/mLQ9CAWVutK
-IRzs/GWxGFjH5xotDaJdDDzSdQye4tUqvUVxv7zzzsVycCPBYFkyRQ8Tmr5FLtUJ
-Cl48TZ6J8Rx5avjdtOw3QC8=
------END PRIVATE KEY-----
diff --git a/test/fixtures/test-charmap.py b/test/fixtures/test-charmap.py
index d9fa6fb25e..9f601af4ab 100644
--- a/test/fixtures/test-charmap.py
+++ b/test/fixtures/test-charmap.py
@@ -1,22 +1,31 @@
import sys
import locale
-reload(sys)
+try:
+ reload(sys)
+except NameError: # Python 3
+ pass
+
def main():
- encoding = locale.getdefaultlocale()[1]
- if not encoding:
- return False
+ encoding = locale.getdefaultlocale()[1]
+ if not encoding:
+ return False
+
+ try:
+ sys.setdefaultencoding(encoding)
+ except AttributeError: # Python 3
+ pass
+
+ textmap = {
+ "cp936": "\u4e2d\u6587",
+ "cp1252": "Lat\u012bna",
+ "cp932": "\u306b\u307b\u3093\u3054",
+ }
+ if encoding in textmap:
+ print(textmap[encoding])
+ return True
- sys.setdefaultencoding(encoding)
- textmap = {
- 'cp936': u'\u4e2d\u6587',
- 'cp1252': u'Lat\u012Bna',
- 'cp932': u'\u306b\u307b\u3093\u3054'
- }
- if textmap.has_key(encoding):
- print textmap[encoding]
- return True
-if __name__ == '__main__':
- print main()
+if __name__ == "__main__":
+ print(main())
diff --git a/test/fixtures/win-lto/include/node/config.gypi b/test/fixtures/win-lto/include/node/config.gypi
new file mode 100644
index 0000000000..2e432eb842
--- /dev/null
+++ b/test/fixtures/win-lto/include/node/config.gypi
@@ -0,0 +1,8 @@
+# Test configuration simulating a Node.js Windows release built with Thin LTO
+{
+ 'variables': {
+ 'enable_lto': 'true',
+ 'enable_thin_lto': 'true',
+ 'lto_jobs': '2'
+ }
+}
diff --git a/test/node_modules/hello_napi/binding.gyp b/test/node_modules/hello_napi/binding.gyp
new file mode 100644
index 0000000000..cb6a3bdc22
--- /dev/null
+++ b/test/node_modules/hello_napi/binding.gyp
@@ -0,0 +1,8 @@
+{
+ "targets": [
+ {
+ "target_name": "hello",
+ "sources": [ "hello.c" ],
+ }
+ ]
+}
diff --git a/test/node_modules/hello_napi/common.gypi b/test/node_modules/hello_napi/common.gypi
new file mode 100644
index 0000000000..f3c53bbad9
--- /dev/null
+++ b/test/node_modules/hello_napi/common.gypi
@@ -0,0 +1,110 @@
+{
+ 'variables': {
+ # OS: 'emscripten' | 'wasi' | 'unknown' | 'wasm'
+ 'clang': 1,
+ 'target_arch%': 'wasm32',
+ 'stack_size%': 1048576,
+ 'initial_memory%': 16777216,
+ 'max_memory%': 2147483648,
+ },
+
+ 'target_defaults': {
+ 'type': 'executable',
+
+ 'defines': [
+ 'BUILDING_NODE_EXTENSION',
+ '__STDC_FORMAT_MACROS',
+ ],
+
+ 'cflags': [
+ '-Wall',
+ '-Wextra',
+ '-Wno-unused-parameter',
+ '--target=wasm32-unknown-unknown',
+ ],
+ 'cflags_cc': [
+ '-fno-rtti',
+ '-fno-exceptions',
+ '-std=c++17'
+ ],
+ 'ldflags': [
+ '--target=wasm32-unknown-unknown',
+ ],
+
+ 'xcode_settings': {
+ # WARNING_CFLAGS == cflags
+ # OTHER_CFLAGS == cflags_c
+ # OTHER_CPLUSPLUSFLAGS == cflags_cc
+ # OTHER_LDFLAGS == ldflags
+
+ 'CLANG_CXX_LANGUAGE_STANDARD': 'c++17',
+ 'GCC_ENABLE_CPP_RTTI': 'NO',
+ 'GCC_ENABLE_CPP_EXCEPTIONS': 'NO',
+ 'WARNING_CFLAGS': [
+ '-Wall',
+ '-Wextra',
+ '-Wno-unused-parameter',
+ '--target=wasm32-unknown-unknown'
+ ],
+ 'OTHER_LDFLAGS': [ '--target=wasm32-unknown-unknown' ],
+ },
+
+ 'default_configuration': 'Release',
+ 'configurations': {
+ 'Debug': {
+ 'defines': [ 'DEBUG', '_DEBUG' ],
+ 'cflags': [ '-g', '-O0' ],
+ 'ldflags': [ '-g', '-O0' ],
+ 'xcode_settings': {
+ 'WARNING_CFLAGS': [ '-g', '-O0' ],
+ 'OTHER_LDFLAGS': [ '-g', '-O0' ],
+ },
+ },
+ 'Release': {
+ 'cflags': [ '-O3' ],
+ 'ldflags': [ '-O3', '-Wl,--strip-debug' ],
+ 'xcode_settings': {
+ 'WARNING_CFLAGS': [ '-O3' ],
+ 'OTHER_LDFLAGS': [ '-O3', '-Wl,--strip-debug' ],
+ },
+ }
+ },
+
+ 'target_conditions': [
+ ['_type=="executable"', {
+
+ 'product_extension': 'wasm',
+
+ 'ldflags': [
+ '-Wl,--export-dynamic',
+ '-Wl,--export=napi_register_wasm_v1',
+ '-Wl,--export-if-defined=node_api_module_get_api_version_v1',
+ '-Wl,--import-undefined',
+ '-Wl,--export-table',
+ '-Wl,-zstack-size=<(stack_size)',
+ '-Wl,--initial-memory=<(initial_memory)',
+ '-Wl,--max-memory=<(max_memory)',
+ '-nostdlib',
+ '-Wl,--no-entry',
+ ],
+ 'xcode_settings': {
+ 'OTHER_LDFLAGS': [
+ '-Wl,--export-dynamic',
+ '-Wl,--export=napi_register_wasm_v1',
+ '-Wl,--export-if-defined=node_api_module_get_api_version_v1',
+ '-Wl,--import-undefined',
+ '-Wl,--export-table',
+ '-Wl,-zstack-size=<(stack_size)',
+ '-Wl,--initial-memory=<(initial_memory)',
+ '-Wl,--max-memory=<(max_memory)',
+ '-nostdlib',
+ '-Wl,--no-entry',
+ ],
+ },
+ 'defines': [
+ 'PAGESIZE=65536'
+ ],
+ }],
+ ],
+ }
+}
diff --git a/test/node_modules/hello_napi/hello.c b/test/node_modules/hello_napi/hello.c
new file mode 100644
index 0000000000..2c03dc156f
--- /dev/null
+++ b/test/node_modules/hello_napi/hello.c
@@ -0,0 +1,54 @@
+#include
+
+#if !defined(__wasm__) || (defined(__EMSCRIPTEN__) || defined(__wasi__))
+#include
+#include
+#else
+#define assert(x) do { if (!(x)) { __builtin_trap(); } } while (0)
+
+
+__attribute__((__import_module__("napi")))
+int napi_create_string_utf8(void* env,
+ const char* str,
+ size_t length,
+ void** result);
+
+__attribute__((__import_module__("napi")))
+int napi_create_function(void* env,
+ const char* utf8name,
+ size_t length,
+ void* cb,
+ void* data,
+ void** result);
+
+__attribute__((__import_module__("napi")))
+int napi_set_named_property(void* env,
+ void* object,
+ const char* utf8name,
+ void* value);
+#ifdef __cplusplus
+#define EXTERN_C extern "C" {
+#else
+#define EXTERN_C
+#endif
+#define NAPI_MODULE_INIT() \
+ EXTERN_C __attribute__((visibility("default"))) void* napi_register_wasm_v1(void* env, void* exports)
+
+typedef void* napi_env;
+typedef void* napi_value;
+typedef void* napi_callback_info;
+#endif
+
+static napi_value hello(napi_env env, napi_callback_info info) {
+ napi_value greeting = NULL;
+ assert(0 == napi_create_string_utf8(env, "world", -1, &greeting));
+ return greeting;
+}
+
+NAPI_MODULE_INIT() {
+ napi_value hello_function = NULL;
+ assert(0 == napi_create_function(env, "hello", -1,
+ hello, NULL, &hello_function));
+ assert(0 == napi_set_named_property(env, exports, "hello", hello_function));
+ return exports;
+}
diff --git a/test/node_modules/hello_napi/hello.js b/test/node_modules/hello_napi/hello.js
new file mode 100644
index 0000000000..00084f851b
--- /dev/null
+++ b/test/node_modules/hello_napi/hello.js
@@ -0,0 +1,57 @@
+const path = require('path')
+const fs = require('fs')
+
+const addon = (function () {
+ const entry = (() => {
+ try {
+ return require.resolve('./build/Release/hello.node')
+ } catch (_) {
+ return require.resolve('./build/Release/hello.wasm')
+ }
+ })()
+
+ const ext = path.extname(entry)
+ if (ext === '.node') {
+ return require(entry)
+ }
+
+ if (ext === '.wasm') {
+ const values = [undefined, undefined, null, false, true, global, {}]
+ const module = new WebAssembly.Module(fs.readFileSync(entry))
+ const instance = new WebAssembly.Instance(module, {
+ napi: {
+ napi_create_string_utf8: (env, str, len, ret) => {
+ let end = str
+ const buffer = new Uint8Array(instance.exports.memory.buffer)
+ while (buffer[end]) end++
+ values.push(new TextDecoder().decode(buffer.slice(str, end)))
+ new DataView(instance.exports.memory.buffer).setInt32(ret, values.length - 1, true)
+ return 0
+ },
+ napi_create_function: (env, name, len, fn, data, ret) => {
+ values.push(function () {
+ return values[instance.exports.__indirect_function_table.get(fn)(env, 0)]
+ })
+ new DataView(instance.exports.memory.buffer).setInt32(ret, values.length - 1, true)
+ return 0
+ },
+ napi_set_named_property: (env, obj, key, val) => {
+ const buffer = new Uint8Array(instance.exports.memory.buffer)
+ let end = key
+ while (buffer[end]) end++
+ const k = new TextDecoder().decode(buffer.slice(key, end))
+ values[obj][k] = values[val]
+ return 0
+ }
+ }
+ })
+ const newExports = values[instance.exports.napi_register_wasm_v1(1, 6)]
+ if (newExports) {
+ values[6] = newExports
+ }
+ return values[6]
+ }
+ throw new Error('Failed to initialize Node-API wasm module')
+})()
+
+exports.hello = function() { return addon.hello() }
diff --git a/test/node_modules/hello_napi/package.json b/test/node_modules/hello_napi/package.json
new file mode 100644
index 0000000000..6cdb71b21f
--- /dev/null
+++ b/test/node_modules/hello_napi/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "hello_napi",
+ "version": "0.0.0",
+ "description": "Node.js Addons Example #2",
+ "main": "hello.js",
+ "private": true,
+ "scripts": {
+ "test": "node hello.js"
+ },
+ "gypfile": true
+}
diff --git a/test/process-exec-sync.js b/test/process-exec-sync.js
deleted file mode 100644
index 859cbc1f6f..0000000000
--- a/test/process-exec-sync.js
+++ /dev/null
@@ -1,138 +0,0 @@
-'use strict'
-
-var fs = require('graceful-fs')
-var child_process = require('child_process')
-
-if (!String.prototype.startsWith) {
- String.prototype.startsWith = function(search, pos) {
- return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search
- }
-}
-
-function processExecSync(file, args, options) {
- var child, error, timeout, tmpdir, command, quote
- command = makeCommand(file, args)
-
- /*
- this function emulates child_process.execSync for legacy node <= 0.10.x
- derived from https://github.com/gvarsanyi/sync-exec/blob/master/js/sync-exec.js
- */
-
- options = options || {}
- // init timeout
- timeout = Date.now() + options.timeout
- // init tmpdir
- var os_temp_base = '/tmp'
- var os = determine_os()
- os_temp_base = '/tmp'
-
- if (process.env.TMP) {
- os_temp_base = process.env.TMP
- }
-
- if (os_temp_base[os_temp_base.length - 1] !== '/') {
- os_temp_base += '/'
- }
-
- tmpdir = os_temp_base + 'processExecSync.' + Date.now() + Math.random()
- fs.mkdirSync(tmpdir)
-
- // init command
- if (os === 'linux') {
- command = '(' + command + ' > ' + tmpdir + '/stdout 2> ' + tmpdir +
- '/stderr); echo $? > ' + tmpdir + '/status'
- } else {
- command = '(' + command + ' > ' + tmpdir + '/stdout 2> ' + tmpdir +
- '/stderr) | echo %errorlevel% > ' + tmpdir + '/status | exit'
- }
-
- // init child
- child = child_process.exec(command, options)
-
- var maxTry = 100000 // increases the test time by 6 seconds on win-2016-node-0.10
- var tryCount = 0
- while (tryCount < maxTry) {
- try {
- var x = fs.readFileSync(tmpdir + '/status')
- if (x.toString() === '0') {
- break
- }
- } catch (ignore) {}
- tryCount++
- if (Date.now() > timeout) {
- error = child
- break
- }
- }
-
- ['stdout', 'stderr', 'status'].forEach(function (file) {
- child[file] = fs.readFileSync(tmpdir + '/' + file, options.encoding)
- setTimeout(unlinkFile, 500, tmpdir + '/' + file)
- })
-
- child.status = Number(child.status)
- if (child.status !== 0) {
- error = child
- }
-
- try {
- fs.rmdirSync(tmpdir)
- } catch (ignore) {}
- if (error) {
- throw error
- }
- return child.stdout
-}
-
-function makeCommand(file, args) {
- var command, quote
- command = file
- if (args.length > 0) {
- for(var i in args) {
- command = command + ' '
- if (args[i][0] === '-') {
- command = command + args[i]
- } else {
- if (!quote) {
- command = command + '\"'
- quote = true
- }
- command = command + args[i]
- if (quote) {
- if (args.length === (parseInt(i) + 1)) {
- command = command + '\"'
- }
- }
- }
- }
- }
- return command
-}
-
-function determine_os() {
- var os = ''
- var tmpVar = ''
- if (process.env.OSTYPE) {
- tmpVar = process.env.OSTYPE
- } else if (process.env.OS) {
- tmpVar = process.env.OS
- } else {
- //default is linux
- tmpVar = 'linux'
- }
-
- if (tmpVar.startsWith('linux')) {
- os = 'linux'
- }
- if (tmpVar.startsWith('win')) {
- os = 'win'
- }
-
- return os
-}
-
-function unlinkFile(file) {
- fs.unlinkSync(file)
-}
-
-module.exports = processExecSync
diff --git a/test/simple-proxy.js b/test/simple-proxy.js
index e55330c445..4f8c4137ef 100644
--- a/test/simple-proxy.js
+++ b/test/simple-proxy.js
@@ -1,23 +1,26 @@
-var http = require('http')
- , https = require('https')
- , server = http.createServer(handler)
- , port = +process.argv[2]
- , prefix = process.argv[3]
- , upstream = process.argv[4]
- , calls = 0
+'use strict'
+
+const http = require('http')
+const https = require('https')
+const server = http.createServer(handler)
+const port = +process.argv[2]
+const prefix = process.argv[3]
+const upstream = process.argv[4]
+let calls = 0
server.listen(port)
function handler (req, res) {
- if (req.url.indexOf(prefix) != 0)
+ if (req.url.indexOf(prefix) !== 0) {
throw new Error('request url [' + req.url + '] does not start with [' + prefix + ']')
+ }
- var upstreamUrl = upstream + req.url.substring(prefix.length)
- console.log(req.url + ' -> ' + upstreamUrl)
+ const upstreamUrl = upstream + req.url.substring(prefix.length)
https.get(upstreamUrl, function (ures) {
ures.on('end', function () {
- if (++calls == 2)
+ if (++calls === 2) {
server.close()
+ }
})
ures.pipe(res)
})
diff --git a/test/test-addon.js b/test/test-addon.js
index 89350effc4..1f4d95ed7e 100644
--- a/test/test-addon.js
+++ b/test/test-addon.js
@@ -1,113 +1,132 @@
'use strict'
-var test = require('tape')
-var path = require('path')
-var fs = require('graceful-fs')
-var child_process = require('child_process')
-var addonPath = path.resolve(__dirname, 'node_modules', 'hello_world')
-var nodeGyp = path.resolve(__dirname, '..', 'bin', 'node-gyp.js')
-var execFileSync = child_process.execFileSync || require('./process-exec-sync')
-var execFile = child_process.execFile
-
-function runHello() {
- var testCode = "console.log(require('hello_world').hello())"
- return execFileSync(process.execPath, ['-e', testCode], { cwd: __dirname }).toString()
+const { describe, it } = require('mocha')
+const assert = require('assert')
+const path = require('path')
+const fs = require('graceful-fs')
+const os = require('os')
+const cp = require('child_process')
+const util = require('../lib/util')
+const { platformTimeout } = require('./common')
+
+const addonPath = path.resolve(__dirname, 'node_modules', 'hello_world')
+const nodeGyp = path.resolve(__dirname, '..', 'bin', 'node-gyp.js')
+
+const execFileSync = (...args) => cp.execFileSync(...args).toString().trim()
+
+const execFile = async (cmd) => {
+ const [err,, stderr] = await util.execFile(process.execPath, cmd, {
+ env: { ...process.env, NODE_GYP_NULL_LOGGER: undefined },
+ encoding: 'utf-8'
+ })
+ return [err, stderr.toString().trim().split(/\r?\n/)]
}
-function getEncoding() {
- var code = 'import locale;print locale.getdefaultlocale()[1]'
- return execFileSync('python', ['-c', code]).toString().trim()
+function runHello (hostProcess = process.execPath) {
+ const testCode = "console.log(require('hello_world').hello())"
+ return execFileSync(hostProcess, ['-e', testCode], { cwd: __dirname })
}
-function checkCharmapValid() {
- var data
+function getEncoding () {
+ const code = 'import locale;print(locale.getdefaultlocale()[1])'
+ return execFileSync('python', ['-c', code])
+}
+
+function checkCharmapValid () {
try {
- data = execFileSync('python', ['fixtures/test-charmap.py'],
- { cwd: __dirname })
- } catch (err) {
+ const data = execFileSync('python', ['fixtures/test-charmap.py'], { cwd: __dirname })
+ return data.split('\n').pop() === 'True'
+ } catch {
return false
}
- var lines = data.toString().trim().split('\n')
- return lines.pop() === 'True'
}
-test('build simple addon', function (t) {
- t.plan(3)
-
- // Set the loglevel otherwise the output disappears when run via 'npm test'
- var cmd = [nodeGyp, 'rebuild', '-C', addonPath, '--loglevel=verbose']
- var proc = execFile(process.execPath, cmd, function (err, stdout, stderr) {
- var logLines = stderr.toString().trim().split(/\r?\n/)
- var lastLine = logLines[logLines.length-1]
- t.strictEqual(err, null)
- t.strictEqual(lastLine, 'gyp info ok', 'should end in ok')
- t.strictEqual(runHello().trim(), 'world')
- })
- proc.stdout.setEncoding('utf-8')
- proc.stderr.setEncoding('utf-8')
-})
+describe('addon', function () {
+ it('build simple addon', async function () {
+ this.timeout(platformTimeout(1, { win32: 5 }))
-test('build simple addon in path with non-ascii characters', function (t) {
- t.plan(1)
+ // Set the loglevel otherwise the output disappears when run via 'npm test'
+ const cmd = [nodeGyp, 'rebuild', '-C', addonPath, '--loglevel=verbose']
+ const [err, logLines] = await execFile(cmd)
+ const lastLine = logLines[logLines.length - 1]
+ assert.strictEqual(err, null)
+ assert.strictEqual(lastLine, 'gyp info ok', 'should end in ok')
+ assert.strictEqual(runHello(), 'world')
+ })
- if (!checkCharmapValid()) {
- return t.skip('python console app can\'t encode non-ascii character.')
- }
+ it('build simple addon in path with non-ascii characters', async function () {
+ if (!checkCharmapValid()) {
+ return this.skip('python console app can\'t encode non-ascii character.')
+ }
- var testDirNames = {
- 'cp936': '文件夹',
- 'cp1252': 'Latīna',
- 'cp932': 'フォルダ'
- }
- // Select non-ascii characters by current encoding
- var testDirName = testDirNames[getEncoding()]
- // If encoding is UTF-8 or other then no need to test
- if (!testDirName) {
- return t.skip('no need to test')
- }
+ // Select non-ascii characters by current encoding
+ const testDirName = {
+ cp936: '文件夹',
+ cp1252: 'Latīna',
+ cp932: 'フォルダ'
+ }[getEncoding()]
+ // If encoding is UTF-8 or other then no need to test
+ if (!testDirName) {
+ return this.skip('no need to test')
+ }
- t.plan(3)
+ this.timeout(platformTimeout(1, { win32: 5 }))
- var data, configPath = path.join(addonPath, 'build', 'config.gypi')
- try {
- data = fs.readFileSync(configPath, 'utf8')
- } catch (err) {
- t.error(err)
- return
- }
- var config = JSON.parse(data.replace(/\#.+\n/, ''))
- var nodeDir = config.variables.nodedir
- var testNodeDir = path.join(addonPath, testDirName)
- // Create symbol link to path with non-ascii characters
- try {
- fs.symlinkSync(nodeDir, testNodeDir, 'dir')
- } catch (err) {
- switch (err.code) {
- case 'EEXIST': break
- case 'EPERM':
- t.error(err, 'Please try to running console as an administrator')
- return
- default:
- t.error(err)
- return
+ let data
+ const configPath = path.join(addonPath, 'build', 'config.gypi')
+ try {
+ data = fs.readFileSync(configPath, 'utf8')
+ } catch (err) {
+ return assert.fail(err)
+ }
+ const config = JSON.parse(data.replace(/#.+\n/, ''))
+ const nodeDir = config.variables.nodedir
+ const testNodeDir = path.join(addonPath, testDirName)
+ // Create symbol link to path with non-ascii characters
+ try {
+ fs.symlinkSync(nodeDir, testNodeDir, 'dir')
+ } catch (err) {
+ switch (err.code) {
+ case 'EEXIST': break
+ case 'EPERM':
+ return assert.fail(err, null, 'Please try to running console as an administrator')
+ default:
+ return assert.fail(err)
+ }
}
- }
- var cmd = [nodeGyp, 'rebuild', '-C', addonPath,
- '--loglevel=verbose', '-nodedir=' + testNodeDir]
- var proc = execFile(process.execPath, cmd, function (err, stdout, stderr) {
+ const cmd = [
+ nodeGyp,
+ 'rebuild',
+ '-C',
+ addonPath,
+ '--loglevel=verbose',
+ '-nodedir=' + testNodeDir
+ ]
+ const [err, logLines] = await execFile(cmd)
try {
fs.unlink(testNodeDir)
} catch (err) {
- t.error(err)
+ assert.fail(err)
}
+ const lastLine = logLines[logLines.length - 1]
+ assert.strictEqual(err, null)
+ assert.strictEqual(lastLine, 'gyp info ok', 'should end in ok')
+ assert.strictEqual(runHello(), 'world')
+ })
+
+ it('addon works with renamed host executable', async function () {
+ this.timeout(platformTimeout(1, { win32: 5 }))
+
+ const notNodePath = path.join(os.tmpdir(), 'notnode' + path.extname(process.execPath))
+ fs.copyFileSync(process.execPath, notNodePath)
- var logLines = stderr.toString().trim().split(/\r?\n/)
- var lastLine = logLines[logLines.length-1]
- t.strictEqual(err, null)
- t.strictEqual(lastLine, 'gyp info ok', 'should end in ok')
- t.strictEqual(runHello().trim(), 'world')
+ const cmd = [nodeGyp, 'rebuild', '-C', addonPath, '--loglevel=verbose']
+ const [err, logLines] = await execFile(cmd)
+ const lastLine = logLines[logLines.length - 1]
+ assert.strictEqual(err, null)
+ assert.strictEqual(lastLine, 'gyp info ok', 'should end in ok')
+ assert.strictEqual(runHello(notNodePath), 'world')
+ fs.unlinkSync(notNodePath)
})
- proc.stdout.setEncoding('utf-8')
- proc.stderr.setEncoding('utf-8')
})
diff --git a/test/test-configure-nodedir.js b/test/test-configure-nodedir.js
new file mode 100644
index 0000000000..a6debded06
--- /dev/null
+++ b/test/test-configure-nodedir.js
@@ -0,0 +1,123 @@
+'use strict'
+
+const { describe, it } = require('mocha')
+const assert = require('assert')
+const path = require('path')
+const os = require('os')
+const gyp = require('../lib/node-gyp')
+const requireInject = require('require-inject')
+const semver = require('semver')
+
+const versionSemver = semver.parse(process.version)
+
+const configure = requireInject('../lib/configure', {
+ 'graceful-fs': {
+ openSync: () => 0,
+ closeSync: () => {},
+ existsSync: () => true,
+ readFileSync: () => '#define NODE_MAJOR_VERSION ' + versionSemver.major + '\n' +
+ '#define NODE_MINOR_VERSION ' + versionSemver.minor + '\n' +
+ '#define NODE_PATCH_VERSION ' + versionSemver.patch + '\n',
+ promises: {
+ stat: async () => ({}),
+ mkdir: async () => {},
+ writeFile: async () => {}
+ }
+ }
+})
+
+const configure2 = requireInject('../lib/configure', {
+ 'graceful-fs': {
+ openSync: () => 0,
+ closeSync: () => {},
+ existsSync: () => true,
+ readFileSync: () => '#define NODE_MAJOR_VERSION 8\n' +
+ '#define NODE_MINOR_VERSION 0\n' +
+ '#define NODE_PATCH_VERSION 0\n',
+ promises: {
+ stat: async () => ({}),
+ mkdir: async () => {},
+ writeFile: async () => {}
+ }
+ }
+})
+
+const SPAWN_RESULT = cb => ({ on: function () { cb() } })
+
+const driveLetter = os.platform() === 'win32' ? `${process.cwd().split(path.sep)[0]}` : ''
+function checkTargetPath (target, value) {
+ let targetPath = path.join(path.sep, target, 'include',
+ 'node', 'common.gypi')
+ if (process.platform === 'win32') {
+ targetPath = driveLetter + targetPath
+ }
+
+ return targetPath.localeCompare(value) === 0
+}
+
+describe('configure-nodedir', function () {
+ it('configure nodedir with node-gyp command line', function (done) {
+ const prog = gyp()
+ prog.parseArgv(['dummy_prog', 'dummy_script', '--nodedir=' + path.sep + 'usr'])
+
+ prog.spawn = function (program, args) {
+ for (let i = 0; i < args.length; i++) {
+ if (checkTargetPath('usr', args[i])) {
+ return SPAWN_RESULT(done)
+ }
+ };
+ assert.fail()
+ }
+ configure(prog, [], assert.fail)
+ })
+
+ if (process.config.variables.use_prefix_to_find_headers) {
+ it('use-prefix-to-find-headers build time option - match', function (done) {
+ const prog = gyp()
+ prog.parseArgv(['dummy_prog', 'dummy_script'])
+
+ prog.spawn = function (program, args) {
+ for (let i = 0; i < args.length; i++) {
+ const nodedir = process.config.variables.node_prefix
+ if (checkTargetPath(nodedir, args[i])) {
+ return SPAWN_RESULT(done)
+ }
+ };
+ assert.fail()
+ }
+ configure(prog, [], assert.fail)
+ })
+
+ it('use-prefix-to-find-headers build time option - no match', function (done) {
+ const prog = gyp()
+ prog.parseArgv(['dummy_prog', 'dummy_script'])
+
+ prog.spawn = function (program, args) {
+ for (let i = 0; i < args.length; i++) {
+ const nodedir = process.config.variables.node_prefix
+ if (checkTargetPath(nodedir, args[i])) {
+ assert.fail()
+ }
+ };
+ return SPAWN_RESULT(done)
+ }
+ configure2(prog, [], assert.fail)
+ })
+
+ it('use-prefix-to-find-headers build time option, target specified', function (done) {
+ const prog = gyp()
+ prog.parseArgv(['dummy_prog', 'dummy_script', '--target=8.0.0'])
+
+ prog.spawn = function (program, args) {
+ for (let i = 0; i < args.length; i++) {
+ const nodedir = process.config.variables.node_prefix
+ if (checkTargetPath(nodedir, args[i])) {
+ assert.fail()
+ }
+ };
+ return SPAWN_RESULT(done)
+ }
+ configure(prog, [], assert.fail)
+ })
+ }
+})
diff --git a/test/test-configure-python.js b/test/test-configure-python.js
index f235bdbba1..094e79182c 100644
--- a/test/test-configure-python.js
+++ b/test/test-configure-python.js
@@ -1,74 +1,78 @@
'use strict'
-var test = require('tape')
-var path = require('path')
-var gyp = require('../lib/node-gyp')
-var requireInject = require('require-inject')
-var configure = requireInject('../lib/configure', {
+const { describe, it } = require('mocha')
+const assert = require('assert')
+const path = require('path')
+const { devDir } = require('./common')
+const gyp = require('../lib/node-gyp')
+const requireInject = require('require-inject')
+
+const configure = requireInject('../lib/configure', {
'graceful-fs': {
- 'openSync': function (file, mode) { return 0; },
- 'closeSync': function (fd) { },
- 'writeFile': function (file, data, cb) { cb() },
- 'stat': function (file, cb) { cb(null, {}) }
+ openSync: () => 0,
+ closeSync: () => {},
+ existsSync: () => {},
+ promises: {
+ stat: async () => ({}),
+ mkdir: async () => {},
+ writeFile: async () => {}
+ }
}
})
-var EXPECTED_PYPATH = path.join(__dirname, '..', 'gyp', 'pylib')
-var SEPARATOR = process.platform == 'win32' ? ';' : ':'
-var SPAWN_RESULT = { on: function () { } }
-
-test('configure PYTHONPATH with no existing env', function (t) {
- t.plan(1)
-
- delete process.env.PYTHONPATH
-
- var prog = gyp()
- prog.parseArgv([])
- prog.spawn = function () {
- t.equal(process.env.PYTHONPATH, EXPECTED_PYPATH)
- return SPAWN_RESULT
- }
- configure(prog, [], t.fail)
-})
-
-test('configure PYTHONPATH with existing env of one dir', function (t) {
- t.plan(2)
-
- var existingPath = path.join('a', 'b')
- process.env.PYTHONPATH = existingPath
-
- var prog = gyp()
- prog.parseArgv([])
- prog.spawn = function () {
-
- t.equal(process.env.PYTHONPATH, [EXPECTED_PYPATH, existingPath].join(SEPARATOR))
-
- var dirs = process.env.PYTHONPATH.split(SEPARATOR)
- t.deepEqual(dirs, [EXPECTED_PYPATH, existingPath])
-
- return SPAWN_RESULT
- }
- configure(prog, [], t.fail)
-})
-
-test('configure PYTHONPATH with existing env of multiple dirs', function (t) {
- t.plan(2)
-
- var pythonDir1 = path.join('a', 'b')
- var pythonDir2 = path.join('b', 'c')
- var existingPath = [pythonDir1, pythonDir2].join(SEPARATOR)
- process.env.PYTHONPATH = existingPath
-
- var prog = gyp()
- prog.parseArgv([])
- prog.spawn = function () {
-
- t.equal(process.env.PYTHONPATH, [EXPECTED_PYPATH, existingPath].join(SEPARATOR))
-
- var dirs = process.env.PYTHONPATH.split(SEPARATOR)
- t.deepEqual(dirs, [EXPECTED_PYPATH, pythonDir1, pythonDir2])
-
- return SPAWN_RESULT
- }
- configure(prog, [], t.fail)
+const EXPECTED_PYPATH = path.join(__dirname, '..', 'gyp', 'pylib')
+const SEPARATOR = process.platform === 'win32' ? ';' : ':'
+const SPAWN_RESULT = cb => ({ on: function () { cb() } })
+
+describe('configure-python', function () {
+ it('configure PYTHONPATH with no existing env', function (done) {
+ delete process.env.PYTHONPATH
+
+ const prog = gyp()
+ prog.parseArgv([])
+ prog.spawn = function () {
+ assert.strictEqual(process.env.PYTHONPATH, EXPECTED_PYPATH)
+ return SPAWN_RESULT(done)
+ }
+ prog.devDir = devDir
+ configure(prog, [], assert.fail)
+ })
+
+ it('configure PYTHONPATH with existing env of one dir', function (done) {
+ const existingPath = path.join('a', 'b')
+ process.env.PYTHONPATH = existingPath
+
+ const prog = gyp()
+ prog.parseArgv([])
+ prog.spawn = function () {
+ assert.strictEqual(process.env.PYTHONPATH, [EXPECTED_PYPATH, existingPath].join(SEPARATOR))
+
+ const dirs = process.env.PYTHONPATH.split(SEPARATOR)
+ assert.deepStrictEqual(dirs, [EXPECTED_PYPATH, existingPath])
+
+ return SPAWN_RESULT(done)
+ }
+ prog.devDir = devDir
+ configure(prog, [], assert.fail)
+ })
+
+ it('configure PYTHONPATH with existing env of multiple dirs', function (done) {
+ const pythonDir1 = path.join('a', 'b')
+ const pythonDir2 = path.join('b', 'c')
+ const existingPath = [pythonDir1, pythonDir2].join(SEPARATOR)
+ process.env.PYTHONPATH = existingPath
+
+ const prog = gyp()
+ prog.parseArgv([])
+ prog.spawn = function () {
+ assert.strictEqual(process.env.PYTHONPATH, [EXPECTED_PYPATH, existingPath].join(SEPARATOR))
+
+ const dirs = process.env.PYTHONPATH.split(SEPARATOR)
+ assert.deepStrictEqual(dirs, [EXPECTED_PYPATH, pythonDir1, pythonDir2])
+
+ return SPAWN_RESULT(done)
+ }
+ prog.devDir = devDir
+ configure(prog, [], assert.fail)
+ })
})
diff --git a/test/test-create-config-gypi.js b/test/test-create-config-gypi.js
new file mode 100644
index 0000000000..602fd3d678
--- /dev/null
+++ b/test/test-create-config-gypi.js
@@ -0,0 +1,79 @@
+'use strict'
+
+const path = require('path')
+const { describe, it } = require('mocha')
+const assert = require('assert')
+const gyp = require('../lib/node-gyp')
+const { parseConfigGypi, getCurrentConfigGypi } = require('../lib/create-config-gypi')
+
+describe('create-config-gypi', function () {
+ it('config.gypi with no options', async function () {
+ const prog = gyp()
+ prog.parseArgv([])
+
+ const config = await getCurrentConfigGypi({ gyp: prog, vsInfo: {} })
+ assert.strictEqual(config.target_defaults.default_configuration, 'Release')
+ assert.strictEqual(config.variables.target_arch, process.arch)
+ })
+
+ it('config.gypi with --debug', async function () {
+ const prog = gyp()
+ prog.parseArgv(['_', '_', '--debug'])
+
+ const config = await getCurrentConfigGypi({ gyp: prog, vsInfo: {} })
+ assert.strictEqual(config.target_defaults.default_configuration, 'Debug')
+ })
+
+ it('config.gypi with custom options', async function () {
+ const prog = gyp()
+ prog.parseArgv(['_', '_', '--shared-libxml2'])
+
+ const config = await getCurrentConfigGypi({ gyp: prog, vsInfo: {} })
+ assert.strictEqual(config.variables.shared_libxml2, true)
+ })
+
+ it('config.gypi with nodedir', async function () {
+ const nodeDir = path.join(__dirname, 'fixtures', 'nodedir')
+
+ const prog = gyp()
+ prog.parseArgv(['_', '_', `--nodedir=${nodeDir}`])
+
+ const config = await getCurrentConfigGypi({ gyp: prog, nodeDir, vsInfo: {} })
+ assert.strictEqual(config.variables.build_with_electron, true)
+ })
+
+ it('config.gypi with --force-process-config', async function () {
+ const nodeDir = path.join(__dirname, 'fixtures', 'nodedir')
+
+ const prog = gyp()
+ prog.parseArgv(['_', '_', '--force-process-config', `--nodedir=${nodeDir}`])
+
+ const config = await getCurrentConfigGypi({ gyp: prog, nodeDir, vsInfo: {} })
+ assert.strictEqual(config.variables.build_with_electron, undefined)
+ })
+
+ it('config.gypi disables LTO for addon builds on Windows', async function () {
+ const nodeDir = path.join(__dirname, 'fixtures', 'win-lto')
+
+ const prog = gyp()
+ prog.parseArgv(['_', '_', `--nodedir=${nodeDir}`])
+
+ const originalPlatform = process.platform
+ Object.defineProperty(process, 'platform', { value: 'win32' })
+ try {
+ const config = await getCurrentConfigGypi({ gyp: prog, nodeDir, vsInfo: {} })
+ // thin LTO leaks clang/lld-only flags into the MSVC addon build, so it
+ // must be disabled regardless of how node itself was built
+ assert.strictEqual(config.variables.enable_lto, 'false')
+ assert.strictEqual(config.variables.enable_thin_lto, 'false')
+ } finally {
+ Object.defineProperty(process, 'platform', { value: originalPlatform })
+ }
+ })
+
+ it('config.gypi parsing', function () {
+ const str = "# Some comments\n{'variables': {'multiline': 'A'\n'B'}}"
+ const config = parseConfigGypi(str)
+ assert.deepStrictEqual(config, { variables: { multiline: 'AB' } })
+ })
+})
diff --git a/test/test-download.js b/test/test-download.js
index 6e6f64f058..756142f623 100644
--- a/test/test-download.js
+++ b/test/test-download.js
@@ -1,102 +1,292 @@
'use strict'
-var fs = require('fs')
-var http = require('http')
-var https = require('https')
-var test = require('tape')
-var install = require('../lib/install')
-
-test('download over http', function (t) {
- t.plan(2)
-
- var server = http.createServer(function (req, res) {
- t.strictEqual(req.headers['user-agent'],
- 'node-gyp v42 (node ' + process.version + ')')
- res.end('ok')
- server.close()
- })
+const { describe, it, after } = require('mocha')
+const assert = require('assert')
+const fs = require('fs/promises')
+const path = require('path')
+const http = require('http')
+const https = require('https')
+const net = require('net')
+const install = require('../lib/install')
+const { download, readCAFile } = require('../lib/download')
+const { FULL_TEST, devDir, platformTimeout } = require('./common')
+const gyp = require('../lib/node-gyp')
+const certs = require('./fixtures/certs')
+
+describe('download', function () {
+ it('download over http', async function () {
+ const server = http.createServer((req, res) => {
+ assert.strictEqual(req.headers['user-agent'], `node-gyp v42 (node ${process.version})`)
+ res.end('ok')
+ })
+
+ after(() => new Promise((resolve) => server.close(resolve)))
- var host = '127.0.0.1'
- server.listen(0, host, function () {
- var port = this.address().port
- var gyp = {
+ const host = 'localhost'
+ await new Promise((resolve) => server.listen(0, host, resolve))
+ const { port } = server.address()
+ const gyp = {
opts: {},
- version: '42',
+ version: '42'
}
- var url = 'http://' + host + ':' + port
- var req = install.test.download(gyp, {}, url)
- req.on('response', function (res) {
- var body = ''
- res.setEncoding('utf8')
- res.on('data', function(data) {
- body += data
- })
- res.on('end', function() {
- t.strictEqual(body, 'ok')
+ const url = `http://${host}:${port}`
+ const res = await download(gyp, url)
+ assert.strictEqual(await res.text(), 'ok')
+ })
+
+ it('download over https with custom ca', async function () {
+ const cafile = path.join(__dirname, 'fixtures/ca.crt')
+ const cacontents = certs['ca.crt']
+ const cert = certs['server.crt']
+ const key = certs['server.key']
+ await fs.writeFile(cafile, cacontents, 'utf8')
+ const ca = await readCAFile(cafile)
+
+ assert.strictEqual(ca.length, 1)
+
+ const options = { ca, cert, key }
+ const server = https.createServer(options, (req, res) => {
+ assert.strictEqual(req.headers['user-agent'], `node-gyp v42 (node ${process.version})`)
+ res.end('ok')
+ })
+
+ after(async () => {
+ await new Promise((resolve) => server.close(resolve))
+ await fs.unlink(cafile)
+ })
+
+ server.on('clientError', (err) => { throw err })
+
+ const host = 'localhost'
+ await new Promise((resolve) => server.listen(0, host, resolve))
+ const { port } = server.address()
+ const gyp = {
+ opts: { cafile },
+ version: '42'
+ }
+ const url = `https://${host}:${port}`
+ const res = await download(gyp, url)
+ assert.strictEqual(await res.text(), 'ok')
+ })
+
+ it('download over http with proxy', async function () {
+ const server = http.createServer((req, res) => {
+ assert.strictEqual(req.headers['user-agent'], `node-gyp v42 (node ${process.version})`)
+ res.end('ok')
+ })
+
+ let proxyUsed = false
+ const pserver = http.createServer()
+ pserver.on('connect', (req, clientSocket, head) => {
+ proxyUsed = true
+ const [targetHost, targetPort] = req.url.split(':')
+ const serverSocket = net.connect(targetPort, targetHost, () => {
+ clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n')
+ serverSocket.write(head)
+ serverSocket.pipe(clientSocket)
+ clientSocket.pipe(serverSocket)
})
+ clientSocket.on('error', () => serverSocket.destroy())
+ serverSocket.on('error', () => clientSocket.destroy())
})
+
+ after(() => Promise.all([
+ new Promise((resolve) => server.close(resolve)),
+ new Promise((resolve) => pserver.close(resolve))
+ ]))
+
+ const host = 'localhost'
+ await new Promise((resolve) => server.listen(0, host, resolve))
+ const { port } = server.address()
+ await new Promise((resolve) => pserver.listen(port + 1, host, resolve))
+ const gyp = {
+ opts: {
+ proxy: `http://${host}:${port + 1}`,
+ noproxy: 'bad'
+ },
+ version: '42'
+ }
+ const url = `http://${host}:${port}`
+ const res = await download(gyp, url)
+ assert.strictEqual(await res.text(), 'ok')
+ assert.strictEqual(proxyUsed, true)
})
-})
-test('download over https with custom ca', function (t) {
- t.plan(3)
+ it('download over https with proxy and custom ca', async function () {
+ const cafile = path.join(__dirname, 'fixtures/ca-proxy.crt')
+ await fs.writeFile(cafile, certs['ca.crt'], 'utf8')
- var cert = fs.readFileSync(__dirname + '/fixtures/server.crt', 'utf8')
- var key = fs.readFileSync(__dirname + '/fixtures/server.key', 'utf8')
+ const server = https.createServer({
+ ca: await readCAFile(cafile),
+ cert: certs['server.crt'],
+ key: certs['server.key']
+ }, (_, res) => res.end('ok'))
- var cafile = __dirname + '/fixtures/ca.crt'
- var ca = install.test.readCAFile(cafile)
- t.strictEqual(ca.length, 1)
+ let proxyUsed = false
+ const pserver = http.createServer()
+ pserver.on('connect', (req, clientSocket, head) => {
+ proxyUsed = true
+ const [targetHost, targetPort] = req.url.split(':')
+ const serverSocket = net.connect(targetPort, targetHost, () => {
+ clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n')
+ serverSocket.write(head)
+ serverSocket.pipe(clientSocket)
+ clientSocket.pipe(serverSocket)
+ })
+ clientSocket.on('error', () => serverSocket.destroy())
+ serverSocket.on('error', () => clientSocket.destroy())
+ })
- var options = { ca: ca, cert: cert, key: key }
- var server = https.createServer(options, function (req, res) {
- t.strictEqual(req.headers['user-agent'],
- 'node-gyp v42 (node ' + process.version + ')')
- res.end('ok')
- server.close()
+ after(async () => {
+ await new Promise((resolve) => server.close(resolve))
+ await new Promise((resolve) => pserver.close(resolve))
+ await fs.unlink(cafile)
+ })
+
+ const host = 'localhost'
+ await new Promise((resolve) => server.listen(0, host, resolve))
+ const { port } = server.address()
+ await new Promise((resolve) => pserver.listen(port + 1, host, resolve))
+ const gyp = {
+ opts: {
+ cafile,
+ proxy: `http://${host}:${port + 1}`,
+ noproxy: 'bad'
+ },
+ version: '42'
+ }
+ const res = await download(gyp, `https://${host}:${port}`)
+ assert.strictEqual(await res.text(), 'ok')
+ assert.strictEqual(proxyUsed, true)
})
- server.on('clientError', function (err) {
- throw err
+ it('download over http with noproxy', async function () {
+ const server = http.createServer((req, res) => {
+ assert.strictEqual(req.headers['user-agent'], `node-gyp v42 (node ${process.version})`)
+ res.end('ok')
+ })
+
+ let proxyUsed = false
+ const pserver = http.createServer()
+ pserver.on('connect', (_, socket) => {
+ proxyUsed = true
+ socket.destroy()
+ })
+
+ after(() => Promise.all([
+ new Promise((resolve) => server.close(resolve)),
+ new Promise((resolve) => pserver.close(resolve))
+ ]))
+
+ const host = 'localhost'
+ await new Promise((resolve) => server.listen(0, host, resolve))
+ const { port } = server.address()
+ await new Promise((resolve) => pserver.listen(port + 1, host, resolve))
+ const gyp = {
+ opts: {
+ proxy: `http://${host}:${port + 1}`,
+ noproxy: host
+ },
+ version: '42'
+ }
+ const url = `http://${host}:${port}`
+ const res = await download(gyp, url)
+ assert.strictEqual(await res.text(), 'ok')
+ assert.strictEqual(proxyUsed, false)
})
- var host = '127.0.0.1'
- server.listen(8000, host, function () {
- var port = this.address().port
- var gyp = {
- opts: { cafile: cafile },
- version: '42',
+ it('download with missing cafile', async function () {
+ const gyp = {
+ opts: { cafile: 'no.such.file' }
}
- var url = 'https://' + host + ':' + port
- var req = install.test.download(gyp, {}, url)
- req.on('response', function (res) {
- var body = ''
- res.setEncoding('utf8')
- res.on('data', function(data) {
- body += data
- })
- res.on('end', function() {
- t.strictEqual(body, 'ok')
- })
+ try {
+ await download(gyp, {}, 'http://bad/')
+ } catch (e) {
+ assert.ok(/no.such.file/.test(e.message))
+ }
+ })
+
+ it('check certificate splitting', async function () {
+ const cafile = path.join(__dirname, 'fixtures/ca-bundle.crt')
+ const cacontents = certs['ca-bundle.crt']
+ await fs.writeFile(cafile, cacontents, 'utf8')
+ after(async () => {
+ await fs.unlink(cafile)
})
+ const cas = await readCAFile(path.join(__dirname, 'fixtures/ca-bundle.crt'))
+ assert.strictEqual(cas.length, 2)
+ assert.notStrictEqual(cas[0], cas[1])
})
-})
-test('download with missing cafile', function (t) {
- t.plan(1)
- var gyp = {
- opts: { cafile: 'no.such.file' },
- }
- try {
- install.test.download(gyp, {}, 'http://bad/')
- } catch (e) {
- t.ok(/no.such.file/.test(e.message))
- }
-})
+ it('download will retry on ECONNRESET', async function () {
+ let requestCount = 0
+ const server = http.createServer((req, res) => {
+ requestCount++
+ if (requestCount < 3) {
+ req.socket.destroy()
+ return
+ }
+ res.end('ok')
+ })
+
+ after(() => new Promise((resolve) => server.close(resolve)))
-test('check certificate splitting', function (t) {
- var cas = install.test.readCAFile(__dirname + '/fixtures/ca-bundle.crt')
- t.plan(2)
- t.strictEqual(cas.length, 2)
- t.notStrictEqual(cas[0], cas[1])
+ const host = 'localhost'
+ await new Promise((resolve) => server.listen(0, host, resolve))
+ const { port } = server.address()
+ const gyp = {
+ opts: {},
+ version: '42'
+ }
+ const url = `http://${host}:${port}`
+ const res = await download(gyp, url)
+ assert.strictEqual(await res.text(), 'ok')
+ assert.ok(requestCount >= 2, `expected at least 2 requests but got ${requestCount}`)
+ })
+
+ // only run this test if we are running a version of Node with predictable version path behavior
+
+ it('download headers (actual)', async function () {
+ if (!FULL_TEST) {
+ return this.skip('Skipping actual download of headers due to test environment configuration')
+ }
+
+ this.timeout(platformTimeout(1, { win32: 5 }))
+
+ const expectedDir = path.join(devDir, process.version.replace(/^v/, ''))
+ await fs.rm(expectedDir, { recursive: true, force: true, maxRetries: 3 })
+
+ const prog = gyp()
+ prog.parseArgv([])
+ prog.devDir = devDir
+ await install(prog, [])
+
+ const data = await fs.readFile(path.join(expectedDir, 'installVersion'), 'utf8')
+ assert.strictEqual(data, '11\n', 'correct installVersion')
+
+ const list = await fs.readdir(path.join(expectedDir, 'include/node'))
+ assert.ok(list.includes('common.gypi'))
+ assert.ok(list.includes('config.gypi'))
+ assert.ok(list.includes('node.h'))
+ assert.ok(list.includes('node_version.h'))
+ assert.ok(list.includes('openssl'))
+ assert.ok(list.includes('uv'))
+ assert.ok(list.includes('uv.h'))
+ assert.ok(list.includes('v8-platform.h'))
+ assert.ok(list.includes('v8.h'))
+ assert.ok(list.includes('zlib.h'))
+
+ const lines = (await fs.readFile(path.join(expectedDir, 'include/node/node_version.h'), 'utf8')).split('\n')
+
+ // extract the 3 version parts from the defines to build a valid version string and
+ // and check them against our current env version
+ const version = ['major', 'minor', 'patch'].reduce((version, type) => {
+ const re = new RegExp(`^#define\\sNODE_${type.toUpperCase()}_VERSION`)
+ const line = lines.find((l) => re.test(l))
+ const i = line ? parseInt(line.replace(/^[^0-9]+([0-9]+).*$/, '$1'), 10) : 'ERROR'
+ return `${version}${type !== 'major' ? '.' : 'v'}${i}`
+ }, '')
+
+ assert.strictEqual(version, process.version)
+ })
})
diff --git a/test/test-find-accessible-sync.js b/test/test-find-accessible-sync.js
index d336243dd0..0c8a5ddf07 100644
--- a/test/test-find-accessible-sync.js
+++ b/test/test-find-accessible-sync.js
@@ -1,86 +1,73 @@
'use strict'
-var test = require('tape')
-var path = require('path')
-var requireInject = require('require-inject')
-var configure = requireInject('../lib/configure', {
+const { describe, it } = require('mocha')
+const assert = require('assert')
+const path = require('path')
+const requireInject = require('require-inject')
+const { findAccessibleSync } = requireInject('../lib/util', {
'graceful-fs': {
- 'closeSync': function (fd) { return undefined },
- 'openSync': function (path) {
- if (readableFiles.some(function (f) { return f === path} )) {
+ closeSync: function () { return undefined },
+ openSync: function (path) {
+ if (readableFiles.some(function (f) { return f === path })) {
return 0
} else {
- var error = new Error('ENOENT - not found')
+ const error = new Error('ENOENT - not found')
throw error
}
}
}
})
-var dir = path.sep + 'testdir'
-var readableFile = 'readable_file'
-var anotherReadableFile = 'another_readable_file'
-var readableFileInDir = 'somedir' + path.sep + readableFile
-var readableFiles = [
+const dir = path.sep + 'testdir'
+const readableFile = 'readable_file'
+const anotherReadableFile = 'another_readable_file'
+const readableFileInDir = 'somedir' + path.sep + readableFile
+const readableFiles = [
path.resolve(dir, readableFile),
path.resolve(dir, anotherReadableFile),
path.resolve(dir, readableFileInDir)
]
-test('find accessible - empty array', function (t) {
- t.plan(1)
-
- var candidates = []
- var found = configure.test.findAccessibleSync('test', dir, candidates)
- t.strictEqual(found, undefined)
-})
-
-test('find accessible - single item array, readable', function (t) {
- t.plan(1)
-
- var candidates = [ readableFile ]
- var found = configure.test.findAccessibleSync('test', dir, candidates)
- t.strictEqual(found, path.resolve(dir, readableFile))
-})
-
-test('find accessible - single item array, readable in subdir', function (t) {
- t.plan(1)
-
- var candidates = [ readableFileInDir ]
- var found = configure.test.findAccessibleSync('test', dir, candidates)
- t.strictEqual(found, path.resolve(dir, readableFileInDir))
-})
-
-test('find accessible - single item array, unreadable', function (t) {
- t.plan(1)
-
- var candidates = [ 'unreadable_file' ]
- var found = configure.test.findAccessibleSync('test', dir, candidates)
- t.strictEqual(found, undefined)
-})
-
-
-test('find accessible - multi item array, no matches', function (t) {
- t.plan(1)
-
- var candidates = [ 'non_existent_file', 'unreadable_file' ]
- var found = configure.test.findAccessibleSync('test', dir, candidates)
- t.strictEqual(found, undefined)
-})
-
-
-test('find accessible - multi item array, single match', function (t) {
- t.plan(1)
-
- var candidates = [ 'non_existent_file', readableFile ]
- var found = configure.test.findAccessibleSync('test', dir, candidates)
- t.strictEqual(found, path.resolve(dir, readableFile))
-})
-
-test('find accessible - multi item array, return first match', function (t) {
- t.plan(1)
-
- var candidates = [ 'non_existent_file', anotherReadableFile, readableFile ]
- var found = configure.test.findAccessibleSync('test', dir, candidates)
- t.strictEqual(found, path.resolve(dir, anotherReadableFile))
+describe('find-accessible-sync', function () {
+ it('find accessible - empty array', function () {
+ const candidates = []
+ const found = findAccessibleSync('test', dir, candidates)
+ assert.strictEqual(found, undefined)
+ })
+
+ it('find accessible - single item array, readable', function () {
+ const candidates = [readableFile]
+ const found = findAccessibleSync('test', dir, candidates)
+ assert.strictEqual(found, path.resolve(dir, readableFile))
+ })
+
+ it('find accessible - single item array, readable in subdir', function () {
+ const candidates = [readableFileInDir]
+ const found = findAccessibleSync('test', dir, candidates)
+ assert.strictEqual(found, path.resolve(dir, readableFileInDir))
+ })
+
+ it('find accessible - single item array, unreadable', function () {
+ const candidates = ['unreadable_file']
+ const found = findAccessibleSync('test', dir, candidates)
+ assert.strictEqual(found, undefined)
+ })
+
+ it('find accessible - multi item array, no matches', function () {
+ const candidates = ['non_existent_file', 'unreadable_file']
+ const found = findAccessibleSync('test', dir, candidates)
+ assert.strictEqual(found, undefined)
+ })
+
+ it('find accessible - multi item array, single match', function () {
+ const candidates = ['non_existent_file', readableFile]
+ const found = findAccessibleSync('test', dir, candidates)
+ assert.strictEqual(found, path.resolve(dir, readableFile))
+ })
+
+ it('find accessible - multi item array, return first match', function () {
+ const candidates = ['non_existent_file', anotherReadableFile, readableFile]
+ const found = findAccessibleSync('test', dir, candidates)
+ assert.strictEqual(found, path.resolve(dir, anotherReadableFile))
+ })
})
diff --git a/test/test-find-node-directory.js b/test/test-find-node-directory.js
index 46659d0cfe..e36a5c04ea 100644
--- a/test/test-find-node-directory.js
+++ b/test/test-find-node-directory.js
@@ -1,115 +1,115 @@
-var test = require('tape')
-var path = require('path')
-var findNodeDirectory = require('../lib/find-node-directory')
+'use strict'
-var platforms = ['darwin', 'freebsd', 'linux', 'sunos', 'win32', 'aix']
+const { describe, it } = require('mocha')
+const assert = require('assert')
+const path = require('path')
+const findNodeDirectory = require('../lib/find-node-directory')
-// we should find the directory based on the directory
-// the script is running in and it should match the layout
-// in a build tree where npm is installed in
-// .... /deps/npm
-test('test find-node-directory - node install', function (t) {
- t.plan(platforms.length)
- for (var next = 0; next < platforms.length; next++) {
- var processObj = {execPath: '/x/y/bin/node', platform: platforms[next]}
- t.equal(
- findNodeDirectory('/x/deps/npm/node_modules/node-gyp/lib', processObj),
- path.join('/x'))
- }
-})
+const platforms = ['darwin', 'freebsd', 'linux', 'sunos', 'win32', 'aix', 'os400']
-// we should find the directory based on the directory
-// the script is running in and it should match the layout
-// in an installed tree where npm is installed in
-// .... /lib/node_modules/npm or .../node_modules/npm
-// depending on the patform
-test('test find-node-directory - node build', function (t) {
- t.plan(platforms.length)
- for (var next = 0; next < platforms.length; next++) {
- var processObj = {execPath: '/x/y/bin/node', platform: platforms[next]}
- if (platforms[next] === 'win32') {
- t.equal(
- findNodeDirectory('/y/node_modules/npm/node_modules/node-gyp/lib',
- processObj), path.join('/y'))
- } else {
- t.equal(
- findNodeDirectory('/y/lib/node_modules/npm/node_modules/node-gyp/lib',
- processObj), path.join('/y'))
+describe('find-node-directory', function () {
+ // we should find the directory based on the directory
+ // the script is running in and it should match the layout
+ // in a build tree where npm is installed in
+ // .... /deps/npm
+ it('test find-node-directory - node install', function () {
+ for (let next = 0; next < platforms.length; next++) {
+ const processObj = { execPath: '/x/y/bin/node', platform: platforms[next] }
+ assert.strictEqual(
+ findNodeDirectory('/x/deps/npm/node_modules/node-gyp/lib', processObj),
+ path.join('/x'))
}
- }
-})
+ })
-// we should find the directory based on the execPath
-// for node and match because it was in the bin directory
-test('test find-node-directory - node in bin directory', function (t) {
- t.plan(platforms.length)
- for (var next = 0; next < platforms.length; next++) {
- var processObj = {execPath: '/x/y/bin/node', platform: platforms[next]}
- t.equal(
- findNodeDirectory('/nothere/npm/node_modules/node-gyp/lib', processObj),
- path.join('/x/y'))
- }
-})
+ // we should find the directory based on the directory
+ // the script is running in and it should match the layout
+ // in an installed tree where npm is installed in
+ // .... /lib/node_modules/npm or .../node_modules/npm
+ // depending on the patform
+ it('test find-node-directory - node build', function () {
+ for (let next = 0; next < platforms.length; next++) {
+ const processObj = { execPath: '/x/y/bin/node', platform: platforms[next] }
+ if (platforms[next] === 'win32') {
+ assert.strictEqual(
+ findNodeDirectory('/y/node_modules/npm/node_modules/node-gyp/lib',
+ processObj), path.join('/y'))
+ } else {
+ assert.strictEqual(
+ findNodeDirectory('/y/lib/node_modules/npm/node_modules/node-gyp/lib',
+ processObj), path.join('/y'))
+ }
+ }
+ })
-// we should find the directory based on the execPath
-// for node and match because it was in the Release directory
-test('test find-node-directory - node in build release dir', function (t) {
- t.plan(platforms.length)
- for (var next = 0; next < platforms.length; next++) {
- var processObj
- if (platforms[next] === 'win32') {
- processObj = {execPath: '/x/y/Release/node', platform: platforms[next]}
- } else {
- processObj = {execPath: '/x/y/out/Release/node',
- platform: platforms[next]}
+ // we should find the directory based on the execPath
+ // for node and match because it was in the bin directory
+ it('test find-node-directory - node in bin directory', function () {
+ for (let next = 0; next < platforms.length; next++) {
+ const processObj = { execPath: '/x/y/bin/node', platform: platforms[next] }
+ assert.strictEqual(
+ findNodeDirectory('/nothere/npm/node_modules/node-gyp/lib', processObj),
+ path.join('/x/y'))
}
+ })
- t.equal(
- findNodeDirectory('/nothere/npm/node_modules/node-gyp/lib', processObj),
- path.join('/x/y'))
- }
-})
+ // we should find the directory based on the execPath
+ // for node and match because it was in the Release directory
+ it('test find-node-directory - node in build release dir', function () {
+ for (let next = 0; next < platforms.length; next++) {
+ let processObj
+ if (platforms[next] === 'win32') {
+ processObj = { execPath: '/x/y/Release/node', platform: platforms[next] }
+ } else {
+ processObj = {
+ execPath: '/x/y/out/Release/node',
+ platform: platforms[next]
+ }
+ }
-// we should find the directory based on the execPath
-// for node and match because it was in the Debug directory
-test('test find-node-directory - node in Debug release dir', function (t) {
- t.plan(platforms.length)
- for (var next = 0; next < platforms.length; next++) {
- var processObj
- if (platforms[next] === 'win32') {
- processObj = {execPath: '/a/b/Debug/node', platform: platforms[next]}
- } else {
- processObj = {execPath: '/a/b/out/Debug/node', platform: platforms[next]}
+ assert.strictEqual(
+ findNodeDirectory('/nothere/npm/node_modules/node-gyp/lib', processObj),
+ path.join('/x/y'))
}
+ })
- t.equal(
- findNodeDirectory('/nothere/npm/node_modules/node-gyp/lib', processObj),
- path.join('/a/b'))
- }
-})
+ // we should find the directory based on the execPath
+ // for node and match because it was in the Debug directory
+ it('test find-node-directory - node in Debug release dir', function () {
+ for (let next = 0; next < platforms.length; next++) {
+ let processObj
+ if (platforms[next] === 'win32') {
+ processObj = { execPath: '/a/b/Debug/node', platform: platforms[next] }
+ } else {
+ processObj = { execPath: '/a/b/out/Debug/node', platform: platforms[next] }
+ }
-// we should not find it as it will not match based on the execPath nor
-// the directory from which the script is running
-test('test find-node-directory - not found', function (t) {
- t.plan(platforms.length)
- for (var next = 0; next < platforms.length; next++) {
- var processObj = {execPath: '/x/y/z/y', platform:next}
- t.equal(findNodeDirectory('/a/b/c/d', processObj), '')
- }
-})
+ assert.strictEqual(
+ findNodeDirectory('/nothere/npm/node_modules/node-gyp/lib', processObj),
+ path.join('/a/b'))
+ }
+ })
-// we should find the directory based on the directory
-// the script is running in and it should match the layout
-// in a build tree where npm is installed in
-// .... /deps/npm
-// same test as above but make sure additional directory entries
-// don't cause an issue
-test('test find-node-directory - node install', function (t) {
- t.plan(platforms.length)
- for (var next = 0; next < platforms.length; next++) {
- var processObj = {execPath: '/x/y/bin/node', platform: platforms[next]}
- t.equal(
- findNodeDirectory('/x/y/z/a/b/c/deps/npm/node_modules/node-gyp/lib',
- processObj), path.join('/x/y/z/a/b/c'))
- }
+ // we should not find it as it will not match based on the execPath nor
+ // the directory from which the script is running
+ it('test find-node-directory - not found', function () {
+ for (let next = 0; next < platforms.length; next++) {
+ const processObj = { execPath: '/x/y/z/y', platform: next }
+ assert.strictEqual(findNodeDirectory('/a/b/c/d', processObj), '')
+ }
+ })
+
+ // we should find the directory based on the directory
+ // the script is running in and it should match the layout
+ // in a build tree where npm is installed in
+ // .... /deps/npm
+ // same test as above but make sure additional directory entries
+ // don't cause an issue
+ it('test find-node-directory - node install', function () {
+ for (let next = 0; next < platforms.length; next++) {
+ const processObj = { execPath: '/x/y/bin/node', platform: platforms[next] }
+ assert.strictEqual(
+ findNodeDirectory('/x/y/z/a/b/c/deps/npm/node_modules/node-gyp/lib',
+ processObj), path.join('/x/y/z/a/b/c'))
+ }
+ })
})
diff --git a/test/test-find-python.js b/test/test-find-python.js
index 570eb180de..dc7192809a 100644
--- a/test/test-find-python.js
+++ b/test/test-find-python.js
@@ -1,339 +1,204 @@
'use strict'
-var test = require('tape')
-var path = require('path')
-var configure = require('../lib/configure')
-var execFile = require('child_process').execFile
-var PythonFinder = configure.test.PythonFinder
-
-test('find python', function (t) {
- t.plan(4)
-
- configure.test.findPython('python', function (err, found) {
- t.strictEqual(err, null)
- var proc = execFile(found, ['-V'], function (err, stdout, stderr) {
- t.strictEqual(err, null)
- t.strictEqual(stdout, '')
- t.ok(/Python 2/.test(stderr))
- })
- proc.stdout.setEncoding('utf-8')
- proc.stderr.setEncoding('utf-8')
- })
-})
-
-function poison(object, property) {
- function fail() {
- throw new Error('Property ' + property + ' should not have been accessed.')
- }
- var descriptor = {
- configurable: true,
- enumerable: false,
- writable: true,
- getter: fail,
- setter: fail,
+delete process.env.PYTHON
+
+const { describe, it, after } = require('mocha')
+const assert = require('assert')
+const PythonFinder = require('../lib/find-python')
+const { execFile } = require('../lib/util')
+const { poison } = require('./common')
+const fs = require('fs')
+const path = require('path')
+const os = require('os')
+
+class TestPythonFinder extends PythonFinder {
+ constructor (...args) {
+ super(...args)
+ delete this.env.NODE_GYP_FORCE_PYTHON
+ }
+
+ async findPython () {
+ try {
+ return { err: null, python: await super.findPython() }
+ } catch (err) {
+ return { err, python: null }
+ }
}
- Object.defineProperty(object, property, descriptor)
-}
-
-// Work around a v0.10.x CI issue where path.resolve() on UNIX systems prefixes
-// Windows paths with the current working directory. v0.12 and up are free of
-// this issue because they use path.win32.resolve() which does the right thing.
-var resolve = path.win32 && path.win32.resolve || function() {
- function rstrip(s) { return s.replace(/\\+$/, '') }
- return [].slice.call(arguments).map(rstrip).join('\\')
}
-function TestPythonFinder() { PythonFinder.apply(this, arguments) }
-TestPythonFinder.prototype = Object.create(PythonFinder.prototype)
-poison(TestPythonFinder.prototype, 'env')
-poison(TestPythonFinder.prototype, 'execFile')
-poison(TestPythonFinder.prototype, 'resolve')
-poison(TestPythonFinder.prototype, 'stat')
-poison(TestPythonFinder.prototype, 'which')
-poison(TestPythonFinder.prototype, 'win')
-
-test('find python - python', function (t) {
- t.plan(5)
-
- var f = new TestPythonFinder('python', done)
- f.which = function(program, cb) {
- t.strictEqual(program, 'python')
- cb(null, program)
- }
- f.execFile = function(program, args, opts, cb) {
- t.strictEqual(program, 'python')
- t.ok(/import sys/.test(args[1]))
- cb(null, '2.7.0')
- }
- f.checkPython()
-
- function done(err, python) {
- t.strictEqual(err, null)
- t.strictEqual(python, 'python')
- }
-})
-
-test('find python - python too old', function (t) {
- t.plan(4)
-
- var f = new TestPythonFinder('python', done)
- f.which = function(program, cb) {
- t.strictEqual(program, 'python')
- cb(null, program)
- }
- f.execFile = function(program, args, opts, cb) {
- t.strictEqual(program, 'python')
- t.ok(/import sys/.test(args[1]))
- cb(null, '2.3.4')
- }
- f.checkPython()
-
- function done(err, python) {
- t.ok(/is not supported by gyp/.test(err))
- }
-})
-
-test('find python - python too new', function (t) {
- t.plan(4)
-
- var f = new TestPythonFinder('python', done)
- f.which = function(program, cb) {
- t.strictEqual(program, 'python')
- cb(null, program)
- }
- f.execFile = function(program, args, opts, cb) {
- t.strictEqual(program, 'python')
- t.ok(/import sys/.test(args[1]))
- cb(null, '3.0.0')
- }
- f.checkPython()
-
- function done(err, python) {
- t.ok(/is not supported by gyp/.test(err))
- }
-})
-
-test('find python - no python', function (t) {
- t.plan(2)
-
- var f = new TestPythonFinder('python', done)
- f.which = function(program, cb) {
- t.strictEqual(program, 'python')
- cb(new Error('not found'))
- }
- f.checkPython()
-
- function done(err, python) {
- t.ok(/Can't find Python executable/.test(err))
- }
-})
+describe('find-python', function () {
+ it('find python', async function () {
+ const found = await PythonFinder.findPython(null)
+ const [err, stdout, stderr] = await execFile(found, ['-V'], { encoding: 'utf-8' })
+ assert.strictEqual(err, null)
+ assert.ok(/Python 3/.test(stdout))
+ assert.strictEqual(stderr, '')
+ })
-test('find python - no python2', function (t) {
- t.plan(6)
+ it('find python - encoding', async function () {
+ const found = await PythonFinder.findPython(null)
+ const testFolderPath = fs.mkdtempSync(path.join(os.tmpdir(), 'test-ü-'))
+ const testFilePath = path.join(testFolderPath, 'python.exe')
+ after(function () {
+ try {
+ fs.unlinkSync(testFilePath)
+ fs.rmdirSync(testFolderPath)
+ } catch {}
+ })
- var f = new TestPythonFinder('python2', done)
- f.which = function(program, cb) {
- f.which = function(program, cb) {
- t.strictEqual(program, 'python')
- cb(null, program)
+ try {
+ fs.symlinkSync(found, testFilePath)
+ } catch (err) {
+ switch (err.code) {
+ case 'EPERM':
+ return assert.fail(err, null, 'Please try to run console as an administrator')
+ default:
+ return assert.fail(err)
+ }
}
- t.strictEqual(program, 'python2')
- cb(new Error('not found'))
- }
- f.execFile = function(program, args, opts, cb) {
- t.strictEqual(program, 'python')
- t.ok(/import sys/.test(args[1]))
- cb(null, '2.7.0')
- }
- f.checkPython()
- function done(err, python) {
- t.strictEqual(err, null)
- t.strictEqual(python, 'python')
- }
-})
-
-test('find python - no python2, no python, unix', function (t) {
- t.plan(3)
-
- var f = new TestPythonFinder('python2', done)
- poison(f, 'checkPythonLauncher')
- f.win = false
+ const finder = new PythonFinder(testFilePath)
+ await assert.doesNotReject(finder.checkCommand(testFilePath))
+ })
- f.which = function(program, cb) {
- f.which = function(program, cb) {
- t.strictEqual(program, 'python')
- cb(new Error('not found'))
+ it('find python - python', async function () {
+ const f = new TestPythonFinder('python')
+ f.execFile = async function (program, args, opts) {
+ f.execFile = async function (program, args, opts) {
+ poison(f, 'execFile')
+ assert.strictEqual(program, '/path/python')
+ assert.ok(/sys\.version_info/.test(args[1]))
+ return [null, '3.9.1']
+ }
+ assert.strictEqual(program, process.platform === 'win32' ? '"python"' : 'python')
+ assert.ok(/sys\.executable/.test(args[1]))
+ return [null, '/path/python']
}
- t.strictEqual(program, 'python2')
- cb(new Error('not found'))
- }
- f.checkPython()
-
- function done(err, python) {
- t.ok(/Can't find Python executable/.test(err))
- }
-})
-
-test('find python - no python, use python launcher', function (t) {
- t.plan(8)
- var f = new TestPythonFinder('python', done)
- f.env = {}
- f.win = true
+ const { err, python } = await f.findPython()
+ assert.strictEqual(err, null)
+ assert.strictEqual(python, '/path/python')
+ })
- f.which = function(program, cb) {
- t.strictEqual(program, 'python')
- cb(new Error('not found'))
- }
- f.execFile = function(program, args, opts, cb) {
- f.execFile = function(program, args, opts, cb) {
- t.strictEqual(program, 'Z:\\snake.exe')
- t.ok(/import sys/.test(args[1]))
- cb(null, '2.7.0')
+ it('find python - python too old', async function () {
+ const f = new TestPythonFinder(null)
+ f.execFile = async function (program, args, opts) {
+ if (/sys\.executable/.test(args[args.length - 1])) {
+ return [null, '/path/python']
+ } else if (/sys\.version_info/.test(args[args.length - 1])) {
+ return [null, '2.3.4']
+ } else {
+ assert.fail()
+ }
}
- t.strictEqual(program, 'py.exe')
- t.notEqual(args.indexOf('-2'), -1)
- t.notEqual(args.indexOf('-c'), -1)
- cb(null, 'Z:\\snake.exe')
- }
- f.checkPython()
-
- function done(err, python) {
- t.strictEqual(err, null)
- t.strictEqual(python, 'Z:\\snake.exe')
- }
-})
-test('find python - python 3, use python launcher', function (t) {
- t.plan(10)
-
- var f = new TestPythonFinder('python', done)
- f.env = {}
- f.win = true
+ const { err } = await f.findPython()
+ assert.ok(/Could not find any Python/.test(err))
+ assert.ok(/not supported/i.test(f.errorLog))
+ })
- f.which = function(program, cb) {
- t.strictEqual(program, 'python')
- cb(null, program)
- }
- f.execFile = function(program, args, opts, cb) {
- f.execFile = function(program, args, opts, cb) {
- f.execFile = function(program, args, opts, cb) {
- t.strictEqual(program, 'Z:\\snake.exe')
- t.ok(/import sys/.test(args[1]))
- cb(null, '2.7.0')
+ it('find python - no python', async function () {
+ const f = new TestPythonFinder(null)
+ f.execFile = async function (program, args, opts) {
+ if (/sys\.executable/.test(args[args.length - 1])) {
+ throw new Error('not found')
+ } else if (/sys\.version_info/.test(args[args.length - 1])) {
+ throw new Error('not a Python executable')
+ } else {
+ assert.fail()
}
- t.strictEqual(program, 'py.exe')
- t.notEqual(args.indexOf('-2'), -1)
- t.notEqual(args.indexOf('-c'), -1)
- cb(null, 'Z:\\snake.exe')
}
- t.strictEqual(program, 'python')
- t.ok(/import sys/.test(args[1]))
- cb(null, '3.0.0')
- }
- f.checkPython()
-
- function done(err, python) {
- t.strictEqual(err, null)
- t.strictEqual(python, 'Z:\\snake.exe')
- }
-})
-test('find python - python 3, use python launcher, python 2 too old',
- function (t) {
- t.plan(9)
+ const { err } = await f.findPython()
+ assert.ok(/Could not find any Python/.test(err))
+ assert.ok(/not in PATH/.test(f.errorLog))
+ })
- var f = new TestPythonFinder('python', done)
- f.checkedPythonLauncher = false
- f.env = {}
- f.win = true
+ it('find python - no python2, no python, unix', async function () {
+ const f = new TestPythonFinder(null)
+ f.checkPyLauncher = assert.fail
+ f.win = false
- f.which = function(program, cb) {
- t.strictEqual(program, 'python')
- cb(null, program)
- }
- f.execFile = function(program, args, opts, cb) {
- f.execFile = function(program, args, opts, cb) {
- f.execFile = function(program, args, opts, cb) {
- t.strictEqual(program, 'Z:\\snake.exe')
- t.ok(/import sys/.test(args[1]))
- cb(null, '2.3.4')
+ f.execFile = async function (program, args, opts) {
+ if (/sys\.executable/.test(args[args.length - 1])) {
+ throw new Error('not found')
+ } else {
+ assert.fail()
}
- t.strictEqual(program, 'py.exe')
- t.notEqual(args.indexOf('-2'), -1)
- t.notEqual(args.indexOf('-c'), -1)
- cb(null, 'Z:\\snake.exe')
}
- t.strictEqual(program, 'python')
- t.ok(/import sys/.test(args[1]))
- cb(null, '3.0.0')
- }
- f.checkPython()
- function done(err, python) {
- t.ok(/is not supported by gyp/.test(err))
- }
-})
-
-test('find python - no python, no python launcher, good guess', function (t) {
- t.plan(6)
+ const { err } = await f.findPython()
+ assert.ok(/Could not find any Python/.test(err))
+ assert.ok(/not in PATH/.test(f.errorLog))
+ })
- var re = /C:[\\\/]Python27[\\\/]python[.]exe/
- var f = new TestPythonFinder('python', done)
- f.env = {}
- f.win = true
+ it('find python - no python, use python launcher', async function () {
+ const f = new TestPythonFinder(null)
+ f.win = true
- f.which = function(program, cb) {
- t.strictEqual(program, 'python')
- cb(new Error('not found'))
- }
- f.execFile = function(program, args, opts, cb) {
- f.execFile = function(program, args, opts, cb) {
- t.ok(re.test(program))
- t.ok(/import sys/.test(args[1]))
- cb(null, '2.7.0')
+ f.execFile = async function (program, args, opts) {
+ if (program === 'py.exe') {
+ assert.notStrictEqual(args.indexOf('-3'), -1)
+ assert.notStrictEqual(args.indexOf('-c'), -1)
+ return [null, 'Z:\\snake.exe']
+ }
+ if (/sys\.executable/.test(args[args.length - 1])) {
+ throw new Error('not found')
+ } else if (f.winDefaultLocations.includes(program)) {
+ throw new Error('not found')
+ } else if (/sys\.version_info/.test(args[args.length - 1])) {
+ if (program === 'Z:\\snake.exe') {
+ return [null, '3.9.0']
+ } else {
+ assert.fail()
+ }
+ } else {
+ assert.fail()
+ }
}
- t.strictEqual(program, 'py.exe')
- cb(new Error('not found'))
- }
- f.resolve = resolve
- f.stat = function(path, cb) {
- t.ok(re.test(path))
- cb(null, {})
- }
- f.checkPython()
-
- function done(err, python) {
- t.ok(re.test(python))
- }
-})
-
-test('find python - no python, no python launcher, bad guess', function (t) {
- t.plan(4)
+ const { err, python } = await f.findPython()
+ assert.strictEqual(err, null)
+ assert.strictEqual(python, 'Z:\\snake.exe')
+ })
- var f = new TestPythonFinder('python', done)
- f.env = { SystemDrive: 'Z:\\' }
- f.win = true
+ it('find python - no python, no python launcher, good guess', async function () {
+ const f = new TestPythonFinder(null)
+ f.win = true
+ const expectedProgram = f.winDefaultLocations[0]
- f.which = function(program, cb) {
- t.strictEqual(program, 'python')
- cb(new Error('not found'))
- }
- f.execFile = function(program, args, opts, cb) {
- t.strictEqual(program, 'py.exe')
- cb(new Error('not found'))
- }
- f.resolve = resolve
- f.stat = function(path, cb) {
- t.ok(/Z:[\\\/]Python27[\\\/]python.exe/.test(path))
- var err = new Error('not found')
- err.code = 'ENOENT'
- cb(err)
- }
- f.checkPython()
+ f.execFile = async function (program, args, opts) {
+ if (program === 'py.exe') {
+ throw new Error('not found')
+ }
+ if (/sys\.executable/.test(args[args.length - 1])) {
+ throw new Error('not found')
+ } else if (program === expectedProgram &&
+ /sys\.version_info/.test(args[args.length - 1])) {
+ return [null, '3.7.3']
+ } else {
+ assert.fail()
+ }
+ }
+ const { err, python } = await f.findPython()
+ assert.strictEqual(err, null)
+ assert.ok(python === expectedProgram)
+ })
- function done(err, python) {
- t.ok(/Can't find Python executable/.test(err))
- }
+ it('find python - no python, no python launcher, bad guess', async function () {
+ const f = new TestPythonFinder(null)
+ f.win = true
+
+ f.execFile = async function (program, args, opts) {
+ if (/sys\.executable/.test(args[args.length - 1])) {
+ throw new Error('not found')
+ } else if (/sys\.version_info/.test(args[args.length - 1])) {
+ throw new Error('not a Python executable')
+ } else {
+ assert.fail()
+ }
+ }
+ const { err } = await f.findPython()
+ assert.ok(/Could not find any Python/.test(err))
+ assert.ok(/not in PATH/.test(f.errorLog))
+ })
})
diff --git a/test/test-find-visualstudio.js b/test/test-find-visualstudio.js
new file mode 100644
index 0000000000..5f1bbe768c
--- /dev/null
+++ b/test/test-find-visualstudio.js
@@ -0,0 +1,936 @@
+'use strict'
+
+const { describe, it } = require('mocha')
+const assert = require('assert')
+const fs = require('fs')
+const path = require('path')
+const VisualStudioFinder = require('../lib/find-visualstudio')
+const { poison } = require('./common')
+
+const semverV1 = { major: 1, minor: 0, patch: 0 }
+
+delete process.env.VCINSTALLDIR
+
+class TestVisualStudioFinder extends VisualStudioFinder {
+ async findVisualStudio () {
+ try {
+ return { err: null, info: await super.findVisualStudio() }
+ } catch (err) {
+ return { err, info: null }
+ }
+ }
+}
+
+describe('find-visualstudio', function () {
+ this.beforeAll(function () {
+ // Condition to skip the test suite
+ if (process.env.SystemRoot === undefined) {
+ process.env.SystemRoot = '/'
+ }
+ })
+
+ it('VS2013', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null)
+ finder.findNewVSUsingSetupModule = async () => null
+ finder.findNewVS = async () => null
+ finder.regSearchKeys = async (keys, value, addOpts) => {
+ for (let i = 0; i < keys.length; ++i) {
+ const fullName = `${keys[i]}\\${value}`
+ switch (fullName) {
+ case 'HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7\\14.0':
+ case 'HKLM\\Software\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VC7\\14.0':
+ continue
+ case 'HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7\\12.0':
+ assert.ok(true, `expected search for registry value ${fullName}`)
+ return 'C:\\VS2013\\VC\\'
+ case 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions\\12.0\\MSBuildToolsPath':
+ assert.ok(true, `expected search for registry value ${fullName}`)
+ return 'C:\\MSBuild12\\'
+ default:
+ assert.fail(`unexpected search for registry value ${fullName}`)
+ }
+ }
+ throw new Error()
+ }
+
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info, {
+ msBuild: 'C:\\MSBuild12\\MSBuild.exe',
+ path: 'C:\\VS2013',
+ sdk: null,
+ toolset: 'v120',
+ version: '12.0',
+ versionMajor: 12,
+ versionMinor: 0,
+ versionYear: 2013
+ })
+ })
+
+ it('VS2013 should not be found on new node versions', async function () {
+ const finder = new TestVisualStudioFinder({
+ major: 10,
+ minor: 0,
+ patch: 0
+ }, null)
+
+ finder.findNewVSUsingSetupModule = async () => null
+ finder.findVisualStudio2019OrNewer = async () => {
+ const file = path.join(__dirname, 'fixtures', 'VS_2017_Unusable.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2019, 2022, 2026])
+ }
+ finder.findVisualStudio2017 = async () => {
+ const file = path.join(__dirname, 'fixtures', 'VS_2017_Unusable.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2017])
+ }
+ finder.regSearchKeys = async (keys, value, addOpts) => {
+ for (let i = 0; i < keys.length; ++i) {
+ const fullName = `${keys[i]}\\${value}`
+ switch (fullName) {
+ case 'HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7\\14.0':
+ case 'HKLM\\Software\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VC7\\14.0':
+ continue
+ default:
+ assert.fail(`unexpected search for registry value ${fullName}`)
+ }
+ }
+ throw new Error()
+ }
+
+ const { err, info } = await finder.findVisualStudio()
+ assert.ok(/find .* Visual Studio/i.test(err), 'expect error')
+ assert.ok(!info, 'no data')
+ })
+
+ it('VS2015', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null)
+ finder.findNewVSUsingSetupModule = async () => null
+ finder.findNewVS = async () => null
+ finder.regSearchKeys = async (keys, value, addOpts) => {
+ for (let i = 0; i < keys.length; ++i) {
+ const fullName = `${keys[i]}\\${value}`
+ switch (fullName) {
+ case 'HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7\\14.0':
+ assert.ok(true, `expected search for registry value ${fullName}`)
+ return 'C:\\VS2015\\VC\\'
+ case 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions\\14.0\\MSBuildToolsPath':
+ assert.ok(true, `expected search for registry value ${fullName}`)
+ return 'C:\\MSBuild14\\'
+ default:
+ assert.fail(`unexpected search for registry value ${fullName}`)
+ }
+ }
+ throw new Error()
+ }
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info, {
+ msBuild: 'C:\\MSBuild14\\MSBuild.exe',
+ path: 'C:\\VS2015',
+ sdk: null,
+ toolset: 'v140',
+ version: '14.0',
+ versionMajor: 14,
+ versionMinor: 0,
+ versionYear: 2015
+ })
+ })
+
+ it('error from PowerShell', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null, null)
+
+ const vsInfo = finder.parseData(new Error('Error msg'), '', '')
+ assert.ok(/use PowerShell/i.test(finder.errorLog[0]), `expect error, output: ${finder.errorLog[0]}`)
+ assert.ok(/error msg/i.test(finder.errorLog[0]), `expect error, output: ${finder.errorLog[0]}`)
+ assert.equal(vsInfo, null)
+ })
+
+ it('empty output from PowerShell', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null, null)
+
+ const vsInfo = finder.parseData(null, '', '', { checkIsArray: true })
+ assert.ok(/use PowerShell/i.test(finder.errorLog[0]), `expect error, output: ${finder.errorLog[0]}`)
+ assert.equal(vsInfo, null)
+ })
+
+ it('output from PowerShell not JSON', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null, null)
+
+ const vsInfo = finder.parseData(null, 'AAAABBBB', '', { checkIsArray: true })
+ assert.ok(/use PowerShell/i.test(finder.errorLog[0]), `expect error, output: ${finder.errorLog[0]}`)
+ assert.equal(vsInfo, null)
+ })
+
+ it('wrong JSON from PowerShell', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null, null)
+
+ const vsInfo = finder.parseData(null, '{}', '', { checkIsArray: true })
+ assert.ok(/use PowerShell/i.test(finder.errorLog[0]), `expect error, output: ${finder.errorLog[0]}`)
+ assert.ok(/expected array/i.test(finder.errorLog[0]), `expect error, output: ${finder.errorLog[0]}`)
+ assert.equal(vsInfo, null)
+ })
+
+ it('empty JSON from PowerShell', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null, null)
+
+ const vsInfo = finder.parseData(null, '[]', '', { checkIsArray: true })
+ assert.equal(finder.errorLog.length, 0)
+ assert.equal(vsInfo.length, 0)
+ })
+
+ it('future version', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null, null)
+
+ const vsInfo = finder.parseData(null, JSON.stringify([{
+ packages: [
+ 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
+ 'Microsoft.VisualStudio.Component.Windows10SDK.17763',
+ 'Microsoft.VisualStudio.VC.MSBuild.Base'
+ ],
+ path: 'C:\\VS',
+ version: '9999.9999.9999.9999'
+ }]), '', { checkIsArray: true })
+ assert.equal(finder.errorLog.length, 0)
+ assert.equal(vsInfo[0].packages.length, 3)
+ })
+
+ it('single unusable VS2017', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null, null)
+
+ const file = path.join(__dirname, 'fixtures', 'VS_2017_Unusable.txt')
+ const data = fs.readFileSync(file)
+ const vsInfo = finder.parseData(null, data, '', { checkIsArray: true })
+ assert.equal(finder.errorLog.length, 0)
+ assert.equal(vsInfo.length, 1)
+ assert.equal(vsInfo[0].InstallationPath, undefined)
+ assert.notEqual(vsInfo[0].path, undefined)
+ assert.ok(vsInfo[0].packages.length > 1)
+ })
+
+ it('minimal VS2017 Build Tools', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null)
+
+ poison(finder, 'regSearchKeys')
+ finder.findNewVSUsingSetupModule = async () => null
+ finder.findVisualStudio2019OrNewer = async () => {
+ const file = path.join(__dirname, 'fixtures',
+ 'VS_2017_BuildTools_minimal.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2019, 2022, 2026])
+ }
+ finder.findVisualStudio2017 = async () => {
+ const file = path.join(__dirname, 'fixtures',
+ 'VS_2017_BuildTools_minimal.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2017])
+ }
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info, {
+ msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\' +
+ 'BuildTools\\MSBuild\\15.0\\Bin\\MSBuild.exe',
+ path:
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools',
+ sdk: '10.0.17134.0',
+ toolset: 'v141',
+ version: '15.9.28307.665',
+ versionMajor: 15,
+ versionMinor: 9,
+ versionYear: 2017
+ })
+ })
+
+ it('VS2017 Community with C++ workload', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null)
+
+ poison(finder, 'regSearchKeys')
+ finder.findNewVSUsingSetupModule = async () => null
+ finder.findVisualStudio2019OrNewer = async () => {
+ const file = path.join(__dirname, 'fixtures',
+ 'VS_2017_Community_workload.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2019, 2022, 2026])
+ }
+ finder.findVisualStudio2017 = async () => {
+ const file = path.join(__dirname, 'fixtures',
+ 'VS_2017_Community_workload.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2017])
+ }
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info, {
+ msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\' +
+ 'Community\\MSBuild\\15.0\\Bin\\MSBuild.exe',
+ path:
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community',
+ sdk: '10.0.17763.0',
+ toolset: 'v141',
+ version: '15.9.28307.665',
+ versionMajor: 15,
+ versionMinor: 9,
+ versionYear: 2017
+ })
+ })
+
+ it('VS2017 Express', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null)
+
+ poison(finder, 'regSearchKeys')
+ finder.findNewVSUsingSetupModule = async () => null
+ finder.findVisualStudio2019OrNewer = async () => {
+ const file = path.join(__dirname, 'fixtures', 'VS_2017_Express.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2019, 2022, 2026])
+ }
+ finder.findVisualStudio2017 = async () => {
+ const file = path.join(__dirname, 'fixtures', 'VS_2017_Express.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2017])
+ }
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info, {
+ msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\' +
+ 'WDExpress\\MSBuild\\15.0\\Bin\\MSBuild.exe',
+ path:
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\WDExpress',
+ sdk: '10.0.17763.0',
+ toolset: 'v141',
+ version: '15.9.28307.858',
+ versionMajor: 15,
+ versionMinor: 9,
+ versionYear: 2017
+ })
+ })
+
+ it('VS2019 Preview with C++ workload', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null)
+
+ poison(finder, 'regSearchKeys')
+ finder.findNewVSUsingSetupModule = async () => null
+ finder.findVisualStudio2019OrNewer = async () => {
+ const file = path.join(__dirname, 'fixtures',
+ 'VS_2019_Preview.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2019, 2022, 2026])
+ }
+ finder.findVisualStudio2017 = async () => {
+ const file = path.join(__dirname, 'fixtures',
+ 'VS_2019_Preview.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2017])
+ }
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info, {
+ msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\' +
+ 'Preview\\MSBuild\\Current\\Bin\\MSBuild.exe',
+ path:
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Preview',
+ sdk: '10.0.17763.0',
+ toolset: 'v142',
+ version: '16.0.28608.199',
+ versionMajor: 16,
+ versionMinor: 0,
+ versionYear: 2019
+ })
+ })
+
+ it('minimal VS2019 Build Tools', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null)
+
+ poison(finder, 'regSearchKeys')
+ finder.findNewVSUsingSetupModule = async () => null
+ finder.findVisualStudio2019OrNewer = async () => {
+ const file = path.join(__dirname, 'fixtures',
+ 'VS_2019_BuildTools_minimal.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2019, 2022, 2026])
+ }
+ finder.findVisualStudio2017 = async () => {
+ const file = path.join(__dirname, 'fixtures',
+ 'VS_2019_BuildTools_minimal.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2017])
+ }
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info, {
+ msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\' +
+ 'BuildTools\\MSBuild\\Current\\Bin\\MSBuild.exe',
+ path:
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools',
+ sdk: '10.0.17134.0',
+ toolset: 'v142',
+ version: '16.1.28922.388',
+ versionMajor: 16,
+ versionMinor: 1,
+ versionYear: 2019
+ })
+ })
+
+ it('VS2019 Community with C++ workload', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null)
+
+ poison(finder, 'regSearchKeys')
+ finder.findNewVSUsingSetupModule = async () => null
+ finder.findVisualStudio2019OrNewer = async () => {
+ const file = path.join(__dirname, 'fixtures',
+ 'VS_2019_Community_workload.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2019, 2022, 2026])
+ }
+ finder.findVisualStudio2017 = async () => {
+ const file = path.join(__dirname, 'fixtures',
+ 'VS_2019_Community_workload.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2017])
+ }
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info, {
+ msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\' +
+ 'Community\\MSBuild\\Current\\Bin\\MSBuild.exe',
+ path:
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community',
+ sdk: '10.0.17763.0',
+ toolset: 'v142',
+ version: '16.1.28922.388',
+ versionMajor: 16,
+ versionMinor: 1,
+ versionYear: 2019
+ })
+ })
+
+ it('VS2022 Preview with C++ workload', async function () {
+ const msBuildPath = process.arch === 'arm64'
+ ? 'C:\\Program Files\\Microsoft Visual Studio\\2022\\' +
+ 'Community\\MSBuild\\Current\\Bin\\arm64\\MSBuild.exe'
+ : 'C:\\Program Files\\Microsoft Visual Studio\\2022\\' +
+ 'Community\\MSBuild\\Current\\Bin\\MSBuild.exe'
+
+ const finder = new TestVisualStudioFinder(semverV1, null)
+
+ poison(finder, 'regSearchKeys')
+ finder.msBuildPathExists = (path) => {
+ return true
+ }
+ finder.findNewVSUsingSetupModule = async () => null
+ finder.findVisualStudio2019OrNewer = async () => {
+ const file = path.join(__dirname, 'fixtures',
+ 'VS_2022_Community_workload.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2019, 2022, 2026])
+ }
+ finder.findVisualStudio2017 = async () => {
+ const file = path.join(__dirname, 'fixtures',
+ 'VS_2022_Community_workload.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2017])
+ }
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info, {
+ msBuild: msBuildPath,
+ path:
+ 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Community',
+ sdk: '10.0.22621.0',
+ toolset: 'v143',
+ version: '17.4.33213.308',
+ versionMajor: 17,
+ versionMinor: 4,
+ versionYear: 2022
+ })
+ })
+
+ it('VS2026 Preview with C++ workload', async function () {
+ const msBuildPath = process.arch === 'arm64'
+ ? 'C:\\Program Files\\Microsoft Visual Studio\\18\\' +
+ 'Insiders\\MSBuild\\Current\\Bin\\arm64\\MSBuild.exe'
+ : 'C:\\Program Files\\Microsoft Visual Studio\\18\\' +
+ 'Insiders\\MSBuild\\Current\\Bin\\MSBuild.exe'
+
+ const finder = new TestVisualStudioFinder(semverV1, null)
+
+ poison(finder, 'regSearchKeys')
+ finder.msBuildPathExists = (path) => {
+ return true
+ }
+ finder.findNewVSUsingSetupModule = async () => null
+ finder.findVisualStudio2019OrNewer = async () => {
+ const file = path.join(__dirname, 'fixtures',
+ 'VS_2026_Insiders_workload.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2019, 2022, 2026])
+ }
+ finder.findVisualStudio2017 = async () => {
+ const file = path.join(__dirname, 'fixtures',
+ 'VS_2026_Insiders_workload.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2017])
+ }
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info, {
+ msBuild: msBuildPath,
+ path:
+ 'C:\\Program Files\\Microsoft Visual Studio\\18\\Insiders',
+ sdk: '10.0.26100.0',
+ toolset: 'v145',
+ version: '18.3.11206.111',
+ versionMajor: 18,
+ versionMinor: 3,
+ versionYear: 2026
+ })
+ })
+
+ it('VS2026 Release with C++ workload', async function () {
+ const msBuildPath = process.arch === 'arm64'
+ ? 'C:\\Program Files\\Microsoft Visual Studio\\2026\\' +
+ 'Community\\MSBuild\\Current\\Bin\\arm64\\MSBuild.exe'
+ : 'C:\\Program Files\\Microsoft Visual Studio\\2026\\' +
+ 'Community\\MSBuild\\Current\\Bin\\MSBuild.exe'
+
+ const finder = new TestVisualStudioFinder(semverV1, null)
+
+ poison(finder, 'regSearchKeys')
+ finder.msBuildPathExists = (path) => {
+ return true
+ }
+ finder.findNewVSUsingSetupModule = async () => null
+ finder.findVisualStudio2019OrNewer = async () => {
+ // Create mock data for release version with 2026 path
+ const releaseData = [{
+ path: 'C:\\Program Files\\Microsoft Visual Studio\\2026\\Community',
+ version: '18.0.1000.100',
+ packages: [
+ 'Microsoft.VisualStudio.Product.Community',
+ 'Microsoft.VisualStudio.Workload.NativeDesktop',
+ 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
+ 'Microsoft.VisualStudio.Component.Windows11SDK.26100'
+ ]
+ }]
+ return finder.processData(releaseData, [2019, 2022, 2026])
+ }
+ finder.findVisualStudio2017 = async () => null
+
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info, {
+ msBuild: msBuildPath,
+ path:
+ 'C:\\Program Files\\Microsoft Visual Studio\\2026\\Community',
+ sdk: '10.0.26100.0',
+ toolset: 'v145',
+ version: '18.0.1000.100',
+ versionMajor: 18,
+ versionMinor: 0,
+ versionYear: 2026
+ })
+ })
+
+ it('VS2022 Build Tools with ARM64 MSVC only', async function () {
+ if (process.arch !== 'arm64') {
+ return
+ }
+
+ const msBuildPath = 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\' +
+ 'BuildTools\\MSBuild\\Current\\Bin\\arm64\\MSBuild.exe'
+
+ const finder = new TestVisualStudioFinder(semverV1, null)
+
+ poison(finder, 'regSearchKeys')
+ poison(finder, 'findVisualStudio2017')
+ finder.msBuildPathExists = (path) => {
+ return true
+ }
+ finder.findNewVSUsingSetupModule = async () => null
+ finder.findVisualStudio2019OrNewer = async () => {
+ const file = path.join(__dirname, 'fixtures',
+ 'VS_2022_BuildTools_arm64_only.txt')
+ const data = fs.readFileSync(file)
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2019, 2022, 2026])
+ }
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info, {
+ msBuild: msBuildPath,
+ path:
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools',
+ sdk: '10.0.22621.0',
+ toolset: 'v143',
+ version: '17.11.35222.181',
+ versionMajor: 17,
+ versionMinor: 11,
+ versionYear: 2022
+ })
+ })
+
+ it('VSSetup: VS2022 with C++ workload without SDK', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null)
+ finder.msBuildPathExists = (path) => {
+ return true
+ }
+ finder.findNewVS = async () => null
+ finder.findOldVS = async () => null
+ setupExecFixture(finder, 'VSSetup_VS_2022_workload_missing_sdk.txt')
+ const { err, info } = await finder.findVisualStudio()
+ assert.match(err.message, /could not find/i)
+ assert.strictEqual(info, null)
+ })
+
+ it('VSSetup: VS2019 with C++ workload', async function () {
+ await verifyVSSetupData('VSSetup_VS_2019_Professional_workload.txt', 'Professional', 2019,
+ '10.0.19041.0', 'v142', '16.11.34407.143', 'Program Files (x86)')
+ })
+
+ it('VSSetup: VS2022 with C++ workload', async function () {
+ await verifyVSSetupData('VSSetup_VS_2022_workload.txt', 'Enterprise', 2022,
+ '10.0.22000.0', 'v143', '17.8.34330.188', 'Program Files')
+ })
+
+ it('VSSetup: VS2022 and VS2019 with C++ workload', async function () {
+ await verifyVSSetupData('VSSetup_VS_2022_VS2019_workload.txt', 'Enterprise', 2022,
+ '10.0.22000.0', 'v143', '17.8.34330.188', 'Program Files')
+ })
+
+ it('VSSetup: VS2022 with multiple installations', async function () {
+ await verifyVSSetupData('VSSetup_VS_2022_multiple_install.txt', 'Enterprise', 2022,
+ '10.0.22000.0', 'v143', '17.8.34330.188', 'Program Files')
+ })
+
+ async function verifyVSSetupData (fixtureName, vsType, vsYear, sdkVersion, toolsetVersion, vsVersion, expectedProgramFilesPath) {
+ const msBuildPath = process.arch === 'arm64'
+ ? `C:\\${expectedProgramFilesPath}\\Microsoft Visual Studio\\${vsYear}\\` +
+ `${vsType}\\MSBuild\\Current\\Bin\\arm64\\MSBuild.exe`
+ : `C:\\${expectedProgramFilesPath}\\Microsoft Visual Studio\\${vsYear}\\` +
+ `${vsType}\\MSBuild\\Current\\Bin\\MSBuild.exe`
+
+ const finder = new TestVisualStudioFinder(semverV1, null)
+
+ poison(finder, 'regSearchKeys')
+ const expectedVSPath = `C:\\${expectedProgramFilesPath}\\Microsoft Visual Studio\\${vsYear}\\${vsType}`
+ finder.msBuildPathExists = (path) => {
+ if (path.startsWith(expectedVSPath) && path.endsWith('MSBuild.exe')) {
+ return true
+ }
+ return false
+ }
+ finder.findVisualStudio2019OrNewer = async () => {
+ throw new Error("findVisualStudio2019OrNewer shouldn't be called")
+ }
+ finder.findVisualStudio2017 = async () => {
+ throw new Error("findVisualStudio2017 shouldn't be called")
+ }
+ setupExecFixture(finder, fixtureName)
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ const vsVersionTokens = vsVersion.split('.')
+ assert.deepStrictEqual(info, {
+ msBuild: msBuildPath,
+ path:
+ `C:\\${expectedProgramFilesPath}\\Microsoft Visual Studio\\${vsYear}\\${vsType}`,
+ sdk: sdkVersion,
+ toolset: toolsetVersion,
+ version: vsVersion,
+ versionMajor: parseInt(vsVersionTokens[0]),
+ versionMinor: parseInt(vsVersionTokens[1]),
+ versionYear: vsYear
+ })
+ }
+
+ function setupExecFixture (finder, fixtureName) {
+ finder.execFile = async (exec, args) => {
+ if (args.length > 2 && args[2].includes('Get-Module')) {
+ return [null, '1.0.0', '']
+ } else if (args.length > 2 && args.at(-1).includes('Get-VSSetupInstance')) {
+ const file = path.join(__dirname, 'fixtures', fixtureName)
+ return [null, fs.readFileSync(file), '']
+ }
+ return [new Error(), '', '']
+ }
+ }
+
+ function allVsVersions (finder) {
+ finder.findVisualStudio2017OrNewerUsingSetupModule = async () => {
+ return null
+ }
+ finder.findVisualStudio2017 = async () => {
+ const data0 = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures',
+ 'VS_2017_Unusable.txt')))
+ const data1 = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures',
+ 'VS_2017_BuildTools_minimal.txt')))
+ const data2 = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures',
+ 'VS_2017_Community_workload.txt')))
+ const data3 = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures',
+ 'VS_2017_Express.txt')))
+ const data = JSON.stringify(data0.concat(data1, data2, data3))
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2017])
+ }
+ finder.findVisualStudio2019OrNewer = async () => {
+ const data0 = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures',
+ 'VS_2019_Preview.txt')))
+ const data1 = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures',
+ 'VS_2019_BuildTools_minimal.txt')))
+ const data2 = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures',
+ 'VS_2019_Community_workload.txt')))
+ const data3 = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures',
+ 'VS_2022_Community_workload.txt')))
+ const data = JSON.stringify(data0.concat(data1, data2, data3))
+ const parsedData = finder.parseData(null, data, '', { checkIsArray: true })
+ return finder.processData(parsedData, [2019, 2022, 2026])
+ }
+ finder.regSearchKeys = async (keys, value, addOpts) => {
+ for (let i = 0; i < keys.length; ++i) {
+ const fullName = `${keys[i]}\\${value}`
+ switch (fullName) {
+ case 'HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7\\14.0':
+ case 'HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7\\12.0':
+ continue
+ case 'HKLM\\Software\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VC7\\12.0':
+ return 'C:\\VS2013\\VC\\'
+ case 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions\\12.0\\MSBuildToolsPath':
+ return 'C:\\MSBuild12\\'
+ case 'HKLM\\Software\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VC7\\14.0':
+ return 'C:\\VS2015\\VC\\'
+ case 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions\\14.0\\MSBuildToolsPath':
+ return 'C:\\MSBuild14\\'
+ default:
+ assert.fail(`unexpected search for registry value ${fullName}`)
+ }
+ }
+ throw new Error()
+ }
+ }
+
+ it('fail when looking for invalid path', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, 'AABB')
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.ok(/find .* Visual Studio/i.test(err), 'expect error')
+ assert.ok(!info, 'no data')
+ })
+
+ it('look for VS2013 by version number', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, '2013')
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info.versionYear, 2013)
+ })
+
+ it('look for VS2013 by installation path', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2013')
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info.path, 'C:\\VS2013')
+ })
+
+ it('look for VS2015 by version number', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, '2015')
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info.versionYear, 2015)
+ })
+
+ it('look for VS2015 by installation path', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2015')
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info.path, 'C:\\VS2015')
+ })
+
+ it('look for VS2017 by version number', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, '2017')
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info.versionYear, 2017)
+ })
+
+ it('look for VS2017 by installation path', async function () {
+ const finder = new TestVisualStudioFinder(semverV1,
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community')
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info.path,
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community')
+ })
+
+ it('look for VS2019 by version number', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, '2019')
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info.versionYear, 2019)
+ })
+
+ it('look for VS2019 by installation path', async function () {
+ const finder = new TestVisualStudioFinder(semverV1,
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools')
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info.path,
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools')
+ })
+
+ it('look for VS2022 by version number', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, '2022')
+
+ finder.msBuildPathExists = (path) => {
+ return true
+ }
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info.versionYear, 2022)
+ })
+
+ it('msvs_version match should be case insensitive', async function () {
+ const finder = new TestVisualStudioFinder(semverV1,
+ 'c:\\program files (x86)\\microsoft visual studio\\2019\\BUILDTOOLS')
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info.path,
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools')
+ })
+
+ it('latest version should be found by default', async function () {
+ const finder = new TestVisualStudioFinder(semverV1, null)
+
+ finder.msBuildPathExists = (path) => {
+ return true
+ }
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info.versionYear, 2022)
+ })
+
+ it('run on a usable VS Command Prompt', async function () {
+ process.env.VCINSTALLDIR = 'C:\\VS2015\\VC'
+ // VSINSTALLDIR is not defined on Visual C++ Build Tools 2015
+ delete process.env.VSINSTALLDIR
+
+ const finder = new TestVisualStudioFinder(semverV1, null)
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info.path, 'C:\\VS2015')
+ })
+
+ it('VCINSTALLDIR match should be case insensitive', async function () {
+ process.env.VCINSTALLDIR =
+ 'c:\\program files (x86)\\microsoft visual studio\\2019\\BUILDTOOLS\\VC'
+
+ const finder = new TestVisualStudioFinder(semverV1, null)
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info.path,
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools')
+ })
+
+ it('run on a unusable VS Command Prompt', async function () {
+ process.env.VCINSTALLDIR =
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildToolsUnusable\\VC'
+
+ const finder = new TestVisualStudioFinder(semverV1, null)
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.ok(/find .* Visual Studio/i.test(err), 'expect error')
+ assert.ok(!info, 'no data')
+ })
+
+ it('run on a VS Command Prompt with matching msvs_version', async function () {
+ process.env.VCINSTALLDIR = 'C:\\VS2015\\VC'
+
+ const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2015')
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info.path, 'C:\\VS2015')
+ })
+
+ it('run on a VS Command Prompt with mismatched msvs_version', async function () {
+ process.env.VCINSTALLDIR =
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC'
+
+ const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2015')
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.ok(/find .* Visual Studio/i.test(err), 'expect error')
+ assert.ok(!info, 'no data')
+ })
+
+ it('run on a portable VS Command Prompt with sufficient environs', async function () {
+ process.env.VCINSTALLDIR = 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC'
+ process.env.VSCMD_VER = '16.0'
+ process.env.WindowsSDKVersion = '10.0.17763.0'
+
+ const finder = new TestVisualStudioFinder(semverV1, null)
+
+ allVsVersions(finder)
+ const { err, info } = await finder.findVisualStudio()
+ assert.strictEqual(err, null)
+ assert.deepStrictEqual(info, {
+ msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\' +
+ 'Community\\MSBuild\\Current\\Bin\\MSBuild.exe',
+ path:
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community',
+ sdk: '10.0.17763.0',
+ toolset: 'v142',
+ // Assume version in the environ is correct.
+ version: '16.0',
+ versionMajor: 16,
+ versionMinor: 0,
+ versionYear: 2019
+ })
+ })
+})
diff --git a/test/test-install.js b/test/test-install.js
index f647326a7f..8aecf78e11 100644
--- a/test/test-install.js
+++ b/test/test-install.js
@@ -1,37 +1,105 @@
'use strict'
-var test = require('tape')
-var install = require('../lib/install').test.install
-
-test('EACCES retry once', function (t) {
- t.plan(3)
-
- var fs = {}
- fs.stat = function (path, cb) {
- var err = new Error()
- err.code = 'EACCES'
- cb(err)
- t.ok(true);
- }
-
-
- var gyp = {}
- gyp.devDir = __dirname
- gyp.opts = {}
- gyp.opts.ensure = true
- gyp.commands = {}
- gyp.commands.install = function (argv, cb) {
- install(fs, gyp, argv, cb)
- }
- gyp.commands.remove = function (argv, cb) {
- cb()
- }
-
- gyp.commands.install([], function (err) {
- t.ok(true)
+const { describe, it, afterEach, beforeEach } = require('mocha')
+const { rm, mkdtemp } = require('fs/promises')
+const { createWriteStream } = require('fs')
+const assert = require('assert')
+const path = require('path')
+const os = require('os')
+const { pipeline: streamPipeline } = require('stream/promises')
+const requireInject = require('require-inject')
+const { FULL_TEST, platformTimeout } = require('./common')
+const gyp = require('../lib/node-gyp')
+const install = require('../lib/install')
+const { download } = require('../lib/download')
+
+describe('install', function () {
+ it('EACCES retry once', async () => {
+ let statCalled = 0
+ const mockInstall = requireInject('../lib/install', {
+ 'graceful-fs': {
+ promises: {
+ stat (_) {
+ const err = new Error()
+ err.code = 'EACCES'
+ statCalled++
+ throw err
+ }
+ }
+ }
+ })
+ const Gyp = {
+ devDir: __dirname,
+ opts: {
+ ensure: true
+ },
+ commands: {
+ install: (...args) => mockInstall(Gyp, ...args),
+ remove: async () => {}
+ }
+ }
+
+ let err
+ try {
+ await Gyp.commands.install([])
+ } catch (e) {
+ err = e
+ }
+
+ assert.ok(err)
+ assert.equal(statCalled, 2)
if (/"pre" versions of node cannot be installed/.test(err.message)) {
- t.ok(true)
- t.ok(true)
+ assert.ok(true)
}
})
+
+ describe('parallel', function () {
+ let prog
+
+ beforeEach(async () => {
+ prog = gyp()
+ prog.parseArgv([])
+ prog.devDir = await mkdtemp(path.join(os.tmpdir(), 'node-gyp-test-'))
+ })
+
+ afterEach(async () => {
+ await rm(prog.devDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 1000 })
+ prog = null
+ })
+
+ const runIt = (name, fn) => {
+ // only run these tests if we are running a version of Node with predictable version path behavior
+ if (!FULL_TEST) {
+ return it.skip('Skipping parallel installs test due to test environment configuration')
+ }
+
+ return it(name, async function () {
+ this.timeout(platformTimeout(4, { win32: 20 }))
+ await fn.call(this)
+ const expectedDir = path.join(prog.devDir, process.version.replace(/^v/, ''))
+ await rm(expectedDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 1000 })
+ await Promise.all(new Array(5).fill(0).map(async (_, i) => {
+ const title = `${' '.repeat(8)}${name} ${(i + 1).toString().padEnd(2, ' ')}`
+ console.log(`${title} : Start`)
+ console.time(title)
+ await install(prog, [])
+ console.timeEnd(title)
+ }))
+ })
+ }
+
+ runIt('ensure=true', async function () {
+ prog.opts.ensure = true
+ })
+
+ runIt('ensure=false', async function () {
+ prog.opts.ensure = false
+ })
+
+ runIt('tarball', async function () {
+ prog.opts.tarball = path.join(prog.devDir, 'node-headers.tar.gz')
+ const dl = await download(prog, `https://nodejs.org/dist/${process.version}/node-${process.version}.tar.gz`)
+ await streamPipeline(dl.body, createWriteStream(prog.opts.tarball))
+ })
+ })
})
diff --git a/test/test-options.js b/test/test-options.js
index d097f81be6..e14f827d56 100644
--- a/test/test-options.js
+++ b/test/test-options.js
@@ -1,25 +1,72 @@
-'use strict';
+'use strict'
-var test = require('tape')
-var gyp = require('../lib/node-gyp')
+const { describe, it } = require('mocha')
+const assert = require('assert')
+const gyp = require('../lib/node-gyp')
+const log = require('../lib/log')
-test('options in environment', function (t) {
- t.plan(1)
+describe('options', function () {
+ it('options in environment', () => {
+ // `npm test` dumps a ton of npm_config_* variables in the environment.
+ Object.keys(process.env)
+ .filter((key) => /^npm_config_/i.test(key) || /^npm_package_config_node_gyp_/i.test(key))
+ .forEach((key) => { delete process.env[key] })
- // `npm test` dumps a ton of npm_config_* variables in the environment.
- Object.keys(process.env)
- .filter(function(key) { return /^npm_config_/.test(key) })
- .forEach(function(key) { delete process.env[key] })
+ // in some platforms, certain keys are stubborn and cannot be removed
+ const keys = Object.keys(process.env)
+ .filter((key) => /^npm_config_/i.test(key) || /^npm_package_config_node_gyp_/i.test(key))
+ .map((key) => key.substring('npm_config_'.length))
- // Zero-length keys should get filtered out.
- process.env.npm_config_ = '42'
- // Other keys should get added.
- process.env.npm_config_x = '42'
- // Except loglevel.
- process.env.npm_config_loglevel = 'debug'
+ // Environment variables with the following prefixes should be added to opts.
+ // - `npm_config_` for npm versions before v11.
+ // - `npm_package_config_node_gyp_` for npm versions 11 and later.
- var g = gyp();
- g.parseArgv(['rebuild']) // Also sets opts.argv.
+ // Zero-length keys should get filtered out.
+ process.env.npm_config_ = '42'
+ process.env.npm_package_config_node_gyp_ = '42'
+ // Other keys should get added.
+ process.env.npm_package_config_node_gyp_foo = '42'
+ process.env.npm_config_x = '42'
+ process.env.npm_config_y = '41'
+ // Package config should take precedence over npm_config_ keys.
+ process.env.npm_package_config_node_gyp_y = '42'
+ // All configs should be case-insensitive.
+ process.env.NPM_PACKAGE_CONFIG_NODE_GYP_XX = 'value'
+ process.env.NPM_CONFIG_YY = 'value'
+ // loglevel does not get added to opts but will change the logger's level.
+ process.env.npm_config_loglevel = 'silly'
- t.deepEqual(Object.keys(g.opts).sort(), ['argv', 'x'])
+ const g = gyp()
+
+ assert.strictEqual(log.logger.level.id, 'info')
+
+ g.parseArgv(['rebuild']) // Also sets opts.argv.
+
+ assert.strictEqual(log.logger.level.id, 'silly')
+
+ assert.deepStrictEqual(Object.keys(g.opts).sort(), [...keys, 'argv', 'x', 'y', 'foo', 'xx', 'yy'].sort())
+ assert.strictEqual(g.opts['x'], '42')
+ assert.strictEqual(g.opts['y'], '42')
+ assert.strictEqual(g.opts['foo'], '42')
+ assert.strictEqual(g.opts['xx'], 'value')
+ assert.strictEqual(g.opts['yy'], 'value')
+ })
+
+ it('options with spaces in environment', () => {
+ process.env.npm_config_force_process_config = 'true'
+
+ const g = gyp()
+ g.parseArgv(['rebuild']) // Also sets opts.argv.
+
+ assert.strictEqual(g.opts['force-process-config'], 'true')
+ })
+
+ it('options with msvs_version', () => {
+ process.env.npm_config_msvs_version = '2017'
+
+ const g = gyp()
+ g.parseArgv(['rebuild']) // Also sets opts.argv.
+
+ assert.strictEqual(g.opts['msvs-version'], '2017')
+ })
})
diff --git a/test/test-process-release.js b/test/test-process-release.js
index 48411ae0a7..31a87be9e9 100644
--- a/test/test-process-release.js
+++ b/test/test-process-release.js
@@ -1,637 +1,401 @@
-var test = require('tape')
-var processRelease = require('../lib/process-release')
-
-test('test process release - process.version = 0.8.20', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: {} }, 'v0.8.20', null)
-
- t.equal(release.semver.version, '0.8.20')
- delete release.semver
-
- t.deepEqual(release, {
- version: '0.8.20',
- name: 'node',
- baseUrl: 'https://nodejs.org/dist/v0.8.20/',
- tarballUrl: 'https://nodejs.org/dist/v0.8.20/node-v0.8.20.tar.gz',
- shasumsUrl: 'https://nodejs.org/dist/v0.8.20/SHASUMS256.txt',
- versionDir: '0.8.20',
- libUrl32: 'https://nodejs.org/dist/v0.8.20/node.lib',
- libUrl64: 'https://nodejs.org/dist/v0.8.20/x64/node.lib',
- libPath32: 'node.lib',
- libPath64: 'x64/node.lib'
- })
-})
-
-test('test process release - process.version = 0.10.21', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: {} }, 'v0.10.21', null)
-
- t.equal(release.semver.version, '0.10.21')
- delete release.semver
-
- t.deepEqual(release, {
- version: '0.10.21',
- name: 'node',
- baseUrl: 'https://nodejs.org/dist/v0.10.21/',
- tarballUrl: 'https://nodejs.org/dist/v0.10.21/node-v0.10.21.tar.gz',
- shasumsUrl: 'https://nodejs.org/dist/v0.10.21/SHASUMS256.txt',
- versionDir: '0.10.21',
- libUrl32: 'https://nodejs.org/dist/v0.10.21/node.lib',
- libUrl64: 'https://nodejs.org/dist/v0.10.21/x64/node.lib',
- libPath32: 'node.lib',
- libPath64: 'x64/node.lib'
- })
-})
-
-// prior to -headers.tar.gz
-test('test process release - process.version = 0.12.9', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: {} }, 'v0.12.9', null)
-
- t.equal(release.semver.version, '0.12.9')
- delete release.semver
-
- t.deepEqual(release, {
- version: '0.12.9',
- name: 'node',
- baseUrl: 'https://nodejs.org/dist/v0.12.9/',
- tarballUrl: 'https://nodejs.org/dist/v0.12.9/node-v0.12.9.tar.gz',
- shasumsUrl: 'https://nodejs.org/dist/v0.12.9/SHASUMS256.txt',
- versionDir: '0.12.9',
- libUrl32: 'https://nodejs.org/dist/v0.12.9/node.lib',
- libUrl64: 'https://nodejs.org/dist/v0.12.9/x64/node.lib',
- libPath32: 'node.lib',
- libPath64: 'x64/node.lib'
- })
-})
-
-// prior to -headers.tar.gz
-test('test process release - process.version = 0.10.41', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: {} }, 'v0.10.41', null)
-
- t.equal(release.semver.version, '0.10.41')
- delete release.semver
-
- t.deepEqual(release, {
- version: '0.10.41',
- name: 'node',
- baseUrl: 'https://nodejs.org/dist/v0.10.41/',
- tarballUrl: 'https://nodejs.org/dist/v0.10.41/node-v0.10.41.tar.gz',
- shasumsUrl: 'https://nodejs.org/dist/v0.10.41/SHASUMS256.txt',
- versionDir: '0.10.41',
- libUrl32: 'https://nodejs.org/dist/v0.10.41/node.lib',
- libUrl64: 'https://nodejs.org/dist/v0.10.41/x64/node.lib',
- libPath32: 'node.lib',
- libPath64: 'x64/node.lib'
- })
-})
-
-// has -headers.tar.gz
-test('test process release - process.release ~ node@0.10.42', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: {} }, 'v0.10.42', null)
-
- t.equal(release.semver.version, '0.10.42')
- delete release.semver
-
- t.deepEqual(release, {
- version: '0.10.42',
- name: 'node',
- baseUrl: 'https://nodejs.org/dist/v0.10.42/',
- tarballUrl: 'https://nodejs.org/dist/v0.10.42/node-v0.10.42-headers.tar.gz',
- shasumsUrl: 'https://nodejs.org/dist/v0.10.42/SHASUMS256.txt',
- versionDir: '0.10.42',
- libUrl32: 'https://nodejs.org/dist/v0.10.42/node.lib',
- libUrl64: 'https://nodejs.org/dist/v0.10.42/x64/node.lib',
- libPath32: 'node.lib',
- libPath64: 'x64/node.lib'
- })
-})
-
-// has -headers.tar.gz
-test('test process release - process.release ~ node@0.12.10', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: {} }, 'v0.12.10', null)
-
- t.equal(release.semver.version, '0.12.10')
- delete release.semver
-
- t.deepEqual(release, {
- version: '0.12.10',
- name: 'node',
- baseUrl: 'https://nodejs.org/dist/v0.12.10/',
- tarballUrl: 'https://nodejs.org/dist/v0.12.10/node-v0.12.10-headers.tar.gz',
- shasumsUrl: 'https://nodejs.org/dist/v0.12.10/SHASUMS256.txt',
- versionDir: '0.12.10',
- libUrl32: 'https://nodejs.org/dist/v0.12.10/node.lib',
- libUrl64: 'https://nodejs.org/dist/v0.12.10/x64/node.lib',
- libPath32: 'node.lib',
- libPath64: 'x64/node.lib'
- })
-})
-
-test('test process release - process.release ~ node@4.1.23', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: {} }, 'v4.1.23', {
- name: 'node',
- headersUrl: 'https://nodejs.org/dist/v4.1.23/node-v4.1.23-headers.tar.gz'
- })
-
- t.equal(release.semver.version, '4.1.23')
- delete release.semver
-
- t.deepEqual(release, {
- version: '4.1.23',
- name: 'node',
- baseUrl: 'https://nodejs.org/dist/v4.1.23/',
- tarballUrl: 'https://nodejs.org/dist/v4.1.23/node-v4.1.23-headers.tar.gz',
- shasumsUrl: 'https://nodejs.org/dist/v4.1.23/SHASUMS256.txt',
- versionDir: '4.1.23',
- libUrl32: 'https://nodejs.org/dist/v4.1.23/win-x86/node.lib',
- libUrl64: 'https://nodejs.org/dist/v4.1.23/win-x64/node.lib',
- libPath32: 'win-x86/node.lib',
- libPath64: 'win-x64/node.lib'
- })
-})
-
-test('test process release - process.release ~ node@4.1.23 / corp build', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: {} }, 'v4.1.23', {
- name: 'node',
- headersUrl: 'https://some.custom.location/node-v4.1.23-headers.tar.gz'
- })
-
- t.equal(release.semver.version, '4.1.23')
- delete release.semver
-
- t.deepEqual(release, {
- version: '4.1.23',
- name: 'node',
- baseUrl: 'https://some.custom.location/',
- tarballUrl: 'https://some.custom.location/node-v4.1.23-headers.tar.gz',
- shasumsUrl: 'https://some.custom.location/SHASUMS256.txt',
- versionDir: '4.1.23',
- libUrl32: 'https://some.custom.location/win-x86/node.lib',
- libUrl64: 'https://some.custom.location/win-x64/node.lib',
- libPath32: 'win-x86/node.lib',
- libPath64: 'win-x64/node.lib'
- })
-})
-
-test('test process release - process.version = 1.8.4', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: {} }, 'v1.8.4', null)
-
- t.equal(release.semver.version, '1.8.4')
- delete release.semver
-
- t.deepEqual(release, {
- version: '1.8.4',
- name: 'iojs',
- baseUrl: 'https://iojs.org/download/release/v1.8.4/',
- tarballUrl: 'https://iojs.org/download/release/v1.8.4/iojs-v1.8.4.tar.gz',
- shasumsUrl: 'https://iojs.org/download/release/v1.8.4/SHASUMS256.txt',
- versionDir: 'iojs-1.8.4',
- libUrl32: 'https://iojs.org/download/release/v1.8.4/win-x86/iojs.lib',
- libUrl64: 'https://iojs.org/download/release/v1.8.4/win-x64/iojs.lib',
- libPath32: 'win-x86/iojs.lib',
- libPath64: 'win-x64/iojs.lib'
- })
-})
-
-test('test process release - process.release ~ iojs@3.2.24', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: {} }, 'v3.2.24', {
- name: 'io.js',
- headersUrl: 'https://iojs.org/download/release/v3.2.24/iojs-v3.2.24-headers.tar.gz'
- })
-
- t.equal(release.semver.version, '3.2.24')
- delete release.semver
-
- t.deepEqual(release, {
- version: '3.2.24',
- name: 'iojs',
- baseUrl: 'https://iojs.org/download/release/v3.2.24/',
- tarballUrl: 'https://iojs.org/download/release/v3.2.24/iojs-v3.2.24-headers.tar.gz',
- shasumsUrl: 'https://iojs.org/download/release/v3.2.24/SHASUMS256.txt',
- versionDir: 'iojs-3.2.24',
- libUrl32: 'https://iojs.org/download/release/v3.2.24/win-x86/iojs.lib',
- libUrl64: 'https://iojs.org/download/release/v3.2.24/win-x64/iojs.lib',
- libPath32: 'win-x86/iojs.lib',
- libPath64: 'win-x64/iojs.lib'
- })
-})
-
-test('test process release - process.release ~ iojs@3.2.11 +libUrl32', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: {} }, 'v3.2.11', {
- name: 'io.js',
- headersUrl: 'https://iojs.org/download/release/v3.2.11/iojs-v3.2.11-headers.tar.gz',
- libUrl: 'https://iojs.org/download/release/v3.2.11/win-x86/iojs.lib' // custom
- })
-
- t.equal(release.semver.version, '3.2.11')
- delete release.semver
-
- t.deepEqual(release, {
- version: '3.2.11',
- name: 'iojs',
- baseUrl: 'https://iojs.org/download/release/v3.2.11/',
- tarballUrl: 'https://iojs.org/download/release/v3.2.11/iojs-v3.2.11-headers.tar.gz',
- shasumsUrl: 'https://iojs.org/download/release/v3.2.11/SHASUMS256.txt',
- versionDir: 'iojs-3.2.11',
- libUrl32: 'https://iojs.org/download/release/v3.2.11/win-x86/iojs.lib',
- libUrl64: 'https://iojs.org/download/release/v3.2.11/win-x64/iojs.lib',
- libPath32: 'win-x86/iojs.lib',
- libPath64: 'win-x64/iojs.lib'
- })
-})
-
-test('test process release - process.release ~ iojs@3.2.101 +libUrl64', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: {} }, 'v3.2.101', {
- name: 'io.js',
- headersUrl: 'https://iojs.org/download/release/v3.2.101/iojs-v3.2.101-headers.tar.gz',
- libUrl: 'https://iojs.org/download/release/v3.2.101/win-x64/iojs.lib' // custom
- })
-
- t.equal(release.semver.version, '3.2.101')
- delete release.semver
-
- t.deepEqual(release, {
- version: '3.2.101',
- name: 'iojs',
- baseUrl: 'https://iojs.org/download/release/v3.2.101/',
- tarballUrl: 'https://iojs.org/download/release/v3.2.101/iojs-v3.2.101-headers.tar.gz',
- shasumsUrl: 'https://iojs.org/download/release/v3.2.101/SHASUMS256.txt',
- versionDir: 'iojs-3.2.101',
- libUrl32: 'https://iojs.org/download/release/v3.2.101/win-x86/iojs.lib',
- libUrl64: 'https://iojs.org/download/release/v3.2.101/win-x64/iojs.lib',
- libPath32: 'win-x86/iojs.lib',
- libPath64: 'win-x64/iojs.lib'
- })
-})
-
-test('test process release - process.release ~ iojs@3.3.0 - borked win-ia32', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: {} }, 'v3.2.101', {
- name: 'io.js',
- headersUrl: 'https://iojs.org/download/release/v3.2.101/iojs-v3.2.101-headers.tar.gz',
- libUrl: 'https://iojs.org/download/release/v3.2.101/win-ia32/iojs.lib' // custom
- })
-
- t.equal(release.semver.version, '3.2.101')
- delete release.semver
-
- t.deepEqual(release, {
- version: '3.2.101',
- name: 'iojs',
- baseUrl: 'https://iojs.org/download/release/v3.2.101/',
- tarballUrl: 'https://iojs.org/download/release/v3.2.101/iojs-v3.2.101-headers.tar.gz',
- shasumsUrl: 'https://iojs.org/download/release/v3.2.101/SHASUMS256.txt',
- versionDir: 'iojs-3.2.101',
- libUrl32: 'https://iojs.org/download/release/v3.2.101/win-x86/iojs.lib',
- libUrl64: 'https://iojs.org/download/release/v3.2.101/win-x64/iojs.lib',
- libPath32: 'win-x86/iojs.lib',
- libPath64: 'win-x64/iojs.lib'
- })
-})
-
-test('test process release - process.release ~ node@4.1.23 --target=0.10.40', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: { target: '0.10.40' } }, 'v4.1.23', {
- name: 'node',
- headersUrl: 'https://nodejs.org/dist/v4.1.23/node-v4.1.23-headers.tar.gz'
- })
-
- t.equal(release.semver.version, '0.10.40')
- delete release.semver
-
- t.deepEqual(release, {
- version: '0.10.40',
- name: 'node',
- baseUrl: 'https://nodejs.org/dist/v0.10.40/',
- tarballUrl: 'https://nodejs.org/dist/v0.10.40/node-v0.10.40.tar.gz',
- shasumsUrl: 'https://nodejs.org/dist/v0.10.40/SHASUMS256.txt',
- versionDir: '0.10.40',
- libUrl32: 'https://nodejs.org/dist/v0.10.40/node.lib',
- libUrl64: 'https://nodejs.org/dist/v0.10.40/x64/node.lib',
- libPath32: 'node.lib',
- libPath64: 'x64/node.lib'
- })
-})
-
-test('test process release - process.release ~ node@4.1.23 --target=1.8.4', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: { target: '1.8.4' } }, 'v4.1.23', {
- name: 'node',
- headersUrl: 'https://nodejs.org/dist/v4.1.23/node-v4.1.23-headers.tar.gz'
- })
-
- t.equal(release.semver.version, '1.8.4')
- delete release.semver
-
- t.deepEqual(release, {
- version: '1.8.4',
- name: 'iojs',
- baseUrl: 'https://iojs.org/download/release/v1.8.4/',
- tarballUrl: 'https://iojs.org/download/release/v1.8.4/iojs-v1.8.4.tar.gz',
- shasumsUrl: 'https://iojs.org/download/release/v1.8.4/SHASUMS256.txt',
- versionDir: 'iojs-1.8.4',
- libUrl32: 'https://iojs.org/download/release/v1.8.4/win-x86/iojs.lib',
- libUrl64: 'https://iojs.org/download/release/v1.8.4/win-x64/iojs.lib',
- libPath32: 'win-x86/iojs.lib',
- libPath64: 'win-x64/iojs.lib'
- })
-})
-
-test('test process release - process.release ~ node@4.1.23 --dist-url=https://foo.bar/baz', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: { 'dist-url': 'https://foo.bar/baz' } }, 'v4.1.23', {
- name: 'node',
- headersUrl: 'https://nodejs.org/dist/v4.1.23/node-v4.1.23-headers.tar.gz'
- })
-
- t.equal(release.semver.version, '4.1.23')
- delete release.semver
-
- t.deepEqual(release, {
- version: '4.1.23',
- name: 'node',
- baseUrl: 'https://foo.bar/baz/v4.1.23/',
- tarballUrl: 'https://foo.bar/baz/v4.1.23/node-v4.1.23-headers.tar.gz',
- shasumsUrl: 'https://foo.bar/baz/v4.1.23/SHASUMS256.txt',
- versionDir: '4.1.23',
- libUrl32: 'https://foo.bar/baz/v4.1.23/win-x86/node.lib',
- libUrl64: 'https://foo.bar/baz/v4.1.23/win-x64/node.lib',
- libPath32: 'win-x86/node.lib',
- libPath64: 'win-x64/node.lib'
- })
-})
-
-test('test process release - process.release ~ frankenstein@4.1.23', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: {} }, 'v4.1.23', {
- name: 'frankenstein',
- headersUrl: 'https://frankensteinjs.org/dist/v4.1.23/frankenstein-v4.1.23-headers.tar.gz'
- })
-
- t.equal(release.semver.version, '4.1.23')
- delete release.semver
-
- t.deepEqual(release, {
- version: '4.1.23',
- name: 'frankenstein',
- baseUrl: 'https://frankensteinjs.org/dist/v4.1.23/',
- tarballUrl: 'https://frankensteinjs.org/dist/v4.1.23/frankenstein-v4.1.23-headers.tar.gz',
- shasumsUrl: 'https://frankensteinjs.org/dist/v4.1.23/SHASUMS256.txt',
- versionDir: 'frankenstein-4.1.23',
- libUrl32: 'https://frankensteinjs.org/dist/v4.1.23/win-x86/frankenstein.lib',
- libUrl64: 'https://frankensteinjs.org/dist/v4.1.23/win-x64/frankenstein.lib',
- libPath32: 'win-x86/frankenstein.lib',
- libPath64: 'win-x64/frankenstein.lib'
- })
-})
-
-
-test('test process release - process.release ~ frankenstein@4.1.23 --dist-url=http://foo.bar/baz/', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: { 'dist-url': 'http://foo.bar/baz/' } }, 'v4.1.23', {
- name: 'frankenstein',
- headersUrl: 'https://frankensteinjs.org/dist/v4.1.23/frankenstein-v4.1.23.tar.gz'
- })
-
- t.equal(release.semver.version, '4.1.23')
- delete release.semver
-
- t.deepEqual(release, {
- version: '4.1.23',
- name: 'frankenstein',
- baseUrl: 'http://foo.bar/baz/v4.1.23/',
- tarballUrl: 'http://foo.bar/baz/v4.1.23/frankenstein-v4.1.23-headers.tar.gz',
- shasumsUrl: 'http://foo.bar/baz/v4.1.23/SHASUMS256.txt',
- versionDir: 'frankenstein-4.1.23',
- libUrl32: 'http://foo.bar/baz/v4.1.23/win-x86/frankenstein.lib',
- libUrl64: 'http://foo.bar/baz/v4.1.23/win-x64/frankenstein.lib',
- libPath32: 'win-x86/frankenstein.lib',
- libPath64: 'win-x64/frankenstein.lib'
- })
-})
-
-test('test process release - process.release ~ node@4.0.0-rc.4', function (t) {
- t.plan(2)
-
- var release = processRelease([], { opts: {} }, 'v4.0.0-rc.4', {
- name: 'node',
- headersUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/node-v4.0.0-rc.4-headers.tar.gz'
- })
-
- t.equal(release.semver.version, '4.0.0-rc.4')
- delete release.semver
-
- t.deepEqual(release, {
- version: '4.0.0-rc.4',
- name: 'node',
- baseUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/',
- tarballUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/node-v4.0.0-rc.4-headers.tar.gz',
- shasumsUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/SHASUMS256.txt',
- versionDir: '4.0.0-rc.4',
- libUrl32: 'https://nodejs.org/download/rc/v4.0.0-rc.4/win-x86/node.lib',
- libUrl64: 'https://nodejs.org/download/rc/v4.0.0-rc.4/win-x64/node.lib',
- libPath32: 'win-x86/node.lib',
- libPath64: 'win-x64/node.lib'
- })
-})
-
-
-test('test process release - process.release ~ node@4.0.0-rc.4 passed as argv[0]', function (t) {
- t.plan(2)
-
+'use strict'
+
+const { describe, it } = require('mocha')
+const assert = require('assert')
+const processRelease = require('../lib/process-release')
+
+describe('process-release', function () {
+ it('test process release - process.version = 0.8.20', function () {
+ const release = processRelease([], { opts: {} }, 'v0.8.20', null)
+
+ assert.strictEqual(release.semver.version, '0.8.20')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '0.8.20',
+ name: 'node',
+ baseUrl: 'https://nodejs.org/dist/v0.8.20/',
+ tarballUrl: 'https://nodejs.org/dist/v0.8.20/node-v0.8.20.tar.gz',
+ shasumsUrl: 'https://nodejs.org/dist/v0.8.20/SHASUMS256.txt',
+ versionDir: '0.8.20',
+ ia32: { libUrl: 'https://nodejs.org/dist/v0.8.20/node.lib', libPath: 'node.lib' },
+ x64: { libUrl: 'https://nodejs.org/dist/v0.8.20/x64/node.lib', libPath: 'x64/node.lib' },
+ arm64: { libUrl: 'https://nodejs.org/dist/v0.8.20/arm64/node.lib', libPath: 'arm64/node.lib' }
+ })
+ })
+
+ it('test process release - process.version = 0.10.21', function () {
+ const release = processRelease([], { opts: {} }, 'v0.10.21', null)
+
+ assert.strictEqual(release.semver.version, '0.10.21')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '0.10.21',
+ name: 'node',
+ baseUrl: 'https://nodejs.org/dist/v0.10.21/',
+ tarballUrl: 'https://nodejs.org/dist/v0.10.21/node-v0.10.21.tar.gz',
+ shasumsUrl: 'https://nodejs.org/dist/v0.10.21/SHASUMS256.txt',
+ versionDir: '0.10.21',
+ ia32: { libUrl: 'https://nodejs.org/dist/v0.10.21/node.lib', libPath: 'node.lib' },
+ x64: { libUrl: 'https://nodejs.org/dist/v0.10.21/x64/node.lib', libPath: 'x64/node.lib' },
+ arm64: { libUrl: 'https://nodejs.org/dist/v0.10.21/arm64/node.lib', libPath: 'arm64/node.lib' }
+ })
+ })
+
+ // prior to -headers.tar.gz
+ it('test process release - process.version = 0.12.9', function () {
+ const release = processRelease([], { opts: {} }, 'v0.12.9', null)
+
+ assert.strictEqual(release.semver.version, '0.12.9')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '0.12.9',
+ name: 'node',
+ baseUrl: 'https://nodejs.org/dist/v0.12.9/',
+ tarballUrl: 'https://nodejs.org/dist/v0.12.9/node-v0.12.9.tar.gz',
+ shasumsUrl: 'https://nodejs.org/dist/v0.12.9/SHASUMS256.txt',
+ versionDir: '0.12.9',
+ ia32: { libUrl: 'https://nodejs.org/dist/v0.12.9/node.lib', libPath: 'node.lib' },
+ x64: { libUrl: 'https://nodejs.org/dist/v0.12.9/x64/node.lib', libPath: 'x64/node.lib' },
+ arm64: { libUrl: 'https://nodejs.org/dist/v0.12.9/arm64/node.lib', libPath: 'arm64/node.lib' }
+ })
+ })
+
+ // prior to -headers.tar.gz
+ it('test process release - process.version = 0.10.41', function () {
+ const release = processRelease([], { opts: {} }, 'v0.10.41', null)
+
+ assert.strictEqual(release.semver.version, '0.10.41')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '0.10.41',
+ name: 'node',
+ baseUrl: 'https://nodejs.org/dist/v0.10.41/',
+ tarballUrl: 'https://nodejs.org/dist/v0.10.41/node-v0.10.41.tar.gz',
+ shasumsUrl: 'https://nodejs.org/dist/v0.10.41/SHASUMS256.txt',
+ versionDir: '0.10.41',
+ ia32: { libUrl: 'https://nodejs.org/dist/v0.10.41/node.lib', libPath: 'node.lib' },
+ x64: { libUrl: 'https://nodejs.org/dist/v0.10.41/x64/node.lib', libPath: 'x64/node.lib' },
+ arm64: { libUrl: 'https://nodejs.org/dist/v0.10.41/arm64/node.lib', libPath: 'arm64/node.lib' }
+ })
+ })
+
+ // has -headers.tar.gz
+ it('test process release - process.release ~ node@0.10.42', function () {
+ const release = processRelease([], { opts: {} }, 'v0.10.42', null)
+
+ assert.strictEqual(release.semver.version, '0.10.42')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '0.10.42',
+ name: 'node',
+ baseUrl: 'https://nodejs.org/dist/v0.10.42/',
+ tarballUrl: 'https://nodejs.org/dist/v0.10.42/node-v0.10.42-headers.tar.gz',
+ shasumsUrl: 'https://nodejs.org/dist/v0.10.42/SHASUMS256.txt',
+ versionDir: '0.10.42',
+ ia32: { libUrl: 'https://nodejs.org/dist/v0.10.42/node.lib', libPath: 'node.lib' },
+ x64: { libUrl: 'https://nodejs.org/dist/v0.10.42/x64/node.lib', libPath: 'x64/node.lib' },
+ arm64: { libUrl: 'https://nodejs.org/dist/v0.10.42/arm64/node.lib', libPath: 'arm64/node.lib' }
+ })
+ })
+
+ // has -headers.tar.gz
+ it('test process release - process.release ~ node@0.12.10', function () {
+ const release = processRelease([], { opts: {} }, 'v0.12.10', null)
+
+ assert.strictEqual(release.semver.version, '0.12.10')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '0.12.10',
+ name: 'node',
+ baseUrl: 'https://nodejs.org/dist/v0.12.10/',
+ tarballUrl: 'https://nodejs.org/dist/v0.12.10/node-v0.12.10-headers.tar.gz',
+ shasumsUrl: 'https://nodejs.org/dist/v0.12.10/SHASUMS256.txt',
+ versionDir: '0.12.10',
+ ia32: { libUrl: 'https://nodejs.org/dist/v0.12.10/node.lib', libPath: 'node.lib' },
+ x64: { libUrl: 'https://nodejs.org/dist/v0.12.10/x64/node.lib', libPath: 'x64/node.lib' },
+ arm64: { libUrl: 'https://nodejs.org/dist/v0.12.10/arm64/node.lib', libPath: 'arm64/node.lib' }
+ })
+ })
+
+ it('test process release - process.release ~ node@4.1.23', function () {
+ const release = processRelease([], { opts: {} }, 'v4.1.23', {
+ name: 'node',
+ headersUrl: 'https://nodejs.org/dist/v4.1.23/node-v4.1.23-headers.tar.gz'
+ })
+
+ assert.strictEqual(release.semver.version, '4.1.23')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '4.1.23',
+ name: 'node',
+ baseUrl: 'https://nodejs.org/dist/v4.1.23/',
+ tarballUrl: 'https://nodejs.org/dist/v4.1.23/node-v4.1.23-headers.tar.gz',
+ shasumsUrl: 'https://nodejs.org/dist/v4.1.23/SHASUMS256.txt',
+ versionDir: '4.1.23',
+ ia32: { libUrl: 'https://nodejs.org/dist/v4.1.23/win-x86/node.lib', libPath: 'win-x86/node.lib' },
+ x64: { libUrl: 'https://nodejs.org/dist/v4.1.23/win-x64/node.lib', libPath: 'win-x64/node.lib' },
+ arm64: { libUrl: 'https://nodejs.org/dist/v4.1.23/win-arm64/node.lib', libPath: 'win-arm64/node.lib' }
+ })
+ })
+
+ it('test process release - process.release ~ node@4.1.23 / corp build', function () {
+ const release = processRelease([], { opts: {} }, 'v4.1.23', {
+ name: 'node',
+ headersUrl: 'https://some.custom.location/node-v4.1.23-headers.tar.gz'
+ })
+
+ assert.strictEqual(release.semver.version, '4.1.23')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '4.1.23',
+ name: 'node',
+ baseUrl: 'https://some.custom.location/',
+ tarballUrl: 'https://some.custom.location/node-v4.1.23-headers.tar.gz',
+ shasumsUrl: 'https://some.custom.location/SHASUMS256.txt',
+ versionDir: '4.1.23',
+ ia32: { libUrl: 'https://some.custom.location/win-x86/node.lib', libPath: 'win-x86/node.lib' },
+ x64: { libUrl: 'https://some.custom.location/win-x64/node.lib', libPath: 'win-x64/node.lib' },
+ arm64: { libUrl: 'https://some.custom.location/win-arm64/node.lib', libPath: 'win-arm64/node.lib' }
+ })
+ })
+
+ it('test process release - process.release ~ node@12.8.0 Windows', function () {
+ const release = processRelease([], { opts: {} }, 'v12.8.0', {
+ name: 'node',
+ sourceUrl: 'https://nodejs.org/download/release/v12.8.0/node-v12.8.0.tar.gz',
+ headersUrl: 'https://nodejs.org/download/release/v12.8.0/node-v12.8.0-headers.tar.gz',
+ libUrl: 'https://nodejs.org/download/release/v12.8.0/win-x64/node.lib'
+ })
+
+ assert.strictEqual(release.semver.version, '12.8.0')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '12.8.0',
+ name: 'node',
+ baseUrl: 'https://nodejs.org/download/release/v12.8.0/',
+ tarballUrl: 'https://nodejs.org/download/release/v12.8.0/node-v12.8.0-headers.tar.gz',
+ shasumsUrl: 'https://nodejs.org/download/release/v12.8.0/SHASUMS256.txt',
+ versionDir: '12.8.0',
+ ia32: { libUrl: 'https://nodejs.org/download/release/v12.8.0/win-x86/node.lib', libPath: 'win-x86/node.lib' },
+ x64: { libUrl: 'https://nodejs.org/download/release/v12.8.0/win-x64/node.lib', libPath: 'win-x64/node.lib' },
+ arm64: { libUrl: 'https://nodejs.org/download/release/v12.8.0/win-arm64/node.lib', libPath: 'win-arm64/node.lib' }
+ })
+ })
+
+ it('test process release - process.release ~ node@12.8.0 Windows ARM64', function () {
+ const release = processRelease([], { opts: {} }, 'v12.8.0', {
+ name: 'node',
+ sourceUrl: 'https://unofficial-builds.nodejs.org/download/release/v12.8.0/node-v12.8.0.tar.gz',
+ headersUrl: 'https://unofficial-builds.nodejs.org/download/release/v12.8.0/node-v12.8.0-headers.tar.gz',
+ libUrl: 'https://unofficial-builds.nodejs.org/download/release/v12.8.0/win-arm64/node.lib'
+ })
+
+ assert.strictEqual(release.semver.version, '12.8.0')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '12.8.0',
+ name: 'node',
+ baseUrl: 'https://unofficial-builds.nodejs.org/download/release/v12.8.0/',
+ tarballUrl: 'https://unofficial-builds.nodejs.org/download/release/v12.8.0/node-v12.8.0-headers.tar.gz',
+ shasumsUrl: 'https://unofficial-builds.nodejs.org/download/release/v12.8.0/SHASUMS256.txt',
+ versionDir: '12.8.0',
+ ia32: { libUrl: 'https://unofficial-builds.nodejs.org/download/release/v12.8.0/win-x86/node.lib', libPath: 'win-x86/node.lib' },
+ x64: { libUrl: 'https://unofficial-builds.nodejs.org/download/release/v12.8.0/win-x64/node.lib', libPath: 'win-x64/node.lib' },
+ arm64: { libUrl: 'https://unofficial-builds.nodejs.org/download/release/v12.8.0/win-arm64/node.lib', libPath: 'win-arm64/node.lib' }
+ })
+ })
+
+ it('test process release - process.release ~ node@4.1.23 --target=0.10.40', function () {
+ const release = processRelease([], { opts: { target: '0.10.40' } }, 'v4.1.23', {
+ name: 'node',
+ headersUrl: 'https://nodejs.org/dist/v4.1.23/node-v4.1.23-headers.tar.gz'
+ })
+
+ assert.strictEqual(release.semver.version, '0.10.40')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '0.10.40',
+ name: 'node',
+ baseUrl: 'https://nodejs.org/dist/v0.10.40/',
+ tarballUrl: 'https://nodejs.org/dist/v0.10.40/node-v0.10.40.tar.gz',
+ shasumsUrl: 'https://nodejs.org/dist/v0.10.40/SHASUMS256.txt',
+ versionDir: '0.10.40',
+ ia32: { libUrl: 'https://nodejs.org/dist/v0.10.40/node.lib', libPath: 'node.lib' },
+ x64: { libUrl: 'https://nodejs.org/dist/v0.10.40/x64/node.lib', libPath: 'x64/node.lib' },
+ arm64: { libUrl: 'https://nodejs.org/dist/v0.10.40/arm64/node.lib', libPath: 'arm64/node.lib' }
+ })
+ })
+
+ it('test process release - process.release ~ node@4.1.23 --dist-url=https://foo.bar/baz', function () {
+ const release = processRelease([], { opts: { 'dist-url': 'https://foo.bar/baz' } }, 'v4.1.23', {
+ name: 'node',
+ headersUrl: 'https://nodejs.org/dist/v4.1.23/node-v4.1.23-headers.tar.gz'
+ })
+
+ assert.strictEqual(release.semver.version, '4.1.23')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '4.1.23',
+ name: 'node',
+ baseUrl: 'https://foo.bar/baz/v4.1.23/',
+ tarballUrl: 'https://foo.bar/baz/v4.1.23/node-v4.1.23-headers.tar.gz',
+ shasumsUrl: 'https://foo.bar/baz/v4.1.23/SHASUMS256.txt',
+ versionDir: '4.1.23',
+ ia32: { libUrl: 'https://foo.bar/baz/v4.1.23/win-x86/node.lib', libPath: 'win-x86/node.lib' },
+ x64: { libUrl: 'https://foo.bar/baz/v4.1.23/win-x64/node.lib', libPath: 'win-x64/node.lib' },
+ arm64: { libUrl: 'https://foo.bar/baz/v4.1.23/win-arm64/node.lib', libPath: 'win-arm64/node.lib' }
+ })
+ })
+
+ it('test process release - process.release ~ frankenstein@4.1.23', function () {
+ const release = processRelease([], { opts: {} }, 'v4.1.23', {
+ name: 'frankenstein',
+ headersUrl: 'https://frankensteinjs.org/dist/v4.1.23/frankenstein-v4.1.23-headers.tar.gz'
+ })
+
+ assert.strictEqual(release.semver.version, '4.1.23')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '4.1.23',
+ name: 'frankenstein',
+ baseUrl: 'https://frankensteinjs.org/dist/v4.1.23/',
+ tarballUrl: 'https://frankensteinjs.org/dist/v4.1.23/frankenstein-v4.1.23-headers.tar.gz',
+ shasumsUrl: 'https://frankensteinjs.org/dist/v4.1.23/SHASUMS256.txt',
+ versionDir: 'frankenstein-4.1.23',
+ ia32: { libUrl: 'https://frankensteinjs.org/dist/v4.1.23/win-x86/frankenstein.lib', libPath: 'win-x86/frankenstein.lib' },
+ x64: { libUrl: 'https://frankensteinjs.org/dist/v4.1.23/win-x64/frankenstein.lib', libPath: 'win-x64/frankenstein.lib' },
+ arm64: { libUrl: 'https://frankensteinjs.org/dist/v4.1.23/win-arm64/frankenstein.lib', libPath: 'win-arm64/frankenstein.lib' }
+ })
+ })
+
+ it('test process release - process.release ~ frankenstein@4.1.23 --dist-url=http://foo.bar/baz/', function () {
+ const release = processRelease([], { opts: { 'dist-url': 'http://foo.bar/baz/' } }, 'v4.1.23', {
+ name: 'frankenstein',
+ headersUrl: 'https://frankensteinjs.org/dist/v4.1.23/frankenstein-v4.1.23.tar.gz'
+ })
+
+ assert.strictEqual(release.semver.version, '4.1.23')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '4.1.23',
+ name: 'frankenstein',
+ baseUrl: 'http://foo.bar/baz/v4.1.23/',
+ tarballUrl: 'http://foo.bar/baz/v4.1.23/frankenstein-v4.1.23-headers.tar.gz',
+ shasumsUrl: 'http://foo.bar/baz/v4.1.23/SHASUMS256.txt',
+ versionDir: 'frankenstein-4.1.23',
+ ia32: { libUrl: 'http://foo.bar/baz/v4.1.23/win-x86/frankenstein.lib', libPath: 'win-x86/frankenstein.lib' },
+ x64: { libUrl: 'http://foo.bar/baz/v4.1.23/win-x64/frankenstein.lib', libPath: 'win-x64/frankenstein.lib' },
+ arm64: { libUrl: 'http://foo.bar/baz/v4.1.23/win-arm64/frankenstein.lib', libPath: 'win-arm64/frankenstein.lib' }
+ })
+ })
+
+ it('test process release - process.release ~ node@4.0.0-rc.4', function () {
+ const release = processRelease([], { opts: {} }, 'v4.0.0-rc.4', {
+ name: 'node',
+ headersUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/node-v4.0.0-rc.4-headers.tar.gz'
+ })
+
+ assert.strictEqual(release.semver.version, '4.0.0-rc.4')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '4.0.0-rc.4',
+ name: 'node',
+ baseUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/',
+ tarballUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/node-v4.0.0-rc.4-headers.tar.gz',
+ shasumsUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/SHASUMS256.txt',
+ versionDir: '4.0.0-rc.4',
+ ia32: { libUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/win-x86/node.lib', libPath: 'win-x86/node.lib' },
+ x64: { libUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/win-x64/node.lib', libPath: 'win-x64/node.lib' },
+ arm64: { libUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/win-arm64/node.lib', libPath: 'win-arm64/node.lib' }
+ })
+ })
+
+ it('test process release - process.release ~ node@4.0.0-rc.4 passed as argv[0]', function () {
// note the missing 'v' on the arg, it should normalise when checking
- // whether we're on the default or not
- var release = processRelease([ '4.0.0-rc.4' ], { opts: {} }, 'v4.0.0-rc.4', {
- name: 'node',
- headersUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/node-v4.0.0-rc.4-headers.tar.gz'
- })
-
- t.equal(release.semver.version, '4.0.0-rc.4')
- delete release.semver
-
- t.deepEqual(release, {
- version: '4.0.0-rc.4',
- name: 'node',
- baseUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/',
- tarballUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/node-v4.0.0-rc.4-headers.tar.gz',
- shasumsUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/SHASUMS256.txt',
- versionDir: '4.0.0-rc.4',
- libUrl32: 'https://nodejs.org/download/rc/v4.0.0-rc.4/win-x86/node.lib',
- libUrl64: 'https://nodejs.org/download/rc/v4.0.0-rc.4/win-x64/node.lib',
- libPath32: 'win-x86/node.lib',
- libPath64: 'win-x64/node.lib'
- })
-})
-
-
-test('test process release - process.release ~ node@4.0.0-rc.4 - bogus string passed as argv[0]', function (t) {
- t.plan(2)
-
+ // whether we're on the default or not
+ const release = processRelease(['4.0.0-rc.4'], { opts: {} }, 'v4.0.0-rc.4', {
+ name: 'node',
+ headersUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/node-v4.0.0-rc.4-headers.tar.gz'
+ })
+
+ assert.strictEqual(release.semver.version, '4.0.0-rc.4')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '4.0.0-rc.4',
+ name: 'node',
+ baseUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/',
+ tarballUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/node-v4.0.0-rc.4-headers.tar.gz',
+ shasumsUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/SHASUMS256.txt',
+ versionDir: '4.0.0-rc.4',
+ ia32: { libUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/win-x86/node.lib', libPath: 'win-x86/node.lib' },
+ x64: { libUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/win-x64/node.lib', libPath: 'win-x64/node.lib' },
+ arm64: { libUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/win-arm64/node.lib', libPath: 'win-arm64/node.lib' }
+ })
+ })
+
+ it('test process release - process.release ~ node@4.0.0-rc.4 - bogus string passed as argv[0]', function () {
// additional arguments can be passed in on the commandline that should be ignored if they
- // are not specifying a valid version @ position 0
- var release = processRelease([ 'this is no version!' ], { opts: {} }, 'v4.0.0-rc.4', {
- name: 'node',
- headersUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/node-v4.0.0-rc.4-headers.tar.gz'
+ // are not specifying a valid version @ position 0
+ const release = processRelease(['this is no version!'], { opts: {} }, 'v4.0.0-rc.4', {
+ name: 'node',
+ headersUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/node-v4.0.0-rc.4-headers.tar.gz'
+ })
+
+ assert.strictEqual(release.semver.version, '4.0.0-rc.4')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '4.0.0-rc.4',
+ name: 'node',
+ baseUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/',
+ tarballUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/node-v4.0.0-rc.4-headers.tar.gz',
+ shasumsUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/SHASUMS256.txt',
+ versionDir: '4.0.0-rc.4',
+ ia32: { libUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/win-x86/node.lib', libPath: 'win-x86/node.lib' },
+ x64: { libUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/win-x64/node.lib', libPath: 'win-x64/node.lib' },
+ arm64: { libUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/win-arm64/node.lib', libPath: 'win-arm64/node.lib' }
+ })
+ })
+
+ it('test process release - NODEJS_ORG_MIRROR', function () {
+ process.env.NODEJS_ORG_MIRROR = 'http://foo.bar'
+
+ const release = processRelease([], { opts: {} }, 'v4.1.23', {
+ name: 'node',
+ headersUrl: 'https://nodejs.org/dist/v4.1.23/node-v4.1.23-headers.tar.gz'
+ })
+
+ assert.strictEqual(release.semver.version, '4.1.23')
+ delete release.semver
+
+ assert.deepStrictEqual(release, {
+ version: '4.1.23',
+ name: 'node',
+ baseUrl: 'http://foo.bar/v4.1.23/',
+ tarballUrl: 'http://foo.bar/v4.1.23/node-v4.1.23-headers.tar.gz',
+ shasumsUrl: 'http://foo.bar/v4.1.23/SHASUMS256.txt',
+ versionDir: '4.1.23',
+ ia32: { libUrl: 'http://foo.bar/v4.1.23/win-x86/node.lib', libPath: 'win-x86/node.lib' },
+ x64: { libUrl: 'http://foo.bar/v4.1.23/win-x64/node.lib', libPath: 'win-x64/node.lib' },
+ arm64: { libUrl: 'http://foo.bar/v4.1.23/win-arm64/node.lib', libPath: 'win-arm64/node.lib' }
+ })
+
+ delete process.env.NODEJS_ORG_MIRROR
})
-
- t.equal(release.semver.version, '4.0.0-rc.4')
- delete release.semver
-
- t.deepEqual(release, {
- version: '4.0.0-rc.4',
- name: 'node',
- baseUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/',
- tarballUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/node-v4.0.0-rc.4-headers.tar.gz',
- shasumsUrl: 'https://nodejs.org/download/rc/v4.0.0-rc.4/SHASUMS256.txt',
- versionDir: '4.0.0-rc.4',
- libUrl32: 'https://nodejs.org/download/rc/v4.0.0-rc.4/win-x86/node.lib',
- libUrl64: 'https://nodejs.org/download/rc/v4.0.0-rc.4/win-x64/node.lib',
- libPath32: 'win-x86/node.lib',
- libPath64: 'win-x64/node.lib'
- })
-})
-
-test('test process release - NODEJS_ORG_MIRROR', function (t) {
- t.plan(2)
-
- process.env.NODEJS_ORG_MIRROR = 'http://foo.bar'
-
- var release = processRelease([], { opts: {} }, 'v4.1.23', {
- name: 'node',
- headersUrl: 'https://nodejs.org/dist/v4.1.23/node-v4.1.23-headers.tar.gz'
- })
-
- t.equal(release.semver.version, '4.1.23')
- delete release.semver
-
- t.deepEqual(release, {
- version: '4.1.23',
- name: 'node',
- baseUrl: 'http://foo.bar/v4.1.23/',
- tarballUrl: 'http://foo.bar/v4.1.23/node-v4.1.23-headers.tar.gz',
- shasumsUrl: 'http://foo.bar/v4.1.23/SHASUMS256.txt',
- versionDir: '4.1.23',
- libUrl32: 'http://foo.bar/v4.1.23/win-x86/node.lib',
- libUrl64: 'http://foo.bar/v4.1.23/win-x64/node.lib',
- libPath32: 'win-x86/node.lib',
- libPath64: 'win-x64/node.lib'
- })
-
- delete process.env.NODEJS_ORG_MIRROR
-})
-
-test('test process release - NVM_NODEJS_ORG_MIRROR', function (t) {
- t.plan(2)
-
- process.env.NVM_NODEJS_ORG_MIRROR = 'http://foo.bar'
-
- var release = processRelease([], { opts: {} }, 'v4.1.23', {
- name: 'node',
- headersUrl: 'https://nodejs.org/dist/v4.1.23/node-v4.1.23-headers.tar.gz'
- })
-
- t.equal(release.semver.version, '4.1.23')
- delete release.semver
-
- t.deepEqual(release, {
- version: '4.1.23',
- name: 'node',
- baseUrl: 'http://foo.bar/v4.1.23/',
- tarballUrl: 'http://foo.bar/v4.1.23/node-v4.1.23-headers.tar.gz',
- shasumsUrl: 'http://foo.bar/v4.1.23/SHASUMS256.txt',
- versionDir: '4.1.23',
- libUrl32: 'http://foo.bar/v4.1.23/win-x86/node.lib',
- libUrl64: 'http://foo.bar/v4.1.23/win-x64/node.lib',
- libPath32: 'win-x86/node.lib',
- libPath64: 'win-x64/node.lib'
- })
-
- delete process.env.NVM_NODEJS_ORG_MIRROR
-})
-
-test('test process release - IOJS_ORG_MIRROR', function (t) {
- t.plan(2)
-
- process.env.IOJS_ORG_MIRROR = 'http://foo.bar'
-
- var release = processRelease([], { opts: {} }, 'v3.2.24', {
- name: 'io.js',
- headersUrl: 'https://iojs.org/download/release/v3.2.24/iojs-v3.2.24-headers.tar.gz'
- })
-
- t.equal(release.semver.version, '3.2.24')
- delete release.semver
-
- t.deepEqual(release, {
- version: '3.2.24',
- name: 'iojs',
- baseUrl: 'http://foo.bar/v3.2.24/',
- tarballUrl: 'http://foo.bar/v3.2.24/iojs-v3.2.24-headers.tar.gz',
- shasumsUrl: 'http://foo.bar/v3.2.24/SHASUMS256.txt',
- versionDir: 'iojs-3.2.24',
- libUrl32: 'http://foo.bar/v3.2.24/win-x86/iojs.lib',
- libUrl64: 'http://foo.bar/v3.2.24/win-x64/iojs.lib',
- libPath32: 'win-x86/iojs.lib',
- libPath64: 'win-x64/iojs.lib'
- })
-
- delete process.env.IOJS_ORG_MIRROR
-})
-
-
-test('test process release - NVM_IOJS_ORG_MIRROR', function (t) {
- t.plan(2)
-
- process.env.NVM_IOJS_ORG_MIRROR = 'http://foo.bar'
-
- var release = processRelease([], { opts: {} }, 'v3.2.24', {
- name: 'io.js',
- headersUrl: 'https://iojs.org/download/release/v3.2.24/iojs-v3.2.24-headers.tar.gz'
- })
-
- t.equal(release.semver.version, '3.2.24')
- delete release.semver
-
- t.deepEqual(release, {
- version: '3.2.24',
- name: 'iojs',
- baseUrl: 'http://foo.bar/v3.2.24/',
- tarballUrl: 'http://foo.bar/v3.2.24/iojs-v3.2.24-headers.tar.gz',
- shasumsUrl: 'http://foo.bar/v3.2.24/SHASUMS256.txt',
- versionDir: 'iojs-3.2.24',
- libUrl32: 'http://foo.bar/v3.2.24/win-x86/iojs.lib',
- libUrl64: 'http://foo.bar/v3.2.24/win-x64/iojs.lib',
- libPath32: 'win-x86/iojs.lib',
- libPath64: 'win-x64/iojs.lib'
- })
-
- delete process.env.NVM_IOJS_ORG_MIRROR
})
diff --git a/test/test-windows-make.js b/test/test-windows-make.js
new file mode 100644
index 0000000000..edd9d36412
--- /dev/null
+++ b/test/test-windows-make.js
@@ -0,0 +1,111 @@
+'use strict'
+
+const { describe, it } = require('mocha')
+const assert = require('assert')
+const path = require('path')
+const gracefulFs = require('graceful-fs')
+const cp = require('child_process')
+const util = require('../lib/util')
+const { platformTimeout } = require('./common')
+
+const addonPath = path.resolve(__dirname, 'node_modules', 'hello_napi')
+const nodeGyp = path.resolve(__dirname, '..', 'bin', 'node-gyp.js')
+
+const execFileSync = (...args) => cp.execFileSync(...args).toString().trim()
+
+const execFile = async (cmd, env) => {
+ const [err,, stderr] = await util.execFile(process.execPath, cmd, {
+ env: {
+ ...process.env,
+ NODE_GYP_NULL_LOGGER: undefined,
+ ...env
+ },
+ encoding: 'utf-8'
+ })
+ return [err, stderr.toString().trim().split(/\r?\n/)]
+}
+
+function runHello (hostProcess = process.execPath) {
+ const testCode = "console.log(require('hello_napi').hello())"
+ return execFileSync(hostProcess, ['--experimental-wasi-unstable-preview1', '-e', testCode], { cwd: __dirname })
+}
+
+function executable (name) {
+ return name + (process.platform === 'win32' ? '.exe' : '')
+}
+
+function getEnv (target) {
+ const env = {
+ GYP_CROSSCOMPILE: '1',
+ AR_host: 'ar',
+ CC_host: 'clang',
+ CXX_host: 'clang++'
+ }
+ if (target === 'emscripten') {
+ env.AR_target = 'emar'
+ env.CC_target = 'emcc'
+ env.CXX_target = 'em++'
+ } else if (target === 'wasi') {
+ if (!process.env.WASI_SDK_PATH) return env
+ env.AR_target = path.resolve(__dirname, '..', process.env.WASI_SDK_PATH, 'bin', executable('ar'))
+ env.CC_target = path.resolve(__dirname, '..', process.env.WASI_SDK_PATH, 'bin', executable('clang'))
+ env.CXX_target = path.resolve(__dirname, '..', process.env.WASI_SDK_PATH, 'bin', executable('clang++'))
+ } else if (target === 'wasm') {
+ if (!process.env.WASI_SDK_PATH) return env
+ env.AR_target = path.resolve(__dirname, '..', process.env.WASI_SDK_PATH, 'bin', executable('ar'))
+ env.CC_target = path.resolve(__dirname, '..', process.env.WASI_SDK_PATH, 'bin', executable('clang'))
+ env.CXX_target = path.resolve(__dirname, '..', process.env.WASI_SDK_PATH, 'bin', executable('clang++'))
+ env.CFLAGS = '--target=wasm32'
+ } else if (target === 'win-clang') {
+ let vsdir = 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise'
+ if (!gracefulFs.existsSync(vsdir)) {
+ vsdir = 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Community'
+ }
+ const llvmBin = 'VC\\Tools\\Llvm\\x64\\bin'
+ env.AR_target = path.join(vsdir, llvmBin, 'llvm-ar.exe')
+ env.CC_target = path.join(vsdir, llvmBin, 'clang.exe')
+ env.CXX_target = path.join(vsdir, llvmBin, 'clang++.exe')
+ env.CFLAGS = '--target=wasm32'
+ }
+ return env
+}
+
+function quote (path) {
+ if (path.includes(' ')) {
+ return `"${path}"`
+ }
+ return path
+}
+
+describe('windows-cross-compile', function () {
+ it('build simple node-api addon', async function () {
+ if (process.platform !== 'win32') {
+ return this.skip('This test is only for Windows')
+ }
+ const env = getEnv('wasm')
+ if (!gracefulFs.existsSync(env.CC_target)) {
+ return this.skip('CC_target does not exist')
+ }
+
+ // handle bash whitespace
+ env.AR_target = quote(env.AR_target)
+ env.CC_target = quote(env.CC_target)
+ env.CXX_target = quote(env.CXX_target)
+ this.timeout(platformTimeout(1, { win32: 5 }))
+
+ const cmd = [
+ nodeGyp,
+ 'rebuild',
+ '-C', addonPath,
+ '--loglevel=verbose',
+ `--nodedir=${addonPath}`,
+ '--arch=wasm32',
+ '--', '-f', 'make'
+ ]
+ const [err, logLines] = await execFile(cmd, env)
+ const lastLine = logLines[logLines.length - 1]
+ assert.strictEqual(err, null)
+ assert.strictEqual(lastLine, 'gyp info ok', 'should end in ok')
+ assert.strictEqual(runHello(), 'world')
+ })
+})
diff --git a/tools/gyp/pylib/gyp/generator/compile_commands_json.py b/tools/gyp/pylib/gyp/generator/compile_commands_json.py
deleted file mode 100644
index 575db63c4e..0000000000
--- a/tools/gyp/pylib/gyp/generator/compile_commands_json.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# Copyright (c) 2016 Ben Noordhuis . All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import gyp.common
-import gyp.xcode_emulation
-import json
-import os
-
-generator_additional_non_configuration_keys = []
-generator_additional_path_sections = []
-generator_extra_sources_for_rules = []
-generator_filelist_paths = None
-generator_supports_multiple_toolsets = True
-generator_wants_sorted_dependencies = False
-
-# Lifted from make.py. The actual values don't matter much.
-generator_default_variables = {
- 'CONFIGURATION_NAME': '$(BUILDTYPE)',
- 'EXECUTABLE_PREFIX': '',
- 'EXECUTABLE_SUFFIX': '',
- 'INTERMEDIATE_DIR': '$(obj).$(TOOLSET)/$(TARGET)/geni',
- 'PRODUCT_DIR': '$(builddir)',
- 'RULE_INPUT_DIRNAME': '%(INPUT_DIRNAME)s',
- 'RULE_INPUT_EXT': '$(suffix $<)',
- 'RULE_INPUT_NAME': '$(notdir $<)',
- 'RULE_INPUT_PATH': '$(abspath $<)',
- 'RULE_INPUT_ROOT': '%(INPUT_ROOT)s',
- 'SHARED_INTERMEDIATE_DIR': '$(obj)/gen',
- 'SHARED_LIB_PREFIX': 'lib',
- 'STATIC_LIB_PREFIX': 'lib',
- 'STATIC_LIB_SUFFIX': '.a',
-}
-
-
-def IsMac(params):
- return 'mac' == gyp.common.GetFlavor(params)
-
-
-def CalculateVariables(default_variables, params):
- default_variables.setdefault('OS', gyp.common.GetFlavor(params))
-
-
-def AddCommandsForTarget(cwd, target, params, per_config_commands):
- output_dir = params['generator_flags']['output_dir']
- for configuration_name, configuration in target['configurations'].iteritems():
- builddir_name = os.path.join(output_dir, configuration_name)
-
- if IsMac(params):
- xcode_settings = gyp.xcode_emulation.XcodeSettings(target)
- cflags = xcode_settings.GetCflags(configuration_name)
- cflags_c = xcode_settings.GetCflagsC(configuration_name)
- cflags_cc = xcode_settings.GetCflagsCC(configuration_name)
- else:
- cflags = configuration.get('cflags', [])
- cflags_c = configuration.get('cflags_c', [])
- cflags_cc = configuration.get('cflags_cc', [])
-
- cflags_c = cflags + cflags_c
- cflags_cc = cflags + cflags_cc
-
- defines = configuration.get('defines', [])
- defines = ['-D' + s for s in defines]
-
- # TODO(bnoordhuis) Handle generated source files.
- sources = target.get('sources', [])
- sources = [s for s in sources if s.endswith('.c') or s.endswith('.cc')]
-
- def resolve(filename):
- return os.path.abspath(os.path.join(cwd, filename))
-
- # TODO(bnoordhuis) Handle generated header files.
- include_dirs = configuration.get('include_dirs', [])
- include_dirs = [s for s in include_dirs if not s.startswith('$(obj)')]
- includes = ['-I' + resolve(s) for s in include_dirs]
-
- defines = gyp.common.EncodePOSIXShellList(defines)
- includes = gyp.common.EncodePOSIXShellList(includes)
- cflags_c = gyp.common.EncodePOSIXShellList(cflags_c)
- cflags_cc = gyp.common.EncodePOSIXShellList(cflags_cc)
-
- commands = per_config_commands.setdefault(configuration_name, [])
- for source in sources:
- file = resolve(source)
- isc = source.endswith('.c')
- cc = 'cc' if isc else 'c++'
- cflags = cflags_c if isc else cflags_cc
- command = ' '.join((cc, defines, includes, cflags,
- '-c', gyp.common.EncodePOSIXShellArgument(file)))
- commands.append(dict(command=command, directory=output_dir, file=file))
-
-
-def GenerateOutput(target_list, target_dicts, data, params):
- per_config_commands = {}
- for qualified_target, target in target_dicts.iteritems():
- build_file, target_name, toolset = (
- gyp.common.ParseQualifiedTarget(qualified_target))
- if IsMac(params):
- settings = data[build_file]
- gyp.xcode_emulation.MergeGlobalXcodeSettingsToSpec(settings, target)
- cwd = os.path.dirname(build_file)
- AddCommandsForTarget(cwd, target, params, per_config_commands)
-
- output_dir = params['generator_flags']['output_dir']
- for configuration_name, commands in per_config_commands.iteritems():
- filename = os.path.join(output_dir,
- configuration_name,
- 'compile_commands.json')
- gyp.common.EnsureDirExists(filename)
- fp = open(filename, 'w')
- json.dump(commands, fp=fp, indent=0, check_circular=False)
-
-
-def PerformBuild(data, configurations, params):
- pass
diff --git a/update-gyp.py b/update-gyp.py
new file mode 100755
index 0000000000..a822c0485f
--- /dev/null
+++ b/update-gyp.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import shutil
+import subprocess
+import tarfile
+import tempfile
+import urllib.request
+
+BASE_URL = "https://github.com/nodejs/gyp-next/archive/"
+CHECKOUT_PATH = os.path.dirname(os.path.realpath(__file__))
+CHECKOUT_GYP_PATH = os.path.join(CHECKOUT_PATH, "gyp")
+
+parser = argparse.ArgumentParser()
+parser.add_argument(
+ "--no-commit", action="store_true", dest="no_commit", help="do not run git-commit"
+)
+parser.add_argument("tag", help="gyp tag to update to")
+args = parser.parse_args()
+
+tar_url = BASE_URL + args.tag + ".tar.gz"
+
+changed_files = subprocess.check_output(["git", "diff", "--name-only"]).strip()
+if changed_files:
+ raise Exception("Can't update gyp while you have uncommitted changes in node-gyp")
+
+with tempfile.TemporaryDirectory() as tmp_dir:
+ tar_file = os.path.join(tmp_dir, "gyp.tar.gz")
+ unzip_target = os.path.join(tmp_dir, "gyp")
+ with open(tar_file, "wb") as f:
+ print("Downloading gyp-next@" + args.tag + " into temporary directory...")
+ print("From: " + tar_url)
+ with urllib.request.urlopen(tar_url) as in_file:
+ f.write(in_file.read())
+
+ print("Unzipping...")
+ with tarfile.open(tar_file, "r:gz") as tar_ref:
+
+ def is_within_directory(directory, target):
+ abs_directory = os.path.abspath(directory)
+ abs_target = os.path.abspath(target)
+
+ prefix = os.path.commonprefix([abs_directory, abs_target])
+
+ return prefix == abs_directory
+
+ def safe_extract(tar, path=".", members=None, *, numeric_owner=False):
+ for member in tar.getmembers():
+ member_path = os.path.join(path, member.name)
+ if not is_within_directory(path, member_path):
+ raise Exception("Attempted Path Traversal in Tar File")
+
+ tar.extractall(path, members, numeric_owner=numeric_owner)
+
+ safe_extract(tar_ref, unzip_target)
+
+ print("Moving to current checkout (" + CHECKOUT_PATH + ")...")
+ if os.path.exists(CHECKOUT_GYP_PATH):
+ shutil.rmtree(CHECKOUT_GYP_PATH)
+ shutil.move(
+ os.path.join(unzip_target, os.listdir(unzip_target)[0]), CHECKOUT_GYP_PATH
+ )
+
+if not args.no_commit:
+ subprocess.check_output(["git", "add", "gyp"], cwd=CHECKOUT_PATH)
+ subprocess.check_output(
+ ["git", "commit", "-m", f"feat(gyp): update gyp to {args.tag}"]
+ )