#!/usr/bin/python3 # # Copyright (c) 2019 Martin Storsjo # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
def getArgsParser():
parser = argparse.ArgumentParser(description = "Download and install Visual Studio")
parser.add_argument("--manifest", metavar="manifest", help="A predownloaded manifest file")
parser.add_argument("--save-manifest", const=True, action="store_const", help="Store the downloaded manifest to a file")
parser.add_argument("--major", default=17, metavar="version", help="The major version to download (defaults to 17)")
parser.add_argument("--preview", dest="type", default="release", const="pre", action="store_const", help="Download the preview version instead of the release version")
parser.add_argument("--cache", metavar="dir", help="Directory to use as a persistent cache for downloaded files")
parser.add_argument("--dest", metavar="dir", help="Directory to install into")
parser.add_argument("package", metavar="package", help="Package to install. If omitted, installs the default command line tools.", nargs="*")
parser.add_argument("--ignore", metavar="component", help="Package to skip", action="append")
parser.add_argument("--accept-license", const=True, action="store_const", help="Don't prompt for accepting the license")
parser.add_argument("--print-version", const=True, action="store_const", help="Stop after fetching the manifest")
parser.add_argument("--list-workloads", const=True, action="store_const", help="List high level workloads")
parser.add_argument("--list-components", const=True, action="store_const", help="List available components")
parser.add_argument("--list-packages", const=True, action="store_const", help="List all individual packages, regardless of type")
parser.add_argument("--include-optional", const=True, action="store_const", help="Include all optional dependencies")
parser.add_argument("--skip-recommended", const=True, action="store_const", help="Don't include recommended dependencies")
parser.add_argument("--print-deps-tree", const=True, action="store_const", help="Print a tree of resolved dependencies for the given selection")
parser.add_argument("--print-reverse-deps", const=True, action="store_const", help="Print a tree of packages that depend on the given selection")
parser.add_argument("--print-selection", const=True, action="store_const", help="Print a list of the individual packages that are selected to be installed")
parser.add_argument("--only-download", const=True, action="store_const", help="Stop after downloading package files")
parser.add_argument("--only-unpack", const=True, action="store_const", help="Unpack the selected packages and keep all files, in the layout they are unpacked, don't restructure and prune files other than what's needed for MSVC CLI tools")
parser.add_argument("--keep-unpack", const=True, action="store_const", help="Keep the unpacked files that aren't otherwise selected as needed output")
parser.add_argument("--msvc-version", metavar="version", help="Install a specific MSVC toolchain version")
parser.add_argument("--sdk-version", metavar="version", help="Install a specific Windows SDK version") return parser
def setPackageSelectionMSVC16(args, packages, userversion, sdk, toolversion, defaultPackages): if findPackage(packages, "Microsoft.VisualStudio.Component.VC." + toolversion + ".x86.x64", None, warn=False):
args.package.extend(["Win10SDK_" + sdk, "Microsoft.VisualStudio.Component.VC." + toolversion + ".x86.x64", "Microsoft.VisualStudio.Component.VC." + toolversion + ".ARM", "Microsoft.VisualStudio.Component.VC." + toolversion + ".ARM64"]) else: # Options for toolchains for specific versions. The latest version in # each manifest isn't available as a pinned version though, so if that # version is requested, try the default version.
print("Didn't find exact version packages for " + userversion + ", assuming this is provided by the default/latest version")
args.package.extend(defaultPackages)
def setPackageSelectionMSVC15(args, packages, userversion, sdk, toolversion, defaultPackages): if findPackage(packages, "Microsoft.VisualStudio.Component.VC.Tools." + toolversion, None, warn=False):
args.package.extend(["Win10SDK_" + sdk, "Microsoft.VisualStudio.Component.VC.Tools." + toolversion]) else: # Options for toolchains for specific versions. The latest version in # each manifest isn't available as a pinned version though, so if that # version is requested, try the default version.
print("Didn't find exact version packages for " + userversion + ", assuming this is provided by the default/latest version")
args.package.extend(defaultPackages)
def setPackageSelection(args, packages): # If no packages are selected, install these versionless packages, which # gives the latest/recommended version for the current manifest.
defaultPackages = ["Microsoft.VisualStudio.Workload.VCTools", "Microsoft.VisualStudio.Component.VC.Tools.ARM", "Microsoft.VisualStudio.Component.VC.Tools.ARM64"]
if len(args.package) == 0:
args.package = defaultPackages
if args.sdk_version != None: for key in packages: if key.startswith("win10sdk") or key.startswith("win11sdk"):
base = key[0:8]
sdkname = base + "_" + args.sdk_version if key == sdkname:
args.package.append(key) else:
args.ignore.append(key)
p = packages[key][0]
def lowercaseIgnores(args):
ignore = [] if args.ignore != None: for i in args.ignore:
ignore.append(i.lower())
args.ignore = ignore
def getManifest(args): if args.manifest == None:
url = "https://aka.ms/vs/%s/%s/channel" % (args.major, args.type)
print("Fetching %s" % (url))
manifest = simplejson.loads(six.moves.urllib.request.urlopen(url).read())
print("Got toplevel manifest for %s" % (manifest["info"]["productDisplayVersion"])) for item in manifest["channelItems"]: if"type"in item and item["type"] == "Manifest":
args.manifest = item["payloads"][0]["url"] if args.manifest == None:
print("Unable to find an intaller manifest!")
sys.exit(1)
def getPackages(manifest):
packages = {} for p in manifest["packages"]:
id = p["id"].lower() ifnot id in packages:
packages[id] = []
packages[id].append(p) for key in packages:
packages[key] = sorted(packages[key], key=functools.cmp_to_key(prioritizePackage)) return packages
def listPackageType(packages, type): if type != None:
type = type.lower()
ids = [] for key in packages:
p = packages[key][0] if type == None:
ids.append(p["id"]) elif"type"in p and p["type"].lower() == type:
ids.append(p["id"]) for id in sorted(ids):
print(id)
def findPackage(packages, id, chip, warn=True):
origid = id
id = id.lower()
candidates = None ifnot id in packages: if warn:
print("WARNING: %s not found" % (origid)) returnNone
candidates = packages[id] if chip != None:
chip = chip.lower() for a in candidates: if"chip"in a and a["chip"].lower() == chip: return a return candidates[0]
def printDepends(packages, target, deptype, chip, indent, args):
chipstr = "" if chip != None:
chipstr = " (" + chip + ")"
deptypestr = "" if deptype != "":
deptypestr = " (" + deptype + ")"
ignorestr = ""
ignore = False if target.lower() in args.ignore:
ignorestr = " (Ignored)"
ignore = True
print(indent + target + chipstr + deptypestr + ignorestr) if deptype == "Optional"andnot args.include_optional: return if deptype == "Recommended"and args.skip_recommended: return if ignore: return
p = findPackage(packages, target, chip) if p == None: return if"dependencies"in p:
deps = p["dependencies"] for key in deps:
dep = deps[key]
type = "" if"type"in dep:
type = dep["type"]
chip = None if"chip"in dep:
chip = dep["chip"]
printDepends(packages, key, type, chip, indent + " ", args)
def printReverseDepends(packages, target, deptype, indent, args):
deptypestr = "" if deptype != "":
deptypestr = " (" + deptype + ")"
print(indent + target + deptypestr) if deptype == "Optional"andnot args.include_optional: return if deptype == "Recommended"and args.skip_recommended: return
target = target.lower() for key in packages:
p = packages[key][0] if"dependencies"in p:
deps = p["dependencies"] for k in deps: if k.lower() != target: continue
dep = deps[k]
type = "" if"type"in dep:
type = dep["type"]
printReverseDepends(packages, p["id"], type, indent + " ", args)
def aggregateDepends(packages, included, target, chip, args): if target.lower() in args.ignore: return []
p = findPackage(packages, target, chip) if p == None: return []
packagekey = getPackageKey(p) if packagekey in included: return []
ret = [p]
included[packagekey] = True if"dependencies"in p:
deps = p["dependencies"] for key in deps:
dep = deps[key] if"type"in dep:
deptype = dep["type"] if deptype == "Optional"andnot args.include_optional: continue if deptype == "Recommended"and args.skip_recommended: continue
chip = None if"chip"in dep:
chip = dep["chip"]
ret.extend(aggregateDepends(packages, included, key, chip, args)) return ret
def getSelectedPackages(packages, args):
ret = []
included = {} for i in args.package:
ret.extend(aggregateDepends(packages, included, i, None, args)) return ret
def sumInstalledSize(l):
sum = 0 for p in l: if"installSizes"in p:
sizes = p["installSizes"] for location in sizes:
sum = sum + sizes[location] return sum
def sumDownloadSize(l):
sum = 0 for p in l: if"payloads"in p: for payload in p["payloads"]: if"size"in payload:
sum = sum + payload["size"] return sum
def formatSize(s): if s > 900*1024*1024: return"%.1f GB" % (s/(1024*1024*1024)) if s > 900*1024: return"%.1f MB" % (s/(1024*1024)) if s > 1024: return"%.1f KB" % (s/1024) return"%d bytes" % (s)
def printPackageList(l): for p in sorted(l, key=lambda p: p["id"]):
s = p["id"] if"type"in p:
s = s + " (" + p["type"] + ")" if"chip"in p:
s = s + " (" + p["chip"] + ")" if"language"in p:
s = s + " (" + p["language"] + ")"
s = s + " " + formatSize(sumInstalledSize([p]))
print(s)
def sha256File(file):
sha256Hash = hashlib.sha256() with open(file, "rb") as f: for byteBlock in iter(lambda: f.read(4096), b""):
sha256Hash.update(byteBlock) return sha256Hash.hexdigest()
def getPayloadName(payload):
name = payload["fileName"] if"\\"in name:
name = name.split("\\")[-1] if"/"in name:
name = name.split("/")[-1] return name
def downloadPackages(selected, cache, allowHashMismatch = False):
pool = multiprocessing.Pool(5)
tasks = []
makedirs(cache) for p in selected: ifnot"payloads"in p: continue
dir = os.path.join(cache, getPackageKey(p))
makedirs(dir) for payload in p["payloads"]:
name = getPayloadName(payload)
destname = os.path.join(dir, name)
fileid = os.path.join(getPackageKey(p), name)
args = (payload, destname, fileid, allowHashMismatch)
tasks.append(pool.apply_async(_downloadPayload, args))
downloaded = sum(task.get() for task in tasks)
pool.close()
print("Downloaded %s in total" % (formatSize(downloaded)))
def mergeTrees(src, dest): ifnot os.path.isdir(src): return ifnot os.path.isdir(dest):
shutil.move(src, dest) return
names = os.listdir(src)
destnames = {} for n in os.listdir(dest):
destnames[n.lower()] = n for n in names:
srcname = os.path.join(src, n)
destname = os.path.join(dest, n) if os.path.isdir(srcname): if os.path.isdir(destname):
mergeTrees(srcname, destname) elif n.lower() in destnames:
mergeTrees(srcname, os.path.join(dest, destnames[n.lower()])) else:
shutil.move(srcname, destname) else:
shutil.move(srcname, destname)
def unzipFiltered(zip, dest):
tmp = os.path.join(dest, "extract") for f in zip.infolist():
name = six.moves.urllib.parse.unquote(f.filename) if"/"in name:
sep = name.rfind("/")
dir = os.path.join(dest, name[0:sep])
makedirs(dir)
extracted = zip.extract(f, tmp)
shutil.move(extracted, os.path.join(dest, name))
shutil.rmtree(tmp)
def unpackVsix(file, dest, listing):
temp = os.path.join(dest, "vsix")
makedirs(temp) with zipfile.ZipFile(file, 'r') as zip:
unzipFiltered(zip, temp) with open(listing, "w") as f: for n in zip.namelist():
f.write(n + "\n")
contents = os.path.join(temp, "Contents") if os.access(contents, os.F_OK):
mergeTrees(contents, dest)
shutil.rmtree(temp)
def unpackWin10SDK(src, payloads, dest): # We could try to unpack only the MSIs we need here. # Note, this extracts some files into Program Files/..., and some # files directly in the root unpack directory. The files we need # are under Program Files/... though. for payload in payloads:
name = getPayloadName(payload) if name.endswith(".msi"):
print("Extracting " + name)
srcfile = os.path.join(src, name) if sys.platform == "win32":
cmd = ["msiexec", "/a", srcfile, "/qn", "TARGETDIR=" + os.path.abspath(dest)] else:
cmd = ["msiextract", "-C", dest, srcfile] with open(os.path.join(dest, "WinSDK-" + getPayloadName(payload) + "-listing.txt"), "w") as log:
subprocess.check_call(cmd, stdout=log)
def extractPackages(selected, cache, dest):
makedirs(dest) for p in selected:
type = p["type"]
dir = os.path.join(cache, getPackageKey(p)) if type == "Component"or type == "Workload"or type == "Group": continue if type == "Vsix":
print("Unpacking " + p["id"]) for payload in p["payloads"]:
unpackVsix(os.path.join(dir, getPayloadName(payload)), dest, os.path.join(dest, getPackageKey(p) + "-listing.txt")) elif p["id"].startswith("Win10SDK") or p["id"].startswith("Win11SDK"):
print("Unpacking " + p["id"])
unpackWin10SDK(dir, p["payloads"], dest) else:
print("Skipping unpacking of " + p["id"] + " of type " + type)
def moveVCSDK(unpack, dest): # Move the VC and Program Files\Windows Kits\10 directories # out from the unpack directory, allowing the rest of unpacked # files to be removed.
makedirs(os.path.join(dest, "kits"))
mergeTrees(os.path.join(unpack, "VC"), os.path.join(dest, "VC"))
kitsPath = unpack # msiexec extracts to Windows Kits rather than Program Files\Windows Kits if sys.platform != "win32":
kitsPath = os.path.join(kitsPath, "Program Files")
kitsPath = os.path.join(kitsPath, "Windows Kits", "10")
mergeTrees(kitsPath, os.path.join(dest, "kits", "10")) # The DIA SDK isn't necessary for normal use, but can be used when e.g. # compiling LLVM.
mergeTrees(os.path.join(unpack, "DIA SDK"), os.path.join(dest, "DIA SDK"))
ifnot args.accept_license:
response = six.moves.input("Do you accept the license at " + findPackage(packages, "Microsoft.VisualStudio.Product.BuildTools", None)["localizedResources"][0]["license"] + " (yes/no)? ") while response != "yes"and response != "no":
response = six.moves.input("Do you accept the license? Answer \"yes\" or \"no\": ") if response == "no":
sys.exit(0)
setPackageSelection(args, packages)
if args.list_components or args.list_workloads or args.list_packages: if args.list_components:
listPackageType(packages, "Component") if args.list_workloads:
listPackageType(packages, "Workload") if args.list_packages:
listPackageType(packages, None)
sys.exit(0)
if args.print_deps_tree: for i in args.package:
printDepends(packages, i, "", None, "", args)
sys.exit(0)
if args.print_reverse_deps: for i in args.package:
printReverseDepends(packages, i, "", "", args)
sys.exit(0)
selected = getSelectedPackages(packages, args)
if args.print_selection:
printPackageList(selected)
print("Selected %d packages, for a total download size of %s, install size of %s" % (len(selected), formatSize(sumDownloadSize(selected)), formatSize(sumInstalledSize(selected))))
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.