| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | |
| 3 | # Copyright 2017 The Glslang Authors. All rights reserved. |
| 4 | # Copyright (c) 2018 Valve Corporation |
| 5 | # Copyright (c) 2018 LunarG, Inc. |
| 6 | # |
| 7 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 8 | # you may not use this file except in compliance with the License. |
| 9 | # You may obtain a copy of the License at |
| 10 | # |
| 11 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 12 | # |
| 13 | # Unless required by applicable law or agreed to in writing, software |
| 14 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 16 | # See the License for the specific language governing permissions and |
| 17 | # limitations under the License. |
| 18 | |
| 19 | # This script was heavily leveraged from KhronosGroup/glslang |
| 20 | # update_glslang_sources.py. |
| 21 | """update_deps.py |
| 22 | |
| 23 | Get and build dependent repositories using known-good commits. |
| 24 | |
| 25 | Purpose |
| 26 | ------- |
| 27 | |
| 28 | This program is intended to assist a developer of this repository |
| 29 | (the "home" repository) by gathering and building the repositories that |
| 30 | this home repository depend on. It also checks out each dependent |
| 31 | repository at a "known-good" commit in order to provide stability in |
| 32 | the dependent repositories. |
| 33 | |
| 34 | Python Compatibility |
| 35 | -------------------- |
| 36 | |
| 37 | This program can be used with Python 2.7 and Python 3. |
| 38 | |
| 39 | Known-Good JSON Database |
| 40 | ------------------------ |
| 41 | |
| 42 | This program expects to find a file named "known-good.json" in the |
| 43 | same directory as the program file. This JSON file is tailored for |
| 44 | the needs of the home repository by including its dependent repositories. |
| 45 | |
| 46 | Program Options |
| 47 | --------------- |
| 48 | |
| 49 | See the help text (update_deps.py --help) for a complete list of options. |
| 50 | |
| 51 | Program Operation |
| 52 | ----------------- |
| 53 | |
| 54 | The program uses the user's current directory at the time of program |
| 55 | invocation as the location for fetching and building the dependent |
| 56 | repositories. The user can override this by using the "--dir" option. |
| 57 | |
| 58 | For example, a directory named "build" in the repository's root directory |
| 59 | is a good place to put the dependent repositories because that directory |
| 60 | is not tracked by Git. (See the .gitignore file.) The "external" directory |
| 61 | may also be a suitable location. |
| 62 | A user can issue: |
| 63 | |
| 64 | $ cd My-Repo |
| 65 | $ mkdir build |
| 66 | $ cd build |
| 67 | $ ../scripts/update_deps.py |
| 68 | |
| 69 | or, to do the same thing, but using the --dir option: |
| 70 | |
| 71 | $ cd My-Repo |
| 72 | $ mkdir build |
| 73 | $ scripts/update_deps.py --dir=build |
| 74 | |
| 75 | With these commands, the "build" directory is considered the "top" |
| 76 | directory where the program clones the dependent repositories. The |
| 77 | JSON file configures the build and install working directories to be |
| 78 | within this "top" directory. |
| 79 | |
| 80 | Note that the "dir" option can also specify an absolute path: |
| 81 | |
| 82 | $ cd My-Repo |
| 83 | $ scripts/update_deps.py --dir=/tmp/deps |
| 84 | |
| 85 | The "top" dir is then /tmp/deps (Linux filesystem example) and is |
| 86 | where this program will clone and build the dependent repositories. |
| 87 | |
| 88 | Helper CMake Config File |
| 89 | ------------------------ |
| 90 | |
| 91 | When the program finishes building the dependencies, it writes a file |
| 92 | named "helper.cmake" to the "top" directory that contains CMake commands |
| 93 | for setting CMake variables for locating the dependent repositories. |
| 94 | This helper file can be used to set up the CMake build files for this |
| 95 | "home" repository. |
| 96 | |
| 97 | A complete sequence might look like: |
| 98 | |
| 99 | $ git clone git@github.com:My-Group/My-Repo.git |
| 100 | $ cd My-Repo |
| 101 | $ mkdir build |
| 102 | $ cd build |
| 103 | $ ../scripts/update_deps.py |
| 104 | $ cmake -C helper.cmake .. |
| 105 | $ cmake --build . |
| 106 | |
| 107 | JSON File Schema |
| 108 | ---------------- |
| 109 | |
| Shannon McPherson | fabe2c2 | 2018-07-31 09:41:15 -0600 | [diff] [blame] | 110 | There's no formal schema for the "known-good" JSON file, but here is |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 111 | a description of its elements. All elements are required except those |
| 112 | marked as optional. Please see the "known_good.json" file for |
| 113 | examples of all of these elements. |
| 114 | |
| 115 | - name |
| 116 | |
| 117 | The name of the dependent repository. This field can be referenced |
| 118 | by the "deps.repo_name" structure to record a dependency. |
| 119 | |
| 120 | - url |
| 121 | |
| 122 | Specifies the URL of the repository. |
| 123 | Example: https://github.com/KhronosGroup/Vulkan-Loader.git |
| 124 | |
| 125 | - sub_dir |
| 126 | |
| 127 | The directory where the program clones the repository, relative to |
| 128 | the "top" directory. |
| 129 | |
| 130 | - build_dir |
| 131 | |
| 132 | The directory used to build the repository, relative to the "top" |
| 133 | directory. |
| 134 | |
| 135 | - install_dir |
| 136 | |
| 137 | The directory used to store the installed build artifacts, relative |
| 138 | to the "top" directory. |
| 139 | |
| 140 | - commit |
| 141 | |
| 142 | The commit used to checkout the repository. This can be a SHA-1 |
| 143 | object name or a refname used with the remote name "origin". |
| 144 | For example, this field can be set to "origin/sdk-1.1.77" to |
| 145 | select the end of the sdk-1.1.77 branch. |
| 146 | |
| 147 | - deps (optional) |
| 148 | |
| 149 | An array of pairs consisting of a CMake variable name and a |
| 150 | repository name to specify a dependent repo and a "link" to |
| 151 | that repo's install artifacts. For example: |
| 152 | |
| 153 | "deps" : [ |
| 154 | { |
| 155 | "var_name" : "VULKAN_HEADERS_INSTALL_DIR", |
| 156 | "repo_name" : "Vulkan-Headers" |
| 157 | } |
| 158 | ] |
| 159 | |
| 160 | which represents that this repository depends on the Vulkan-Headers |
| 161 | repository and uses the VULKAN_HEADERS_INSTALL_DIR CMake variable to |
| 162 | specify the location where it expects to find the Vulkan-Headers install |
| 163 | directory. |
| 164 | Note that the "repo_name" element must match the "name" element of some |
| 165 | other repository in the JSON file. |
| 166 | |
| 167 | - prebuild (optional) |
| 168 | - prebuild_linux (optional) (For Linux and MacOS) |
| 169 | - prebuild_windows (optional) |
| 170 | |
| 171 | A list of commands to execute before building a dependent repository. |
| 172 | This is useful for repositories that require the execution of some |
| 173 | sort of "update" script or need to clone an auxillary repository like |
| 174 | googletest. |
| 175 | |
| 176 | The commands listed in "prebuild" are executed first, and then the |
| 177 | commands for the specific platform are executed. |
| 178 | |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 179 | - custom_build (optional) |
| 180 | |
| 181 | A list of commands to execute as a custom build instead of using |
| 182 | the built in CMake way of building. Requires "build_step" to be |
| 183 | set to "custom" |
| 184 | |
| 185 | You can insert the following keywords into the commands listed in |
| 186 | "custom_build" if they require runtime information (like whether the |
| 187 | build config is "Debug" or "Release"). |
| 188 | |
| 189 | Keywords: |
| 190 | {0} reference to a dictionary of repos and their attributes |
| 191 | {1} reference to the command line arguments set before start |
| 192 | {2} reference to the CONFIG_MAP value of config. |
| 193 | |
| 194 | Example: |
| 195 | {2} returns the CONFIG_MAP value of config e.g. debug -> Debug |
| 196 | {1}.config returns the config variable set when you ran update_dep.py |
| 197 | {0}[Vulkan-Headers][repo_root] returns the repo_root variable from |
| 198 | the Vulkan-Headers GoodRepo object. |
| 199 | |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 200 | - cmake_options (optional) |
| 201 | |
| 202 | A list of options to pass to CMake during the generation phase. |
| 203 | |
| 204 | - ci_only (optional) |
| 205 | |
| 206 | A list of environment variables where one must be set to "true" |
| 207 | (case-insensitive) in order for this repo to be fetched and built. |
| 208 | This list can be used to specify repos that should be built only in CI. |
| 209 | Typically, this list might contain "TRAVIS" and/or "APPVEYOR" because |
| 210 | each of these CI systems sets an environment variable with its own |
| 211 | name to "true". Note that this could also be (ab)used to control |
| 212 | the processing of the repo with any environment variable. The default |
| 213 | is an empty list, which means that the repo is always processed. |
| 214 | |
| Shannon McPherson | fabe2c2 | 2018-07-31 09:41:15 -0600 | [diff] [blame] | 215 | - build_step (optional) |
| 216 | |
| 217 | Specifies if the dependent repository should be built or not. This can |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 218 | have a value of 'build', 'custom', or 'skip'. The dependent repositories are |
| Shannon McPherson | fabe2c2 | 2018-07-31 09:41:15 -0600 | [diff] [blame] | 219 | built by default. |
| 220 | |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 221 | - build_platforms (optional) |
| 222 | |
| 223 | A list of platforms the repository will be built on. |
| 224 | Legal options include: |
| 225 | "windows" |
| 226 | "linux" |
| 227 | "darwin" |
| 228 | |
| 229 | Builds on all platforms by default. |
| 230 | |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 231 | Note |
| 232 | ---- |
| 233 | |
| 234 | The "sub_dir", "build_dir", and "install_dir" elements are all relative |
| 235 | to the effective "top" directory. Specifying absolute paths is not |
| 236 | supported. However, the "top" directory specified with the "--dir" |
| 237 | option can be a relative or absolute path. |
| 238 | |
| 239 | """ |
| 240 | |
| 241 | from __future__ import print_function |
| 242 | |
| 243 | import argparse |
| 244 | import json |
| 245 | import distutils.dir_util |
| 246 | import os.path |
| 247 | import subprocess |
| 248 | import sys |
| 249 | import platform |
| 250 | import multiprocessing |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 251 | import shlex |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 252 | import shutil |
| 253 | |
| Mark Lobodzinski | ac1043c | 2018-07-19 13:20:46 -0600 | [diff] [blame] | 254 | KNOWN_GOOD_FILE_NAME = 'known_good.json' |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 255 | |
| 256 | CONFIG_MAP = { |
| 257 | 'debug': 'Debug', |
| 258 | 'release': 'Release', |
| 259 | 'relwithdebinfo': 'RelWithDebInfo', |
| 260 | 'minsizerel': 'MinSizeRel' |
| 261 | } |
| 262 | |
| 263 | VERBOSE = False |
| 264 | |
| 265 | DEVNULL = open(os.devnull, 'wb') |
| 266 | |
| 267 | |
| 268 | def command_output(cmd, directory, fail_ok=False): |
| 269 | """Runs a command in a directory and returns its standard output stream. |
| 270 | |
| 271 | Captures the standard error stream and prints it if error. |
| 272 | |
| 273 | Raises a RuntimeError if the command fails to launch or otherwise fails. |
| 274 | """ |
| 275 | if VERBOSE: |
| 276 | print('In {d}: {cmd}'.format(d=directory, cmd=cmd)) |
| 277 | p = subprocess.Popen( |
| 278 | cmd, cwd=directory, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| 279 | (stdout, stderr) = p.communicate() |
| 280 | if p.returncode != 0: |
| 281 | print('*** Error ***\nstderr contents:\n{}'.format(stderr)) |
| 282 | if not fail_ok: |
| 283 | raise RuntimeError('Failed to run {} in {}'.format(cmd, directory)) |
| 284 | if VERBOSE: |
| 285 | print(stdout) |
| 286 | return stdout |
| 287 | |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 288 | class GoodRepo(object): |
| 289 | """Represents a repository at a known-good commit.""" |
| 290 | |
| 291 | def __init__(self, json, args): |
| 292 | """Initializes this good repo object. |
| 293 | |
| 294 | Args: |
| 295 | 'json': A fully populated JSON object describing the repo. |
| 296 | 'args': Results from ArgumentParser |
| 297 | """ |
| 298 | self._json = json |
| 299 | self._args = args |
| 300 | # Required JSON elements |
| 301 | self.name = json['name'] |
| 302 | self.url = json['url'] |
| 303 | self.sub_dir = json['sub_dir'] |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 304 | self.commit = json['commit'] |
| 305 | # Optional JSON elements |
| Mark Lobodzinski | 3fe96b6 | 2018-07-19 14:45:10 -0600 | [diff] [blame] | 306 | self.build_dir = None |
| 307 | self.install_dir = None |
| 308 | if json.get('build_dir'): |
| Mike Schuchardt | c5ffcb0 | 2018-11-08 16:44:13 -0800 | [diff] [blame] | 309 | self.build_dir = os.path.normpath(json['build_dir']) |
| Mark Lobodzinski | 3fe96b6 | 2018-07-19 14:45:10 -0600 | [diff] [blame] | 310 | if json.get('install_dir'): |
| Mike Schuchardt | c5ffcb0 | 2018-11-08 16:44:13 -0800 | [diff] [blame] | 311 | self.install_dir = os.path.normpath(json['install_dir']) |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 312 | self.deps = json['deps'] if ('deps' in json) else [] |
| 313 | self.prebuild = json['prebuild'] if ('prebuild' in json) else [] |
| 314 | self.prebuild_linux = json['prebuild_linux'] if ( |
| 315 | 'prebuild_linux' in json) else [] |
| 316 | self.prebuild_windows = json['prebuild_windows'] if ( |
| 317 | 'prebuild_windows' in json) else [] |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 318 | self.custom_build = json['custom_build'] if ('custom_build' in json) else [] |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 319 | self.cmake_options = json['cmake_options'] if ( |
| 320 | 'cmake_options' in json) else [] |
| 321 | self.ci_only = json['ci_only'] if ('ci_only' in json) else [] |
| Shannon McPherson | fabe2c2 | 2018-07-31 09:41:15 -0600 | [diff] [blame] | 322 | self.build_step = json['build_step'] if ('build_step' in json) else 'build' |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 323 | self.build_platforms = json['build_platforms'] if ('build_platforms' in json) else [] |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 324 | # Absolute paths for a repo's directories |
| 325 | dir_top = os.path.abspath(args.dir) |
| 326 | self.repo_dir = os.path.join(dir_top, self.sub_dir) |
| Mark Lobodzinski | 3fe96b6 | 2018-07-19 14:45:10 -0600 | [diff] [blame] | 327 | if self.build_dir: |
| 328 | self.build_dir = os.path.join(dir_top, self.build_dir) |
| 329 | if self.install_dir: |
| 330 | self.install_dir = os.path.join(dir_top, self.install_dir) |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 331 | # Check if platform is one to build on |
| 332 | self.on_build_platform = False |
| 333 | if self.build_platforms == [] or platform.system().lower() in self.build_platforms: |
| 334 | self.on_build_platform = True |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 335 | |
| 336 | def Clone(self): |
| 337 | distutils.dir_util.mkpath(self.repo_dir) |
| 338 | command_output(['git', 'clone', self.url, '.'], self.repo_dir) |
| 339 | |
| 340 | def Fetch(self): |
| 341 | command_output(['git', 'fetch', 'origin'], self.repo_dir) |
| 342 | |
| 343 | def Checkout(self): |
| 344 | print('Checking out {n} in {d}'.format(n=self.name, d=self.repo_dir)) |
| 345 | if self._args.do_clean_repo: |
| 346 | shutil.rmtree(self.repo_dir) |
| 347 | if not os.path.exists(os.path.join(self.repo_dir, '.git')): |
| 348 | self.Clone() |
| 349 | self.Fetch() |
| 350 | if len(self._args.ref): |
| 351 | command_output(['git', 'checkout', self._args.ref], self.repo_dir) |
| 352 | else: |
| 353 | command_output(['git', 'checkout', self.commit], self.repo_dir) |
| 354 | print(command_output(['git', 'status'], self.repo_dir)) |
| 355 | |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 356 | def CustomPreProcess(self, cmd_str, repo_dict): |
| 357 | return cmd_str.format(repo_dict, self._args, CONFIG_MAP[self._args.config]) |
| 358 | |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 359 | def PreBuild(self): |
| 360 | """Execute any prebuild steps from the repo root""" |
| 361 | for p in self.prebuild: |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 362 | command_output(shlex.split(p), self.repo_dir) |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 363 | if platform.system() == 'Linux' or platform.system() == 'Darwin': |
| 364 | for p in self.prebuild_linux: |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 365 | command_output(shlex.split(p), self.repo_dir) |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 366 | if platform.system() == 'Windows': |
| 367 | for p in self.prebuild_windows: |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 368 | command_output(shlex.split(p), self.repo_dir) |
| 369 | |
| 370 | def CustomBuild(self, repo_dict): |
| 371 | """Execute any custom_build steps from the repo root""" |
| 372 | for p in self.custom_build: |
| 373 | cmd = self.CustomPreProcess(p, repo_dict) |
| 374 | command_output(shlex.split(cmd), self.repo_dir) |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 375 | |
| 376 | def CMakeConfig(self, repos): |
| 377 | """Build CMake command for the configuration phase and execute it""" |
| 378 | if self._args.do_clean_build: |
| 379 | shutil.rmtree(self.build_dir) |
| 380 | if self._args.do_clean_install: |
| 381 | shutil.rmtree(self.install_dir) |
| 382 | |
| 383 | # Create and change to build directory |
| 384 | distutils.dir_util.mkpath(self.build_dir) |
| 385 | os.chdir(self.build_dir) |
| 386 | |
| 387 | cmake_cmd = [ |
| 388 | 'cmake', self.repo_dir, |
| 389 | '-DCMAKE_INSTALL_PREFIX=' + self.install_dir |
| 390 | ] |
| 391 | |
| 392 | # For each repo this repo depends on, generate a CMake variable |
| 393 | # definitions for "...INSTALL_DIR" that points to that dependent |
| 394 | # repo's install dir. |
| 395 | for d in self.deps: |
| 396 | dep_commit = [r for r in repos if r.name == d['repo_name']] |
| 397 | if len(dep_commit): |
| 398 | cmake_cmd.append('-D{var_name}={install_dir}'.format( |
| 399 | var_name=d['var_name'], |
| 400 | install_dir=dep_commit[0].install_dir)) |
| 401 | |
| 402 | # Add any CMake options |
| 403 | for option in self.cmake_options: |
| 404 | cmake_cmd.append(option) |
| 405 | |
| 406 | # Set build config for single-configuration generators |
| 407 | if platform.system() == 'Linux' or platform.system() == 'Darwin': |
| 408 | cmake_cmd.append('-DCMAKE_BUILD_TYPE={config}'.format( |
| 409 | config=CONFIG_MAP[self._args.config])) |
| 410 | |
| 411 | # Use the CMake -A option to select the platform architecture |
| 412 | # without needing a Visual Studio generator. |
| 413 | if platform.system() == 'Windows': |
| 414 | if self._args.arch == '64' or self._args.arch == 'x64' or self._args.arch == 'win64': |
| 415 | cmake_cmd.append('-A') |
| 416 | cmake_cmd.append('x64') |
| 417 | |
| 418 | if VERBOSE: |
| 419 | print("CMake command: " + " ".join(cmake_cmd)) |
| 420 | |
| 421 | ret_code = subprocess.call(cmake_cmd) |
| 422 | if ret_code != 0: |
| 423 | sys.exit(ret_code) |
| 424 | |
| 425 | def CMakeBuild(self): |
| 426 | """Build CMake command for the build phase and execute it""" |
| 427 | cmake_cmd = ['cmake', '--build', self.build_dir, '--target', 'install'] |
| 428 | if self._args.do_clean: |
| 429 | cmake_cmd.append('--clean-first') |
| 430 | |
| 431 | if platform.system() == 'Windows': |
| 432 | cmake_cmd.append('--config') |
| 433 | cmake_cmd.append(CONFIG_MAP[self._args.config]) |
| 434 | |
| 435 | # Speed up the build. |
| 436 | if platform.system() == 'Linux' or platform.system() == 'Darwin': |
| 437 | cmake_cmd.append('--') |
| 438 | cmake_cmd.append('-j{ncpu}' |
| 439 | .format(ncpu=multiprocessing.cpu_count())) |
| 440 | if platform.system() == 'Windows': |
| 441 | cmake_cmd.append('--') |
| 442 | cmake_cmd.append('/maxcpucount') |
| 443 | |
| 444 | if VERBOSE: |
| 445 | print("CMake command: " + " ".join(cmake_cmd)) |
| 446 | |
| 447 | ret_code = subprocess.call(cmake_cmd) |
| 448 | if ret_code != 0: |
| 449 | sys.exit(ret_code) |
| 450 | |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 451 | def Build(self, repos, repo_dict): |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 452 | """Build the dependent repo""" |
| 453 | print('Building {n} in {d}'.format(n=self.name, d=self.repo_dir)) |
| 454 | print('Build dir = {b}'.format(b=self.build_dir)) |
| 455 | print('Install dir = {i}\n'.format(i=self.install_dir)) |
| 456 | |
| 457 | # Run any prebuild commands |
| 458 | self.PreBuild() |
| 459 | |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 460 | if self.build_step == 'custom': |
| 461 | self.CustomBuild(repo_dict) |
| 462 | return |
| 463 | |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 464 | # Build and execute CMake command for creating build files |
| 465 | self.CMakeConfig(repos) |
| 466 | |
| 467 | # Build and execute CMake command for the build |
| 468 | self.CMakeBuild() |
| 469 | |
| 470 | |
| 471 | def GetGoodRepos(args): |
| 472 | """Returns the latest list of GoodRepo objects. |
| 473 | |
| 474 | The known-good file is expected to be in the same |
| Mark Lobodzinski | ac1043c | 2018-07-19 13:20:46 -0600 | [diff] [blame] | 475 | directory as this script unless overridden by the 'known_good_dir' |
| 476 | parameter. |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 477 | """ |
| Mark Lobodzinski | ac1043c | 2018-07-19 13:20:46 -0600 | [diff] [blame] | 478 | if args.known_good_dir: |
| 479 | known_good_file = os.path.join( os.path.abspath(args.known_good_dir), |
| 480 | KNOWN_GOOD_FILE_NAME) |
| 481 | else: |
| 482 | known_good_file = os.path.join( |
| 483 | os.path.dirname(os.path.abspath(__file__)), KNOWN_GOOD_FILE_NAME) |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 484 | with open(known_good_file) as known_good: |
| 485 | return [ |
| 486 | GoodRepo(repo, args) |
| 487 | for repo in json.loads(known_good.read())['repos'] |
| 488 | ] |
| 489 | |
| 490 | |
| Mark Lobodzinski | ac1043c | 2018-07-19 13:20:46 -0600 | [diff] [blame] | 491 | def GetInstallNames(args): |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 492 | """Returns the install names list. |
| Mark Lobodzinski | ac1043c | 2018-07-19 13:20:46 -0600 | [diff] [blame] | 493 | |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 494 | The known-good file is expected to be in the same |
| Mark Lobodzinski | ac1043c | 2018-07-19 13:20:46 -0600 | [diff] [blame] | 495 | directory as this script unless overridden by the 'known_good_dir' |
| 496 | parameter. |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 497 | """ |
| Mark Lobodzinski | ac1043c | 2018-07-19 13:20:46 -0600 | [diff] [blame] | 498 | if args.known_good_dir: |
| 499 | known_good_file = os.path.join(os.path.abspath(args.known_good_dir), |
| 500 | KNOWN_GOOD_FILE_NAME) |
| 501 | else: |
| 502 | known_good_file = os.path.join( |
| 503 | os.path.dirname(os.path.abspath(__file__)), KNOWN_GOOD_FILE_NAME) |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 504 | with open(known_good_file) as known_good: |
| Mark Lobodzinski | 3fe96b6 | 2018-07-19 14:45:10 -0600 | [diff] [blame] | 505 | install_info = json.loads(known_good.read()) |
| 506 | if install_info.get('install_names'): |
| 507 | return install_info['install_names'] |
| 508 | else: |
| 509 | return None |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 510 | |
| 511 | |
| Mark Lobodzinski | ac1043c | 2018-07-19 13:20:46 -0600 | [diff] [blame] | 512 | def CreateHelper(args, repos, filename): |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 513 | """Create a CMake config helper file. |
| Mark Lobodzinski | ac1043c | 2018-07-19 13:20:46 -0600 | [diff] [blame] | 514 | |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 515 | The helper file is intended to be used with 'cmake -C <file>' |
| 516 | to build this home repo using the dependencies built by this script. |
| 517 | |
| 518 | The install_names dictionary represents the CMake variables used by the |
| 519 | home repo to locate the install dirs of the dependent repos. |
| 520 | This information is baked into the CMake files of the home repo and so |
| 521 | this dictionary is kept with the repo via the json file. |
| 522 | """ |
| Mike Schuchardt | c5ffcb0 | 2018-11-08 16:44:13 -0800 | [diff] [blame] | 523 | def escape(path): |
| 524 | return path.replace('\\', '\\\\') |
| Mark Lobodzinski | ac1043c | 2018-07-19 13:20:46 -0600 | [diff] [blame] | 525 | install_names = GetInstallNames(args) |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 526 | with open(filename, 'w') as helper_file: |
| 527 | for repo in repos: |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 528 | if install_names and repo.name in install_names and repo.on_build_platform: |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 529 | helper_file.write('set({var} "{dir}" CACHE STRING "" FORCE)\n' |
| 530 | .format( |
| 531 | var=install_names[repo.name], |
| Mike Schuchardt | c5ffcb0 | 2018-11-08 16:44:13 -0800 | [diff] [blame] | 532 | dir=escape(repo.install_dir))) |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 533 | |
| 534 | |
| 535 | def main(): |
| 536 | parser = argparse.ArgumentParser( |
| 537 | description='Get and build dependent repos at known-good commits') |
| 538 | parser.add_argument( |
| Mark Lobodzinski | ac1043c | 2018-07-19 13:20:46 -0600 | [diff] [blame] | 539 | '--known_good_dir', |
| 540 | dest='known_good_dir', |
| 541 | help="Specify directory for known_good.json file.") |
| 542 | parser.add_argument( |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 543 | '--dir', |
| 544 | dest='dir', |
| 545 | default='.', |
| 546 | help="Set target directory for repository roots. Default is \'.\'.") |
| 547 | parser.add_argument( |
| 548 | '--ref', |
| 549 | dest='ref', |
| 550 | default='', |
| 551 | help="Override 'commit' with git reference. E.g., 'origin/master'") |
| 552 | parser.add_argument( |
| 553 | '--no-build', |
| 554 | dest='do_build', |
| 555 | action='store_false', |
| 556 | help= |
| 557 | "Clone/update repositories and generate build files without performing compilation", |
| 558 | default=True) |
| 559 | parser.add_argument( |
| 560 | '--clean', |
| 561 | dest='do_clean', |
| 562 | action='store_true', |
| 563 | help="Clean files generated by compiler and linker before building", |
| 564 | default=False) |
| 565 | parser.add_argument( |
| 566 | '--clean-repo', |
| 567 | dest='do_clean_repo', |
| 568 | action='store_true', |
| 569 | help="Delete repository directory before building", |
| 570 | default=False) |
| 571 | parser.add_argument( |
| 572 | '--clean-build', |
| 573 | dest='do_clean_build', |
| 574 | action='store_true', |
| 575 | help="Delete build directory before building", |
| 576 | default=False) |
| 577 | parser.add_argument( |
| 578 | '--clean-install', |
| 579 | dest='do_clean_install', |
| 580 | action='store_true', |
| 581 | help="Delete install directory before building", |
| 582 | default=False) |
| 583 | parser.add_argument( |
| 584 | '--arch', |
| 585 | dest='arch', |
| Tony-LunarG | f445ff1 | 2018-07-24 16:31:20 -0600 | [diff] [blame] | 586 | choices=['32', '64', 'x86', 'x64', 'win32', 'win64'], |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 587 | type=str.lower, |
| 588 | help="Set build files architecture (Windows)", |
| 589 | default='64') |
| 590 | parser.add_argument( |
| 591 | '--config', |
| 592 | dest='config', |
| 593 | choices=['debug', 'release', 'relwithdebinfo', 'minsizerel'], |
| 594 | type=str.lower, |
| 595 | help="Set build files configuration", |
| 596 | default='debug') |
| 597 | |
| 598 | args = parser.parse_args() |
| 599 | save_cwd = os.getcwd() |
| 600 | |
| 601 | # Create working "top" directory if needed |
| 602 | distutils.dir_util.mkpath(args.dir) |
| 603 | abs_top_dir = os.path.abspath(args.dir) |
| 604 | |
| 605 | repos = GetGoodRepos(args) |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 606 | repo_dict = {} |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 607 | |
| 608 | print('Starting builds in {d}'.format(d=abs_top_dir)) |
| 609 | for repo in repos: |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 610 | # If the repo has a platform whitelist, skip the repo |
| 611 | # unless we are building on a whitelisted platform. |
| 612 | if not repo.on_build_platform: |
| 613 | continue |
| 614 | |
| 615 | field_list = ('url', |
| 616 | 'sub_dir', |
| 617 | 'commit', |
| 618 | 'build_dir', |
| 619 | 'install_dir', |
| 620 | 'deps', |
| 621 | 'prebuild', |
| 622 | 'prebuild_linux', |
| 623 | 'prebuild_windows', |
| 624 | 'custom_build', |
| 625 | 'cmake_options', |
| 626 | 'ci_only', |
| 627 | 'build_step', |
| 628 | 'build_platforms', |
| 629 | 'repo_dir', |
| 630 | 'on_build_platform') |
| 631 | repo_dict[repo.name] = {field: getattr(repo, field) for field in field_list}; |
| 632 | |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 633 | # If the repo has a CI whitelist, skip the repo unless |
| 634 | # one of the CI's environment variable is set to true. |
| 635 | if len(repo.ci_only): |
| 636 | do_build = False |
| 637 | for env in repo.ci_only: |
| 638 | if not env in os.environ: |
| 639 | continue |
| 640 | if os.environ[env].lower() == 'true': |
| 641 | do_build = True |
| 642 | break |
| 643 | if not do_build: |
| 644 | continue |
| 645 | |
| 646 | # Clone/update the repository |
| 647 | repo.Checkout() |
| 648 | |
| 649 | # Build the repository |
| Jeremy Kniager | 5e9b7f7 | 2018-08-09 09:36:30 -0600 | [diff] [blame] | 650 | if args.do_build and repo.build_step != 'skip': |
| 651 | repo.Build(repos, repo_dict) |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 652 | |
| 653 | # Need to restore original cwd in order for CreateHelper to find json file |
| 654 | os.chdir(save_cwd) |
| Mark Lobodzinski | ac1043c | 2018-07-19 13:20:46 -0600 | [diff] [blame] | 655 | CreateHelper(args, repos, os.path.join(abs_top_dir, 'helper.cmake')) |
| Karl Schultz | 5ff6f74 | 2018-06-21 17:38:56 -0600 | [diff] [blame] | 656 | |
| 657 | sys.exit(0) |
| 658 | |
| 659 | |
| 660 | if __name__ == '__main__': |
| 661 | main() |