servo: Merge #12916 - Create `mach bootstrap` based on Mozilla's mozboot bootstrapper (from UK992:msvc-dependencies); r=larsbergstrom,wafflespeanut
authorUK992 <urbankrajnc92@gmail.com>
Thu, 08 Sep 2016 13:57:23 -0500
changeset 339660 f5c0c57f92e6cde5f477d649bf2408b394c90d74
parent 339659 6f5a1fc4f7a3125bcdd5aacb852b642d8c8c3eba
child 339661 a7001af11ce2892ac6d5ff2eaa2e89891efdffff
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslarsbergstrom, wafflespeanut
servo: Merge #12916 - Create `mach bootstrap` based on Mozilla's mozboot bootstrapper (from UK992:msvc-dependencies); r=larsbergstrom,wafflespeanut Fixes https://github.com/servo/servo/issues/12914 I've made this few weeks ago, its an example how could everything looks like. It downloads and setup all needed dependencies for MSVC. It's has version in case if some dependencies need to be updated. Zip files and folder in zip need to be named ``<dep>-<version>``. Also if cmake already exist in PATH, it won't download it again. I want opinion on that, if this is right approaches and how to improve it. cc @vvuk Source-Repo: https://github.com/servo/servo Source-Revision: daa30e60f1a7af87ee88aae4fd6e47e210ee9a76
servo/appveyor.yml
servo/python/servo/bootstrap_commands.py
servo/python/servo/bootstrapper/__init__.py
servo/python/servo/bootstrapper/base.py
servo/python/servo/bootstrapper/bootstrap.py
servo/python/servo/bootstrapper/packages.py
servo/python/servo/bootstrapper/windows_gnu.py
servo/python/servo/bootstrapper/windows_msvc.py
servo/python/servo/build_commands.py
servo/python/servo/command_base.py
--- a/servo/appveyor.yml
+++ b/servo/appveyor.yml
@@ -1,12 +1,13 @@
 version: 1.0.{build}
 
 environment:
   RUST_BACKTRACE: 1
+  HOME: '%APPVEYOR_BUILD_FOLDER%'
   # The appveyor image we use has a pretty huge set of things installed... we make the
   # initial PATH something sane so we know what to expect
   PATH: "C:\\windows\\system32;\
     C:\\windows;\
     C:\\windows\\System32\\Wbem;\
     C:\\windows\\System32\\WindowsPowerShell\\v1.0;\
     C:\\ProgramData\\chocolatey\\bin;\
     C:\\Python27;\
@@ -40,36 +41,16 @@ platform:
 
 cache:
   - .servo -> rust-nightly-date, cargo-nightly-build
   - .cargo -> rust-nightly-date, cargo-nightly-build
 
 install:
   - if %TARGET:*-msvc=msvc%==msvc set BUILD_ENV=msvc
   - if %TARGET:*-gnu=gnu%==gnu set BUILD_ENV=gnu
-  - ps: 'if ($env:BUILD_ENV -eq "msvc") {
-           Start-FileDownload "http://servo-rust.s3.amazonaws.com/build/openssl-and-ffmpeg.zip" -ErrorAction Stop ;
-           Start-FileDownload "http://servo-rust.s3.amazonaws.com/build/ninja.zip" -ErrorAction Stop ;
-           Start-FileDownload "http://servo-rust.s3.amazonaws.com/build/MozillaBuildSetup-2.2.0.exe" -ErrorAction Stop ;
-           .\MozillaBuildSetup-2.2.0.exe /S | Out-Null ;
-           7z x openssl-and-ffmpeg.zip | Out-Null ;
-           7z x ninja.zip | Out-Null ;
-           Copy-Item C:\mozilla-build\yasm\yasm.exe C:\mozilla-build\msys\bin ;
-           Copy-Item C:\mozilla-build\mozmake\mozmake.exe C:\mozilla-build\msys\bin ;
-           $env:MOZTOOLS_PATH="C:\mozilla-build\msys\bin" ;
-           $env:NATIVE_WIN32_PYTHON="C:/Python27/python.exe" ;
-           $env:PATH="$pwd\ninja;$env:PATH" ;
-           $env:OPENSSL_INCLUDE_DIR="$pwd\openssl-bin\openssl-1.0.1t-vs2015\include" ;
-           $env:OPENSSL_LIB_DIR="$pwd\openssl-bin\openssl-1.0.1t-vs2015\lib64" ;
-           $env:OPENSSL_LIBS="ssleay32MD:libeay32MD" ;
-           $env:FFMPEG_INCLUDE_DIR="$pwd\ffmpeg-bin\include" ;
-           $env:FFMPEG_LIB_DIR="$pwd\ffmpeg-bin\lib" ;
-           $env:FFMPEG_LIBS="avformat:avcodec:avutil" ;
-        }'
-  - if %BUILD_ENV%==msvc call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\vcvars64.bat"
   - if %BUILD_ENV%==gnu  set PATH=C:\msys64\mingw64\bin;C:\msys64\usr\bin\;%PATH%
   - if %BUILD_ENV%==gnu  set MSYSTEM=MINGW64
   - if %BUILD_ENV%==gnu  set MSYS=winsymlinks=lnk
   - if %BUILD_ENV%==gnu  bash -lc "echo $MSYSTEM; pacman --needed --noconfirm -Sy pacman-mirrors"
   - if %BUILD_ENV%==gnu  bash -lc "pacman --noconfirm -Sy"
   - if %BUILD_ENV%==gnu  bash -lc "pacman -Sy --needed --noconfirm git mingw-w64-x86_64-toolchain mingw-w64-x86_64-freetype mingw-w64-x86_64-icu mingw-w64-x86_64-nspr mingw-w64-x86_64-ca-certificates mingw-w64-x86_64-expat mingw-w64-x86_64-cmake tar diffutils patch patchutils make python2-setuptools mingw-w64-x86_64-ffmpeg"
   - if %BUILD_ENV%==gnu  bash -lc "easy_install-2.7 pip virtualenv"
   - if %BUILD_ENV%==gnu  bash -lc "mv /mingw64/bin/python2.exe /mingw64/bin/python2-mingw64.exe"
@@ -82,15 +63,12 @@ install:
 # Uncomment these lines to expose RDP access information to the build machine in the build log.
 #init:
 #  - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
 #
 #on_finish:
 #  - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
 
 build_script:
-  - echo PATH %PATH%
-  - echo VSINSTALLDIR %VSINSTALLDIR%
-  - echo MOZTOOLS_PATH %MOZTOOLS_PATH%
-  - if %BUILD_ENV%==msvc cd %APPVEYOR_BUILD_FOLDER% && mach build -d -v && mach test-unit
-  - if %BUILD_ENV%==gnu  bash -lc "cd $APPVEYOR_BUILD_FOLDER; ./mach build -d -v && ./mach test-unit"
+  - if %BUILD_ENV%==msvc mach build -d -v && mach test-unit
+  - if %BUILD_ENV%==gnu  bash -lc "./mach build -d -v && ./mach test-unit"
 
 test: off
--- a/servo/python/servo/bootstrap_commands.py
+++ b/servo/python/servo/bootstrap_commands.py
@@ -14,16 +14,17 @@ import base64
 import json
 import os
 import os.path as path
 import re
 import shutil
 import sys
 import StringIO
 import tarfile
+import zipfile
 import urllib2
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
@@ -96,17 +97,20 @@ def download_file(desc, src, dst):
 
 def download_bytes(desc, src):
     content_writer = StringIO.StringIO()
     download(desc, src, content_writer)
     return content_writer.getvalue()
 
 
 def extract(src, dst, movedir=None):
-    tarfile.open(src).extractall(dst)
+    if src.endswith(".zip"):
+        zipfile.ZipFile(src).extractall(dst)
+    else:
+        tarfile.open(src).extractall(dst)
 
     if movedir:
         for f in os.listdir(movedir):
             frm = path.join(movedir, f)
             to = path.join(dst, f)
             os.rename(frm, to)
         os.rmdir(movedir)
 
@@ -121,16 +125,34 @@ class MachCommands(CommandBase):
     def env(self):
         env = self.build_env()
         print("export PATH=%s" % env["PATH"])
         if sys.platform == "darwin":
             print("export DYLD_LIBRARY_PATH=%s" % env["DYLD_LIBRARY_PATH"])
         else:
             print("export LD_LIBRARY_PATH=%s" % env["LD_LIBRARY_PATH"])
 
+    @Command('bootstrap',
+             description='Install required packages for building.',
+             category='bootstrap')
+    @CommandArgument('--interactive', "-i",
+                     action='store_true',
+                     help='Need to answer any (Y/n) interactive prompts.')
+    @CommandArgument('--android',
+                     action='store_true',
+                     help='Install required packages for Android')
+    @CommandArgument('--force', '-f',
+                     action='store_true',
+                     help='Force reinstall packages')
+    def bootstrap(self, android=False, interactive=False, force=False):
+        from servo.bootstrapper.bootstrap import Bootstrapper
+
+        bootstrapper = Bootstrapper()
+        bootstrapper.bootstrap(android=android, interactive=interactive, force=force)
+
     @Command('bootstrap-rust',
              description='Download the Rust compiler',
              category='bootstrap')
     @CommandArgument('--force', '-f',
                      action='store_true',
                      help='Force download even if a copy already exists')
     @CommandArgument('--target',
                      action='append',
copy from servo/python/tidy/servo_tidy_tests/shebang_license.py
copy to servo/python/servo/bootstrapper/__init__.py
--- a/servo/python/tidy/servo_tidy_tests/shebang_license.py
+++ b/servo/python/servo/bootstrapper/__init__.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
new file mode 100644
--- /dev/null
+++ b/servo/python/servo/bootstrapper/base.py
@@ -0,0 +1,62 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import print_function, unicode_literals
+
+import distutils
+import subprocess
+
+
+class BaseBootstrapper(object):
+    """Base class for system bootstrappers."""
+
+    def __init__(self, interactive=False):
+        self.package_manager_updated = False
+        self.interactive = interactive
+
+    def ensure_system_packages(self):
+        '''
+        Check for missing packages.
+        '''
+        raise NotImplementedError('%s must implement ensure_system_packages()' %
+                                  __name__)
+
+    def install_system_packages(self):
+        '''
+        Install packages required to build Servo.
+        '''
+        raise NotImplementedError('%s must implement install_system_packages()' %
+                                  __name__)
+
+    def install_mobile_android_packages(self):
+        '''
+        Install packages required to build Servo for Android.
+        '''
+        raise NotImplementedError('Cannot bootstrap Servo for Android: '
+                                  '%s does not yet implement install_mobile_android_packages()'
+                                  % __name__)
+
+    def which(self, name):
+        """Python implementation of which.
+
+        It returns the path of an executable or None if it couldn't be found.
+        """
+        return distutils.spawn.find_executable(name)
+
+    def check_output(self, *args, **kwargs):
+        """Run subprocess.check_output."""
+        return subprocess.check_output(*args, **kwargs)
+
+    def _ensure_package_manager_updated(self):
+        if self.package_manager_updated:
+            return
+
+        self._update_package_manager()
+        self.package_manager_updated = True
+
+    def _update_package_manager(self):
+        """Updates the package manager's manifests/package list.
+
+        This should be defined in child classes.
+        """
new file mode 100644
--- /dev/null
+++ b/servo/python/servo/bootstrapper/bootstrap.py
@@ -0,0 +1,41 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import print_function
+
+import sys
+
+from windows_gnu import WindowsGnuBootstrapper
+from windows_msvc import WindowsMsvcBootstrapper
+
+
+class Bootstrapper(object):
+    """Main class that performs system bootstrap."""
+
+    def __init__(self):
+        self.instance = None
+        cls = None
+        args = {}
+
+        if sys.platform.startswith('msys'):
+            cls = WindowsGnuBootstrapper
+
+        elif sys.platform.startswith('win32'):
+            cls = WindowsMsvcBootstrapper
+
+        if cls is None:
+            sys.exit('Bootstrap support is not yet available for your OS.')
+
+        self.instance = cls(**args)
+
+    def bootstrap(self, android=False, interactive=False, force=False):
+        self.instance.interactive = interactive
+        self.instance.force = force
+
+        if android:
+            self.instance.install_mobile_android_packages()
+        elif force:
+            self.instance.install_system_packages()
+        else:
+            self.instance.ensure_system_packages()
new file mode 100644
--- /dev/null
+++ b/servo/python/servo/bootstrapper/packages.py
@@ -0,0 +1,28 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Listed all packages for different platforms in one file
+
+WINDOWS_GNU = [
+    "mingw-w64-x86_64-toolchain",
+    "mingw-w64-x86_64-freetype",
+    "mingw-w64-x86_64-icu",
+    "mingw-w64-x86_64-nspr",
+    "mingw-w64-x86_64-ca-certificates",
+    "mingw-w64-x86_64-expat",
+    "mingw-w64-x86_64-cmake",
+    "tar",
+    "diffutils",
+    "patch",
+    "patchutils",
+    "make",
+    "python2-setuptools",
+]
+
+WINDOWS_MSVC = [
+    "cmake-3.6.1",
+    "ninja-1.7.1",
+    "openssl-1.0.1t-vs2015",
+    "moztools-0.0.1-5",
+]
new file mode 100644
--- /dev/null
+++ b/servo/python/servo/bootstrapper/windows_gnu.py
@@ -0,0 +1,75 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import sys
+import subprocess
+
+from base import BaseBootstrapper
+from packages import WINDOWS_GNU as deps
+
+
+class WindowsGnuBootstrapper(BaseBootstrapper):
+    '''Bootstrapper for msys2 based environments for building in Windows.'''
+
+    def __init__(self, **kwargs):
+        BaseBootstrapper.__init__(self, **kwargs)
+
+        if not self.which('pacman'):
+            raise NotImplementedError('The Windows bootstrapper only works with msys2 with pacman. Get msys2 at '
+                                      'http://msys2.github.io/')
+
+    def ensure_system_packages(self):
+        install_packages = []
+        for p in deps:
+            command = ['pacman', '-Qs', p]
+            if self.run_check(command):
+                install_packages += [p]
+        if install_packages:
+            install_packages(install_packages)
+
+    def install_system_packages(self, packages=deps):
+        self._ensure_package_manager_updated()
+        self.pacman_install(*packages)
+
+    def install_mobile_android_packages(self):
+        sys.exit('We do not support building Android on Windows. Sorry!')
+
+    def _update_package_manager(self):
+        self.pacman_update()
+
+    def run(self, command):
+        subprocess.check_call(command, stdin=sys.stdin)
+
+    def run_check(self, command):
+        return subprocess.call(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+    def pacman_update(self):
+        command = ['pacman', '--sync', '--refresh']
+        self.run(command)
+
+    def pacman_upgrade(self):
+        command = ['pacman', '--sync', '--refresh', '--sysupgrade']
+        self.run(command)
+
+    def pacman_install(self, *packages):
+        command = ['pacman', '--sync']
+        if not self.force:
+            command.append('--needed')
+        if not self.interactive:
+            command.append('--noconfirm')
+        command.extend(packages)
+        self.run(command)
+
+        # downgrade GCC to 5.4.0-1
+        gcc_type = ["gcc", "gcc-ada", "gcc-fortran", "gcc-libgfortran", "gcc-libs", "gcc-objc"]
+        gcc_version = "5.4.0-1"
+        mingw_url = "http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-{}-{}-any.pkg.tar.xz"
+        gcc_list = []
+        for gcc in gcc_type:
+            gcc_list += [mingw_url.format(gcc, gcc_version)]
+        downgrade_command = ['pacman', '-U']
+        if not self.interactive:
+            downgrade_command.append('--noconfirm')
+        downgrade_command.extend(gcc_list)
+        self.run(downgrade_command)
new file mode 100644
--- /dev/null
+++ b/servo/python/servo/bootstrapper/windows_msvc.py
@@ -0,0 +1,86 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import sys
+import shutil
+from distutils import spawn
+
+from base import BaseBootstrapper
+from packages import WINDOWS_MSVC as deps
+
+
+class WindowsMsvcBootstrapper(BaseBootstrapper):
+    '''Bootstrapper for MSVC building on Windows.'''
+
+    def __init__(self, **kwargs):
+        BaseBootstrapper.__init__(self, **kwargs)
+
+    def ensure_system_packages(self):
+        self.install_system_packages()
+
+    def install_system_packages(self, packages=deps):
+        from servo.bootstrap_commands import extract, download_file
+
+        deps_dir = os.path.join(".servo", "msvc-dependencies")
+        deps_url = "https://servo-rust.s3.amazonaws.com/msvc-deps/"
+        first_run = True
+
+        if self.force:
+            if os.path.isdir(deps_dir):
+                shutil.rmtree(deps_dir)
+
+        if not os.path.isdir(deps_dir):
+            os.makedirs(deps_dir)
+
+        # Read file with installed dependencies, if exist
+        installed_deps_file = os.path.join(deps_dir, "installed-dependencies.txt")
+        if os.path.exists(installed_deps_file):
+            installed_deps = [l.strip() for l in open(installed_deps_file)]
+        else:
+            installed_deps = []
+
+        # list of dependencies that need to be updated
+        update_deps = list(set(packages) - set(installed_deps))
+
+        for dep in packages:
+            dep_name = dep.split("-")[0]
+
+            # Don't download CMake if already exists in PATH
+            if dep_name == "cmake":
+                if spawn.find_executable(dep_name):
+                    continue
+
+            dep_dir = os.path.join(deps_dir, dep_name)
+            # if not installed or need to be updated
+            if not os.path.exists(dep_dir) or dep in update_deps:
+                if first_run:
+                    print "Installing missing MSVC dependencies..."
+                    first_run = False
+
+                dep_version_dir = os.path.join(deps_dir, dep)
+
+                if os.path.exists(dep_version_dir):
+                    shutil.rmtree(dep_version_dir)
+
+                dep_zip = dep_version_dir + ".zip"
+                if not os.path.isfile(dep_zip):
+                    download_file(dep, "%s%s.zip" % (deps_url, dep), dep_zip)
+
+                print "Extracting %s..." % dep,
+                extract(dep_zip, deps_dir)
+                print "done"
+
+                # Delete directory if exist
+                if os.path.exists(dep_dir):
+                    shutil.rmtree(dep_dir)
+                os.rename(dep_version_dir, dep_dir)
+
+        # Write in installed-dependencies.txt file
+        with open(installed_deps_file, 'w') as installed_file:
+            for line in packages:
+                installed_file.write(line + "\n")
+
+    def install_mobile_android_packages(self):
+        sys.exit('We do not support building Android on Windows. Sorry!')
--- a/servo/python/servo/build_commands.py
+++ b/servo/python/servo/build_commands.py
@@ -252,26 +252,24 @@ class MachCommands(CommandBase):
 
         # Do some additional things if the build succeeded
         if status == 0:
             if sys.platform in ("win32", "msys"):
                 servo_exe_dir = path.join(base_path, "debug" if dev else "release")
                 # On windows, copy in our manifest
                 shutil.copy(path.join(self.get_top_dir(), "components", "servo", "servo.exe.manifest"),
                             servo_exe_dir)
-                if "msvc" in host_triple():
+                if "msvc" in (target or host_triple()):
+                    msvc_x64 = "64" if "x86_64" in (target or host_triple()) else ""
                     # on msvc builds, use editbin to change the subsystem to windows
                     call(["editbin", "/nologo", "/subsystem:windows", path.join(servo_exe_dir, "servo.exe")],
                          verbose=verbose)
                     # on msvc, we need to copy in some DLLs in to the servo.exe dir
                     for ssl_lib in ["ssleay32md.dll", "libeay32md.dll"]:
-                        shutil.copy(path.join(os.getenv('OPENSSL_LIB_DIR'), "../bin64", ssl_lib),
-                                    servo_exe_dir)
-                    for ffmpeg_lib in ["avutil-55.dll", "avformat-57.dll", "avcodec-57.dll", "swresample-2.dll"]:
-                        shutil.copy(path.join(os.getenv('FFMPEG_LIB_DIR'), "../bin", ffmpeg_lib),
+                        shutil.copy(path.join(env['OPENSSL_LIB_DIR'], "../bin" + msvc_x64, ssl_lib),
                                     servo_exe_dir)
 
                 elif sys.platform == "darwin":
                     # On the Mac, set a lovely icon. This makes it easier to pick out the Servo binary in tools
                     # like Instruments.app.
                     try:
                         import Cocoa
                         icon_path = path.join(self.get_top_dir(), "resources", "servo.png")
--- a/servo/python/servo/command_base.py
+++ b/servo/python/servo/command_base.py
@@ -367,16 +367,28 @@ class CommandBase(object):
             # turning os.environ['PATH'] into a unicode string.  This doesn't work
             # for passing env vars in to a process, so we force it back to ascii.
             # We don't use UTF8 since that won't be correct anyway; if you actually
             # have unicode stuff in your path, all this PATH munging would have broken
             # it in any case.
             env['PATH'] = env['PATH'].encode('ascii', 'ignore')
         extra_path = []
         extra_lib = []
+        if "msvc" in (target or host_triple()):
+            msvc_x64 = "64" if "x86_64" in (target or host_triple()) else ""
+            msvc_deps_dir = path.join(self.context.sharedir, "msvc-dependencies")
+            extra_path += [path.join(msvc_deps_dir, "cmake", "bin")]
+            extra_path += [path.join(msvc_deps_dir, "ninja", "bin")]
+            # Link openssl
+            env["OPENSSL_INCLUDE_DIR"] = path.join(msvc_deps_dir, "openssl", "include")
+            env["OPENSSL_LIB_DIR"] = path.join(msvc_deps_dir, "openssl", "lib" + msvc_x64)
+            env["OPENSSL_LIBS"] = "ssleay32MD:libeay32MD"
+            # Link moztools
+            env["MOZTOOLS_PATH"] = path.join(msvc_deps_dir, "moztools", "bin")
+
         if not self.config["tools"]["system-rust"] \
                 or self.config["tools"]["rust-root"]:
             env["RUST_ROOT"] = self.config["tools"]["rust-root"]
             # Add mingw64 binary path before rust paths to avoid conflict with libstdc++-6.dll
             if sys.platform == "msys":
                 extra_path += [path.join(os.sep, "mingw64", "bin")]
             # These paths are for when rust-root points to an unpacked installer
             extra_path += [path.join(self.config["tools"]["rust-root"], "rustc", "bin")]
@@ -488,27 +500,33 @@ class CommandBase(object):
 
     def android_build_dir(self, dev):
         return path.join(self.get_target_dir(), "arm-linux-androideabi", "debug" if dev else "release")
 
     def ensure_bootstrapped(self, target=None):
         if self.context.bootstrapped:
             return
 
+        target_platform = target or host_triple()
+
         rust_root = self.config["tools"]["rust-root"]
         rustc_path = path.join(
             rust_root, "rustc", "bin", "rustc" + BIN_SUFFIX
         )
         rustc_binary_exists = path.exists(rustc_path)
 
         base_target_path = path.join(rust_root, "rustc", "lib", "rustlib")
 
-        target_path = path.join(base_target_path, target or host_triple())
+        target_path = path.join(base_target_path, target_platform)
         target_exists = path.exists(target_path)
 
+        # Always check if all needed MSVC dependencies are installed
+        if "msvc" in target_platform:
+            Registrar.dispatch("bootstrap", context=self.context)
+
         if not (self.config['tools']['system-rust'] or (rustc_binary_exists and target_exists)):
             print("looking for rustc at %s" % (rustc_path))
             Registrar.dispatch("bootstrap-rust", context=self.context, target=filter(None, [target]),
                                stable=self._use_stable_rust)
 
         cargo_path = path.join(self.config["tools"]["cargo-root"], "cargo", "bin",
                                "cargo" + BIN_SUFFIX)
         cargo_binary_exists = path.exists(cargo_path)