George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
George Burgess IV | bdaea21 | 2022-12-09 11:02:02 -0700 | [diff] [blame] | 2 | # Copyright 2022 The ChromiumOS Authors |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
George Burgess IV | 4196b08 | 2022-11-03 17:10:47 -0600 | [diff] [blame^] | 5 | """Populates the top-level Cargo.toml with all necessary workspace entries. |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 6 | |
George Burgess IV | 4196b08 | 2022-11-03 17:10:47 -0600 | [diff] [blame^] | 7 | Also updates cargo-vet's policy information with all of the crates discovered |
| 8 | in the tree. |
| 9 | """ |
| 10 | |
| 11 | import argparse |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 12 | import logging |
| 13 | import os |
George Burgess IV | 4196b08 | 2022-11-03 17:10:47 -0600 | [diff] [blame^] | 14 | import subprocess |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 15 | import sys |
George Burgess IV | 4196b08 | 2022-11-03 17:10:47 -0600 | [diff] [blame^] | 16 | import textwrap |
| 17 | from pathlib import Path |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 18 | from typing import List |
| 19 | |
George Burgess IV | 4196b08 | 2022-11-03 17:10:47 -0600 | [diff] [blame^] | 20 | import toml |
| 21 | |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 22 | WORKSPACE_FILE_HEADER = """\ |
| 23 | # Copyright 2022 The ChromiumOS Authors. |
| 24 | # Use of this source code is governed by a BSD-style license that can be |
| 25 | # found in the LICENSE file. |
| 26 | # |
| 27 | # !! Autogenerated by `populate-workspace.py`; please don't edit. !! |
| 28 | |
George Burgess IV | 4196b08 | 2022-11-03 17:10:47 -0600 | [diff] [blame^] | 29 | [workspace.metadata.vet] |
| 30 | store = { path = '../cargo-vet' } |
| 31 | |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 32 | """ |
| 33 | |
George Burgess IV | 4196b08 | 2022-11-03 17:10:47 -0600 | [diff] [blame^] | 34 | # Tools which only run on the host. These are subject to less stringent audit |
| 35 | # requirements. |
| 36 | HOST_TOOLS = frozenset( |
| 37 | ( |
| 38 | "bindgen-deps", |
| 39 | "chromeos-dbus-bindings-deps", |
| 40 | "cxxbridge-cmd-deps", |
| 41 | "dbus-codegen-deps", |
| 42 | "factory_installer-deps", |
| 43 | "flashrom_tester-deps", |
| 44 | "prjoxide-deps", |
| 45 | "pyprjoxide-deps", |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 46 | ) |
George Burgess IV | 4196b08 | 2022-11-03 17:10:47 -0600 | [diff] [blame^] | 47 | ) |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 48 | |
George Burgess IV | 4196b08 | 2022-11-03 17:10:47 -0600 | [diff] [blame^] | 49 | # Dependencies that should not be treated as crates.io dependencies. |
| 50 | NON_CRATES_IO_DEPS = ( |
| 51 | "deqp-runner", |
| 52 | "dlib", |
| 53 | "wayland-commons", |
| 54 | "wayland-scanner", |
| 55 | "wayland-server", |
| 56 | "wayland-sys", |
| 57 | ) |
| 58 | |
| 59 | |
| 60 | def update_cargo_vet_info( |
| 61 | projects_dir: Path, projects: List[str], use_relaxed_target_criteria: bool |
| 62 | ): |
| 63 | """Updates cargo-vet's config.toml with requirements for all `projects`.""" |
| 64 | cargo_vet_config = projects_dir.parent / "cargo-vet" / "config.toml" |
| 65 | config = cargo_vet_config.read_text(encoding="utf-8") |
| 66 | |
| 67 | cargo_vet_policy = toml.loads(config).get("policy", ()) |
| 68 | project_names = [] |
| 69 | unseen_io_deps = set( |
| 70 | x for x in NON_CRATES_IO_DEPS if x not in cargo_vet_policy |
| 71 | ) |
| 72 | for project in projects: |
| 73 | cargo_toml = projects_dir / project / "Cargo.toml" |
| 74 | with cargo_toml.open(encoding="utf-8") as f: |
| 75 | project_name = toml.load(f)["package"]["name"] |
| 76 | project_names.append((project, project_name)) |
| 77 | unseen_io_deps.discard(project_name) |
| 78 | |
| 79 | add_projects = [x for x in project_names if x[1] not in cargo_vet_policy] |
| 80 | add_projects += ((None, x) for x in unseen_io_deps) |
| 81 | if not add_projects: |
| 82 | logging.info("No cargo-vet-related updates to make.") |
| 83 | return |
| 84 | |
| 85 | host_safety_criteria = "safe-to-run" |
| 86 | target_safety_criteria = "rule-of-two-safe-to-deploy" |
| 87 | |
| 88 | # As ugly as it is to hand-update a toml file, our toml implementation has |
| 89 | # known bugs that lead to invalid toml in quite a few cases (b/242668603). |
| 90 | # Stick stuff at the end and have `cargo-vet` handle making it pretty. |
| 91 | with cargo_vet_config.open("a", encoding="utf-8") as f: |
| 92 | for project_path, project_name in add_projects: |
| 93 | if project_path: |
| 94 | logging.info( |
| 95 | "Adding entry %s from %s into cargo-vet", |
| 96 | project_path, |
| 97 | project_name, |
| 98 | ) |
| 99 | else: |
| 100 | logging.info( |
| 101 | "Synthesizing entry %s for cargo-vet", |
| 102 | project_name, |
| 103 | ) |
| 104 | |
| 105 | if project_name in HOST_TOOLS: |
| 106 | safety_criteria = host_safety_criteria |
| 107 | note = "Only safe-to-run is required, as this is a host tool." |
| 108 | elif use_relaxed_target_criteria: |
| 109 | safety_criteria = host_safety_criteria |
| 110 | note = "Using relaxed target criteria to help with migration." |
| 111 | else: |
| 112 | safety_criteria = target_safety_criteria |
| 113 | note = None |
| 114 | |
| 115 | f.write("\n") |
| 116 | f.write( |
| 117 | textwrap.dedent( |
| 118 | f"""\ |
| 119 | [policy."{project_name}"] |
| 120 | criteria = ["{safety_criteria}", "crypto-safe"] |
| 121 | dev-criteria = ["{host_safety_criteria}", "crypto-safe"] |
| 122 | """ |
| 123 | ) |
| 124 | ) |
| 125 | if note: |
| 126 | # Only handle these if we have to. |
| 127 | assert "\\" not in note and '"' not in note, note |
| 128 | f.write(f'notes = "{note}"\n') |
| 129 | |
| 130 | if project_name in NON_CRATES_IO_DEPS: |
| 131 | f.write(f"audit-as-crates-io = false\n") |
| 132 | |
| 133 | subprocess.check_call( |
| 134 | ["scripts/cargo-vet.py", "fmt"], cwd=projects_dir.parent |
| 135 | ) |
| 136 | logging.info("Cargo vet info updated.") |
| 137 | |
| 138 | |
| 139 | def find_projects(projects_dir: Path) -> List[str]: |
| 140 | """Returns a list of projects under `projects_dir`. |
| 141 | |
| 142 | This creates src/ directories for any project that does not currently have |
| 143 | one, for convenience. |
| 144 | """ |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 145 | projects = [] |
| 146 | for dir_path, subdirs, files in os.walk(projects_dir): |
George Burgess IV | 7dffc25 | 2022-08-31 14:37:01 -0700 | [diff] [blame] | 147 | if "Cargo.toml" not in files: |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 148 | continue |
| 149 | |
| 150 | dir_path = Path(dir_path) |
| 151 | if dir_path == projects_dir: |
| 152 | continue |
| 153 | |
| 154 | projects.append(dir_path.relative_to(projects_dir)) |
| 155 | |
| 156 | # It's a waste to descend into src directories. |
George Burgess IV | 7dffc25 | 2022-08-31 14:37:01 -0700 | [diff] [blame] | 157 | if "src" in subdirs: |
| 158 | del subdirs[subdirs.index("src")] |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 159 | else: |
| 160 | # ...Though if src/ doesn't exist, Cargo will get confused. |
| 161 | # Synthesize one with a nop lib.rs. |
George Burgess IV | 7dffc25 | 2022-08-31 14:37:01 -0700 | [diff] [blame] | 162 | src = dir_path / "src" |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 163 | src.mkdir() |
George Burgess IV | 7dffc25 | 2022-08-31 14:37:01 -0700 | [diff] [blame] | 164 | (src / "lib.rs").write_bytes(b"") |
| 165 | logging.info("Synthesized src/lib.rs for %s", dir_path) |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 166 | |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 167 | projects.sort() |
George Burgess IV | 4196b08 | 2022-11-03 17:10:47 -0600 | [diff] [blame^] | 168 | return projects |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 169 | |
George Burgess IV | 4196b08 | 2022-11-03 17:10:47 -0600 | [diff] [blame^] | 170 | |
| 171 | def get_parser() -> argparse.ArgumentParser: |
| 172 | """Gets the arg parser for this script.""" |
| 173 | parser = argparse.ArgumentParser( |
| 174 | description=__doc__, |
| 175 | formatter_class=argparse.RawDescriptionHelpFormatter, |
| 176 | ) |
| 177 | parser.add_argument( |
| 178 | "--use-strict-target-criteria", |
| 179 | action="store_true", |
| 180 | help="Require rule-of-two-safe-to-deploy for target crates.", |
| 181 | ) |
| 182 | return parser |
| 183 | |
| 184 | |
| 185 | def main(argv: List[str]): |
| 186 | """Main function.""" |
| 187 | opts = get_parser().parse_args(argv) |
| 188 | logging.basicConfig( |
| 189 | format=">> %(asctime)s: %(levelname)s: %(filename)s:%(lineno)d: " |
| 190 | "%(message)s", |
| 191 | level=logging.INFO, |
| 192 | ) |
| 193 | |
| 194 | projects_dir = Path(__file__).resolve().parent |
| 195 | projects = find_projects(projects_dir) |
| 196 | assert projects, f"No projects found under {projects_dir}" |
George Burgess IV | 7dffc25 | 2022-08-31 14:37:01 -0700 | [diff] [blame] | 197 | logging.info("Identified %d projects", len(projects)) |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 198 | |
George Burgess IV | 7dffc25 | 2022-08-31 14:37:01 -0700 | [diff] [blame] | 199 | workspace_toml_file = projects_dir / "Cargo.toml" |
| 200 | with workspace_toml_file.open("w", encoding="utf-8") as f: |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 201 | f.write(WORKSPACE_FILE_HEADER) |
George Burgess IV | 7dffc25 | 2022-08-31 14:37:01 -0700 | [diff] [blame] | 202 | # The `toml` crate writes this as a massive line, which is hard to |
| 203 | # read. Since this is simple to write, write it directly. |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 204 | # TODO(b/242668603): find a toml crate with prettier formatting |
George Burgess IV | bdaea21 | 2022-12-09 11:02:02 -0700 | [diff] [blame] | 205 | f.write("[workspace]\nmembers = [\n") |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 206 | for project in projects: |
| 207 | project = str(project) |
George Burgess IV | 7dffc25 | 2022-08-31 14:37:01 -0700 | [diff] [blame] | 208 | assert '"' not in project and "\\" not in project, project |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 209 | f.write(f' "{project}",\n') |
George Burgess IV | 7dffc25 | 2022-08-31 14:37:01 -0700 | [diff] [blame] | 210 | f.write("]") |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 211 | |
George Burgess IV | 7dffc25 | 2022-08-31 14:37:01 -0700 | [diff] [blame] | 212 | logging.info("Workspace Cargo.toml successfully written.") |
George Burgess IV | 4196b08 | 2022-11-03 17:10:47 -0600 | [diff] [blame^] | 213 | update_cargo_vet_info( |
| 214 | projects_dir, |
| 215 | projects, |
| 216 | use_relaxed_target_criteria=not opts.use_strict_target_criteria, |
| 217 | ) |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 218 | |
| 219 | |
George Burgess IV | 7dffc25 | 2022-08-31 14:37:01 -0700 | [diff] [blame] | 220 | if __name__ == "__main__": |
George Burgess IV | 18af563 | 2022-08-30 14:10:53 -0700 | [diff] [blame] | 221 | sys.exit(main(sys.argv[1:])) |