|  | # Copyright 2018 The Chromium Authors. All rights reserved. | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  |  | 
|  | from contextlib import contextmanager | 
|  |  | 
|  | from recipe_engine import recipe_api | 
|  |  | 
|  |  | 
|  | class WindowsSDKApi(recipe_api.RecipeApi): | 
|  | """API for using Windows SDK distributed via CIPD.""" | 
|  |  | 
|  | def __init__(self, sdk_properties, *args, **kwargs): | 
|  | super(WindowsSDKApi, self).__init__(*args, **kwargs) | 
|  |  | 
|  | self._sdk_package = sdk_properties['sdk_package'] | 
|  | self._sdk_version = sdk_properties['sdk_version'] | 
|  |  | 
|  | @contextmanager | 
|  | def __call__(self): | 
|  | """Setups the Windows SDK environment. | 
|  |  | 
|  | This call is a no-op on non-Windows platforms. | 
|  |  | 
|  | Raises: | 
|  | StepFailure or InfraFailure. | 
|  | """ | 
|  | if not self.m.platform.is_win: | 
|  | yield | 
|  | return | 
|  |  | 
|  | with self.m.context(infra_steps=True): | 
|  | sdk_dir = self._ensure_sdk() | 
|  |  | 
|  | with self.m.context(**self._sdk_env(sdk_dir)): | 
|  | try: | 
|  | yield | 
|  | finally: | 
|  | # cl.exe automatically starts background mspdbsrv.exe daemon which | 
|  | # needs to be manually stopped so Swarming can tidy up after itself. | 
|  | self.m.step('taskkill mspdbsrv', | 
|  | ['taskkill.exe', '/f', '/t', '/im', 'mspdbsrv.exe']) | 
|  |  | 
|  | def _ensure_sdk(self): | 
|  | """Ensures the Windows SDK CIPD package is installed. | 
|  |  | 
|  | Returns the directory where the SDK package has been installed. | 
|  |  | 
|  | Args: | 
|  | path (path): Path to a directory. | 
|  | version (str): CIPD instance ID, tag or ref. | 
|  | """ | 
|  | sdk_dir = self.m.path['cache'].join('windows_sdk') | 
|  | pkgs = self.m.cipd.EnsureFile() | 
|  | pkgs.add_package(self._sdk_package, self._sdk_version) | 
|  | self.m.cipd.ensure(sdk_dir, pkgs) | 
|  | return sdk_dir | 
|  |  | 
|  | def _sdk_env(self, sdk_dir): | 
|  | """Constructs the environment for the SDK. | 
|  |  | 
|  | Returns environment and environment prefixes. | 
|  |  | 
|  | Args: | 
|  | sdk_dir (path): Path to a directory containing the SDK. | 
|  | """ | 
|  | env = {} | 
|  | env_prefixes = {} | 
|  |  | 
|  | # Load .../Windows Kits/10/bin/SetEnv.${arch}.json to extract the required | 
|  | # environment. It contains a dict that looks like this: | 
|  | # { | 
|  | #   "env": { | 
|  | #     "VAR": [["x"], ["y"]], | 
|  | #     ... | 
|  | #   } | 
|  | # } | 
|  | # All these environment variables need to be added to the environment | 
|  | # for the compiler and linker to work. | 
|  | filename = 'SetEnv.%s.json' % {32: 'x86', 64: 'x64'}[self.m.platform.bits] | 
|  | step_result = self.m.json.read( | 
|  | 'read %s' % filename, | 
|  | sdk_dir.join('Windows Kits', '10', 'bin', filename), | 
|  | step_test_data=lambda: self.m.json.test_api.output({ | 
|  | 'env': { | 
|  | 'PATH': [['Windows Kits', '10', 'bin', '10.0.19041.0', 'x64']], | 
|  | 'VSINSTALLDIR': [['.\\']], | 
|  | }, | 
|  | })) | 
|  | data = step_result.json.output.get('env') | 
|  | for key in data: | 
|  | results = ['%s' % sdk_dir.join(*e) for e in data[key]] | 
|  |  | 
|  | # PATH is special-cased because we don't want to overwrite other things | 
|  | # like C:\Windows\System32. Others are replacements because prepending | 
|  | # doesn't necessarily makes sense, like VSINSTALLDIR. | 
|  | if key.lower() == 'path': | 
|  | env_prefixes[key] = results | 
|  | else: | 
|  | env[key] = ';'.join(results) | 
|  |  | 
|  | return {'env': env, 'env_prefixes': env_prefixes} |