xref: /aosp_15_r20/external/perfetto/infra/luci/recipe_modules/windows_sdk/api.py (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1# Copyright (C) 2021 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from contextlib import contextmanager
16
17from recipe_engine import recipe_api
18
19
20class WindowsSDKApi(recipe_api.RecipeApi):
21  """API for using Windows SDK distributed via CIPD."""
22
23  def __init__(self, sdk_properties, *args, **kwargs):
24    super(WindowsSDKApi, self).__init__(*args, **kwargs)
25
26    self._sdk_package = sdk_properties['sdk_package']
27    self._sdk_version = sdk_properties['sdk_version']
28
29  @contextmanager
30  def __call__(self):
31    """Setups the Windows SDK environment.
32
33    This call is a no-op on non-Windows platforms.
34
35    Raises:
36        StepFailure or InfraFailure.
37    """
38    if not self.m.platform.is_win:
39      yield
40      return
41
42    with self.m.context(infra_steps=True):
43      sdk_dir = self._ensure_sdk()
44    with self.m.context(**self._sdk_env(sdk_dir)):
45      yield
46
47  def _ensure_sdk(self):
48    """Ensures the Windows SDK CIPD package is installed.
49
50    Returns the directory where the SDK package has been installed.
51
52    Args:
53      path (path): Path to a directory.
54      version (str): CIPD instance ID, tag or ref.
55    """
56    sdk_dir = self.m.path['cache'].join('windows_sdk')
57    pkgs = self.m.cipd.EnsureFile()
58    pkgs.add_package(self._sdk_package, self._sdk_version)
59    self.m.cipd.ensure(sdk_dir, pkgs)
60    return sdk_dir
61
62  def _sdk_env(self, sdk_dir):
63    """Constructs the environment for the SDK.
64
65    Returns environment and environment prefixes.
66
67    Args:
68      sdk_dir (path): Path to a directory containing the SDK.
69    """
70    env = {}
71    env_prefixes = {}
72
73    # Load .../win_sdk/bin/SetEnv.${arch}.json to extract the required
74    # environment. It contains a dict that looks like this:
75    # {
76    #   "env": {
77    #     "VAR": [["..", "..", "x"], ["..", "..", "y"]],
78    #     ...
79    #   }
80    # }
81    # All these environment variables need to be added to the environment
82    # for the compiler and linker to work.
83    filename = 'SetEnv.%s.json' % {32: 'x86', 64: 'x64'}[self.m.platform.bits]
84    step_result = self.m.json.read(
85        'read %s' % filename,
86        sdk_dir.join('win_sdk', 'bin', filename),
87        step_test_data=lambda: self.m.json.test_api.output({
88            'env': {
89                'PATH': [['..', '..', 'win_sdk', 'bin', 'x64']],
90                'VSINSTALLDIR': [['..', '..\\']],
91            },
92        }))
93    data = step_result.json.output.get('env')
94    for key in data:
95      # recipes' Path() does not like .., ., \, or /, so this is cumbersome.
96      # What we want to do is:
97      #   [sdk_bin_dir.join(*e) for e in env[k]]
98      # Instead do that badly, and rely (but verify) on the fact that the paths
99      # are all specified relative to the root, but specified relative to
100      # win_sdk/bin (i.e. everything starts with "../../".)
101      results = []
102      for value in data[key]:
103        assert value[0] == '..' and (value[1] == '..' or value[1] == '..\\')
104        results.append('%s' % sdk_dir.join(*value[2:]))
105
106      # PATH is special-cased because we don't want to overwrite other things
107      # like C:\Windows\System32. Others are replacements because prepending
108      # doesn't necessarily makes sense, like VSINSTALLDIR.
109      if key.lower() == 'path':
110        env_prefixes[key] = results
111      else:
112        env[key] = ';'.join(results)
113
114    return {'env': env, 'env_prefixes': env_prefixes}
115