Haiyang Pan | 7760b29 | 2021-02-12 23:16:32 | [diff] [blame] | 1 | # CIPD and 3pp for chromium dependencies |
| 2 | |
| 3 | [TOC] |
| 4 | |
| 5 | ## What is CIPD? |
| 6 | * CIPD stands for "Chrome Infrastructure Package Deployment". |
| 7 | * Its code and docs [live within the luci-go project][CIPD]. |
| 8 | * Chromium uses CIPD to avoid checking large binary files into git, which git |
| 9 | does not handle well. |
| 10 | * gclient supports CIPD packages in the same way as git repositories. They are |
| 11 | specified in [DEPS] and updated via `gclient sync`. |
| 12 | * You can [browse Chromium's CIPD repository][browse] online. |
| 13 | |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 14 | [CIPD]: https://chromium.googlesource.com/infra/luci/luci-go/+/main/cipd/README.md |
Haiyang Pan | 7760b29 | 2021-02-12 23:16:32 | [diff] [blame] | 15 | [DEPS]: /DEPS |
| 16 | [browse]: https://chrome-infra-packages.appspot.com/p/chromium |
| 17 | |
| 18 | ## What is 3pp? |
| 19 | * 3pp stands for "Third Party Packages" which allows uniform cross-compiliation, |
| 20 | version tracking and archival for third-party software packages for |
| 21 | distribution via CIPD. |
| 22 | * The code and docs [live within the recipe module support_3pp][support_3pp]. |
| 23 | * By specifying a 3pp package, you can define how to build certain artifacts and |
| 24 | where to upload to CIPD. Then our packagers will do the rest for you. |
| 25 | |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 26 | [support_3pp]: https://chromium.googlesource.com/infra/infra/+/main/recipes/README.recipes.md#recipe_modules-support_3pp |
Haiyang Pan | 7760b29 | 2021-02-12 23:16:32 | [diff] [blame] | 27 | |
| 28 | ## Why use CIPD & 3pp?: |
| 29 | |
| 30 | * CIPD is our solution to storing large binary blobs directly in git, which git |
| 31 | is not good at. |
| 32 | * Building these packages with 3pp is beneficial because: |
| 33 | * It makes how each binary file was created clear and verifiable. |
| 34 | * It avoids the need for maintaining a list of ACLs for uploading to CIPD. |
| 35 | |
| 36 | ## Adding a new 3pp package |
| 37 | |
| 38 | ### 1. Set up a new directory for your dependency |
| 39 | |
| 40 | You'll first want somewhere in the repository in which your dependency will |
| 41 | live. For third-party dependencies, this should typically be a subdirectory |
| 42 | of `//third_party`. You'll need to add the same set of things to that |
| 43 | directory that you'd add for a non-CIPD dependency -- OWNERS, README.chromium, |
| 44 | etc. |
| 45 | |
| 46 | For example, if you want to add a package named `sample_cipd_dep`, you might |
| 47 | create the following: |
| 48 | |
| 49 | ``` |
| 50 | third_party/ |
| 51 | sample_cipd_dep/ |
| 52 | LICENSE |
| 53 | OWNERS |
| 54 | README.chromium |
| 55 | ``` |
| 56 | |
| 57 | For more on third-party dependencies, see [adding_to_third_party.md]. |
| 58 | |
| 59 | [adding_to_third_party.md]: /docs/adding_to_third_party.md |
| 60 | |
| 61 | ### 2. Set up the 3pp subdirectory |
| 62 | |
| 63 | The 3pp subdirectory will store all the 3pp related files, including a 3pp spec |
| 64 | (`3pp.pb`), as well as scripts, patches and/or tools to build the software |
Joanna Wang | f85e1bc | 2024-08-14 21:35:46 | [diff] [blame] | 65 | from source. It should be placed directly under the directory path that matches |
| 66 | the desired name of the cipd package. |
Haiyang Pan | 7760b29 | 2021-02-12 23:16:32 | [diff] [blame] | 67 | |
| 68 | Staying with the example from above, the `sample_cipd_dep` directory may be |
| 69 | like the following. |
| 70 | |
| 71 | > Note that among the files in 3pp subdirectory, the `3pp.pb` is always |
| 72 | > required. The rest are optional, depending on how the `3pp.pb` is specified. |
| 73 | |
| 74 | ``` |
| 75 | third_party/ |
| 76 | sample_cipd_dep/ |
| 77 | LICENSE |
| 78 | OWNERS |
| 79 | README.chromium |
| 80 | 3pp/ |
| 81 | 3pp.pb # REQUIRED |
| 82 | bootstrap.py |
| 83 | fetch.py |
| 84 | install.sh |
| 85 | install_win.sh |
| 86 | patches/ |
| 87 | 0001-foo.patch |
| 88 | ``` |
| 89 | |
| 90 | #### 2.1 The file `3pp.pb` (Required) |
| 91 | |
| 92 | `3pp.pb` is a text proto specified by the **[`spec.proto`]** schema. It is broken up |
| 93 | into two main sections: |
| 94 | * `create`: allows you to specify how the package software gets created, and |
| 95 | allows specifying differences in how it's fetched/built/tested on a |
| 96 | per-target basis. See [here][doc_create] for more details. |
| 97 | * `upload`: contains some details on how the final result gets uploaded to CIPD. |
| 98 | See [here][doc_upload] for more details. |
| 99 | |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 100 | [`spec.proto`]: https://chromium.googlesource.com/infra/infra/+/main/recipes/recipe_modules/support_3pp/spec.proto |
| 101 | [doc_create]: https://chromium.googlesource.com/infra/infra/+/main/recipes/README.recipes.md#creation-stages |
| 102 | [doc_upload]: https://chromium.googlesource.com/infra/infra/+/main/recipes/README.recipes.md#upload |
Haiyang Pan | 7760b29 | 2021-02-12 23:16:32 | [diff] [blame] | 103 | |
| 104 | Staying with the example from above, the file `sample_cipd_dep/3pp/3pp.pb` may |
| 105 | be like the following: |
| 106 | |
| 107 | ``` |
| 108 | create { |
| 109 | source { |
| 110 | url { |
| 111 | download_url: "https://some_url_link/foo.zip" |
| 112 | version: "1.0.0" |
| 113 | extension: ".zip" |
| 114 | } |
| 115 | patch_version: "cr0" |
| 116 | unpack_archive: true |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | upload { |
| 121 | pkg_prefix: "tools" |
| 122 | universal: true |
| 123 | } |
| 124 | ``` |
| 125 | |
| 126 | While the above example could meet most of the use case, `3pp.pb` is capable of |
| 127 | handling more complicated use case like the following: |
| 128 | |
| 129 | ``` |
| 130 | # create section that is shared by linux-.* and mac-.* platforms |
| 131 | create { |
| 132 | platform_re: "linux-.*|mac-.*" |
| 133 | source { |
| 134 | git { |
| 135 | repo: "<one_git_repo>" |
| 136 | tag_pattern: "v%s", |
| 137 | |
| 138 | # Fixed to 3.8.x for now. |
| 139 | version_restriction: { op: LT val: "3.9a0"} |
| 140 | } |
| 141 | patch_dir: "patches" |
| 142 | } |
| 143 | build { |
| 144 | # Can also leave as blank since the script name defaults to "install.sh" |
| 145 | install: "install.sh" |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | # create section that is specific to linux-.* platforms |
| 150 | create { |
| 151 | platform_re: "linux-.*" |
| 152 | build { |
| 153 | dep: "<dep_foo>" |
| 154 | dep: "<dep_bar>" |
| 155 | |
| 156 | tool: "<tool_foo>" |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | # create section that is specific to linux-arm.* and linux-mips.* platforms |
| 161 | create { |
| 162 | platform_re: "linux-arm.*|linux-mips.*" |
| 163 | build { |
| 164 | tool: "<tool_bar>" |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | # create section that is specific to windows-* |
| 169 | create { |
| 170 | platform_re: "windows-.*" |
| 171 | source { script { name: "fetch.py" } } |
| 172 | build { |
| 173 | install: "install_win.sh" |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | upload { pkg_prefix: "tools" } |
| 178 | ``` |
| 179 | |
| 180 | #### 2.2 The file `install.sh` (Optional) |
| 181 | |
| 182 | When the `build` message is specified in `3pp.pb`, the file specified in |
| 183 | "build.install" (Default to "install.sh") will be run to transform the source |
| 184 | into the built product. |
| 185 | |
| 186 | Staying with the example from above, the file `sample_cipd_dep/3pp/install.sh` |
| 187 | may be like the following. |
| 188 | |
| 189 | > Note that during the build stage, the 3pp directory and all its dependent 3pp |
| 190 | > directories (i.e. the `tool` and `dep` from the `build` message in `3pp.pb`) |
| 191 | > will be [copied to a different directory]. So commands in `install.sh` should |
| 192 | > not refer to files that are outside of these directories. |
| 193 | |
| 194 | [copied to a different directory]: https://chromium.googlesource.com/infra/infra/+/53fd7d1eda2010009ed00fdc1a7b59fe5034ae0c/recipes/recipe_modules/support_3pp/source.py#246 |
| 195 | |
| 196 | ``` |
| 197 | #!/bin/bash |
Avi Drissman | 7b017a99 | 2022-09-07 15:50:38 | [diff] [blame] | 198 | # Copyright 2021 The Chromium Authors |
Haiyang Pan | 7760b29 | 2021-02-12 23:16:32 | [diff] [blame] | 199 | # Use of this source code is governed by a BSD-style license that can be |
| 200 | # found in the LICENSE file. |
| 201 | |
| 202 | set -e |
| 203 | set -x |
| 204 | set -o pipefail |
| 205 | |
| 206 | # An auto-created directory whose content will ultimately be uploaded to CIPD. |
| 207 | # So the commands below should output the built product to this directory. |
| 208 | PREFIX="$1" |
| 209 | |
| 210 | # Commands to transform the source into the built product and move it to $PREFIX |
| 211 | ./configure |
| 212 | make install |
| 213 | |
| 214 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" |
| 215 | cp -a bin_foo "$SCRIPT_DIR/bootstrap.py" "$PREFIX" |
| 216 | ``` |
| 217 | |
| 218 | #### 2.3 The file `fetch.py` (Optional) |
| 219 | |
| 220 | When specifying the `source` in 3pp.pb, it is possible to use a custom catch-all |
| 221 | script to probe for the latest version and obtain the latest sources. A simple |
| 222 | example can be like the following: |
| 223 | |
Haiyang Pan | 1930245 | 2023-03-03 22:50:49 | [diff] [blame] | 224 | > Note that this python script should be **python3-compatible**. |
| 225 | |
Haiyang Pan | 7760b29 | 2021-02-12 23:16:32 | [diff] [blame] | 226 | ``` |
Haiyang Pan | 1930245 | 2023-03-03 22:50:49 | [diff] [blame] | 227 | #!/usr/bin/env python3 |
| 228 | # Copyright 2023 The Chromium Authors |
Haiyang Pan | 7760b29 | 2021-02-12 23:16:32 | [diff] [blame] | 229 | # Use of this source code is governed by a BSD-style license that can be |
| 230 | # found in the LICENSE file. |
| 231 | |
Haiyang Pan | 7760b29 | 2021-02-12 23:16:32 | [diff] [blame] | 232 | import argparse |
| 233 | import json |
| 234 | import os |
| 235 | import urllib |
| 236 | |
| 237 | |
| 238 | def do_latest(): |
| 239 | print(urllib.urlopen('some_url/master/VERSION').read().strip()) |
| 240 | |
| 241 | |
| 242 | def get_download_url(version, platform): |
| 243 | target_os, target_arch = platform.split('-') |
| 244 | ext = '.zip' if target_os == 'windows' else '.tar.gz' |
| 245 | partial_manifest = { |
| 246 | 'url': ['download_url1', 'download_url2'], |
| 247 | 'ext': ext, |
| 248 | } |
| 249 | print(json.dumps(partial_manifest)) |
| 250 | |
| 251 | |
| 252 | def main(): |
| 253 | ap = argparse.ArgumentParser() |
| 254 | sub = ap.add_subparsers() |
| 255 | |
| 256 | latest = sub.add_parser("latest") |
| 257 | latest.set_defaults(func=lambda _opts: do_latest()) |
| 258 | |
| 259 | download = sub.add_parser("get_url") |
| 260 | download.set_defaults( |
| 261 | func=lambda _opts: get_download_url( |
| 262 | os.environ['_3PP_VERSION'], os.environ['_3PP_PLATFORM'] |
| 263 | ) |
| 264 | ) |
| 265 | |
| 266 | opts = ap.parse_args() |
| 267 | opts.func(opts) |
| 268 | |
| 269 | |
| 270 | if __name__ == '__main__': |
| 271 | main() |
| 272 | ``` |
| 273 | |
| 274 | ### 3. Add "3pp" subdirectory to `chromium/src` repo |
| 275 | |
| 276 | #### 3pp CQ builders (Presubmit) |
| 277 | |
| 278 | The following are the optional CQ builders to run the presubmit check for CLs |
| 279 | that have the directory "3pp" in the patchset. |
| 280 | |
| 281 | * [3pp-linux-amd64-packager](https://ci.chromium.org/p/chromium/builders/try/3pp-linux-amd64-packager): |
| 282 | For builds on the linux-amd64 platform or universal builds (e.g. to be used by |
| 283 | all platforms) |
| 284 | |
| 285 | #### 3pp CI builders (Postsubmit) |
| 286 | |
| 287 | Once the CLs pass the CQ and get landed, the following CI builders will |
| 288 | periodically build all the 3pp packages that match the given platforms and |
| 289 | upload any new results to CIPD. |
| 290 | |
| 291 | * [3pp-linux-amd64-packager](https://ci.chromium.org/p/chromium/builders/ci/3pp-linux-amd64-packager): |
| 292 | For builds on the linux-amd64 platform or universal builds (e.g. to be used by |
| 293 | all platforms) |
| 294 | |
| 295 | ### 4. Add your CIPD package to DEPS |
| 296 | |
| 297 | Once your CIPD package is created by the 3pp CI builders, you can add it to |
| 298 | `DEPS` by adding an entry of the following form to the `deps` dict: |
| 299 | |
| 300 | ``` |
| 301 | deps = { |
| 302 | # ... |
| 303 | |
| 304 | # This is the installation directory. |
| 305 | 'src/third_party/sample_cipd_dep': { |
| 306 | |
| 307 | # In this example, we're only installing one package in this location, |
| 308 | # but installing multiple package in a location is supported. |
| 309 | 'packages': [ |
| 310 | { |
| 311 | 'package': 'chromium/third_party/sample_cipd_dep', |
| 312 | 'version': 'TX7HeY1_1JLwFVx-xiETOpT8YK4W5CbyO26SpmaMA0IC', |
| 313 | }, |
| 314 | ], |
| 315 | |
| 316 | # As with git-based DEPS entries, 'condition' is optional. |
| 317 | 'condition': 'checkout_android', |
| 318 | 'dep_type': 'cipd', |
| 319 | }, |
| 320 | |
| 321 | # ... |
| 322 | } |
| 323 | ``` |
| 324 | |
| 325 | This will result in CIPD package `chromium/third_party/sample_cipd_dep` at |
| 326 | `TX7HeY1_1JLwFVx-xiETOpT8YK4W5CbyO26SpmaMA0IC` being installed in |
| 327 | `src/third_party/sample_cipd_dep` (relative to the gclient root directory). |
| 328 | |
| 329 | ## Updating a CIPD dependency |
| 330 | |
| 331 | To modify a CIPD dependency, follow steps 2 and 3 above, then modify the |
| 332 | version listed in DEPS. |
| 333 | |
| 334 | ## Miscellaneous |
| 335 | |
| 336 | ### Create a cipd.yaml file in the old way |
| 337 | |
| 338 | While it is strongly suggested to use 3pp infrastructure, there are existing flows |
| 339 | that create a cipd.yaml file by a GN template or a script, and upload it to CIPD |
| 340 | by builders with custom recipes. |
| 341 | |
| 342 | Examples are: |
| 343 | * [android-androidx-packager](https://ci.chromium.org/p/chromium/builders/ci/android-androidx-packager) |
| 344 | * [android-sdk-packager](https://ci.chromium.org/p/chromium/builders/ci/android-sdk-packager) |
| 345 | |
| 346 | #### Generating cipd.yaml via GN Template: |
| 347 | The `cipd_package_definition` template in [build/cipd/cipd.gni] can be used to |
| 348 | create the yaml definition as part of Chromium's normal build process. Declare |
| 349 | a target like: |
| 350 | ``` |
| 351 | cipd_package_definition("my_cipd_package") { |
| 352 | package = "path/to/cipd/package" |
| 353 | description = "Prebuilt test binary." |
| 354 | install_mode = "copy" |
| 355 | deps = [ "//path/to:test_binary_target" ] |
| 356 | sources = [ "//path/to:test_binary_file" ] |
| 357 | } |
| 358 | ``` |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 359 | [build/cipd/cipd.gni]: https://source.chromium.org/chromium/chromium/src/+/main:build/cipd/cipd.gni |
Haiyang Pan | 7760b29 | 2021-02-12 23:16:32 | [diff] [blame] | 360 | |
| 361 | ### Permissions in CIPD |
| 362 | |
| 363 | You can check a package's ACLs with `cipd acl-list`: |
| 364 | |
| 365 | ``` |
| 366 | $ cipd acl-list chromium/third_party/sample_cipd_dep |
| 367 | ... |
| 368 | ``` |
| 369 | |
| 370 | Permissions in CIPD are handled hierarchically. You can check entries higher |
| 371 | in the package hierarchy with `cipd acl-list`, too: |
| 372 | |
| 373 | ``` |
| 374 | $ cipd acl-list chromium |
| 375 | ... |
| 376 | ``` |
| 377 | |
| 378 | By default, [cria/project-chromium-cipd-owners][cria] own all CIPD packages |
| 379 | under `chromium/`. If you're adding a package, talk to one of them. |
| 380 | |
| 381 | To obtain write access to a new package, ask an owner to run: |
| 382 | |
| 383 | ``` |
| 384 | $ cipd acl-edit chromium/third_party/sample_cipd_dep -owner user:[email protected] |
| 385 | ``` |
| 386 | |
| 387 | [cria]: https://chrome-infra-auth.appspot.com/auth/groups/project-chromium-cipd-owners |
| 388 | |
| 389 | ## Troubleshooting |
| 390 | |
| 391 | - **A file maintained by CIPD is missing, and gclient sync doesn't recreate it.** |
| 392 | |
| 393 | CIPD currently caches installation state. Modifying packages managed by CIPD |
| 394 | will invalidate this cache in a way that CIPD doesn't detect - i.e., CIPD will |
| 395 | assume that anything it installed is still installed, even if you deleted it. |
| 396 | To clear the cache and force a full reinstallation, delete your |
| 397 | `$GCLIENT_ROOT/.cipd` directory. |
| 398 | |
| 399 | Note that there is a [bug](https://crbug.com/1176408) on file where |
| 400 | `gclient sync` does not reset CIPD entries that are changed locally. |