1 //! Checks the licenses of third-party dependencies.
2
3 use cargo_metadata::{DepKindInfo, Metadata, Package, PackageId};
4 use std::collections::HashSet;
5 use std::path::Path;
6
7 /// These are licenses that are allowed for all crates, including the runtime,
8 /// rustc, tools, etc.
9 #[rustfmt::skip]
10 const LICENSES: &[&str] = &[
11 // tidy-alphabetical-start
12 "(MIT OR Apache-2.0) AND Unicode-DFS-2016", // unicode_ident
13 "0BSD OR MIT OR Apache-2.0", // adler license
14 "0BSD",
15 "Apache-2.0 / MIT",
16 "Apache-2.0 OR MIT",
17 "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", // wasi license
18 "Apache-2.0/MIT",
19 "ISC",
20 "MIT / Apache-2.0",
21 "MIT OR Apache-2.0 OR Zlib", // tinyvec_macros
22 "MIT OR Apache-2.0",
23 "MIT OR Zlib OR Apache-2.0", // miniz_oxide
24 "MIT",
25 "MIT/Apache-2.0",
26 "Unicode-DFS-2016", // tinystr and icu4x
27 "Unlicense OR MIT",
28 "Unlicense/MIT",
29 "Zlib OR Apache-2.0 OR MIT", // tinyvec
30 // tidy-alphabetical-end
31 ];
32
33 /// These are exceptions to Rust's permissive licensing policy, and
34 /// should be considered bugs. Exceptions are only allowed in Rust
35 /// tooling. It is _crucial_ that no exception crates be dependencies
36 /// of the Rust runtime (std/test).
37 #[rustfmt::skip]
38 const EXCEPTIONS: &[(&str, &str)] = &[
39 // tidy-alphabetical-start
40 ("ar_archive_writer", "Apache-2.0 WITH LLVM-exception"), // rustc
41 ("colored", "MPL-2.0"), // rustfmt
42 ("dissimilar", "Apache-2.0"), // rustdoc, rustc_lexer (few tests) via expect-test, (dev deps)
43 ("fluent-langneg", "Apache-2.0"), // rustc (fluent translations)
44 ("fortanix-sgx-abi", "MPL-2.0"), // libstd but only for `sgx` target. FIXME: this dependency violates the documentation comment above.
45 ("instant", "BSD-3-Clause"), // rustc_driver/tracing-subscriber/parking_lot
46 ("mdbook", "MPL-2.0"), // mdbook
47 ("ryu", "Apache-2.0 OR BSL-1.0"), // cargo/... (because of serde)
48 ("self_cell", "Apache-2.0"), // rustc (fluent translations)
49 ("snap", "BSD-3-Clause"), // rustc
50 // tidy-alphabetical-end
51 ];
52
53 const EXCEPTIONS_CARGO: &[(&str, &str)] = &[
54 // tidy-alphabetical-start
55 ("bitmaps", "MPL-2.0+"),
56 ("bytesize", "Apache-2.0"),
57 ("dunce", "CC0-1.0 OR MIT-0 OR Apache-2.0"),
58 ("fiat-crypto", "MIT OR Apache-2.0 OR BSD-1-Clause"),
59 ("im-rc", "MPL-2.0+"),
60 ("imara-diff", "Apache-2.0"),
61 ("instant", "BSD-3-Clause"),
62 ("normalize-line-endings", "Apache-2.0"),
63 ("openssl", "Apache-2.0"),
64 ("ryu", "Apache-2.0 OR BSL-1.0"),
65 ("sha1_smol", "BSD-3-Clause"),
66 ("similar", "Apache-2.0"),
67 ("sized-chunks", "MPL-2.0+"),
68 ("subtle", "BSD-3-Clause"),
69 ("unicode-bom", "Apache-2.0"),
70 // tidy-alphabetical-end
71 ];
72
73 const EXCEPTIONS_CRANELIFT: &[(&str, &str)] = &[
74 // tidy-alphabetical-start
75 ("cranelift-bforest", "Apache-2.0 WITH LLVM-exception"),
76 ("cranelift-codegen", "Apache-2.0 WITH LLVM-exception"),
77 ("cranelift-codegen-meta", "Apache-2.0 WITH LLVM-exception"),
78 ("cranelift-codegen-shared", "Apache-2.0 WITH LLVM-exception"),
79 ("cranelift-control", "Apache-2.0 WITH LLVM-exception"),
80 ("cranelift-entity", "Apache-2.0 WITH LLVM-exception"),
81 ("cranelift-frontend", "Apache-2.0 WITH LLVM-exception"),
82 ("cranelift-isle", "Apache-2.0 WITH LLVM-exception"),
83 ("cranelift-jit", "Apache-2.0 WITH LLVM-exception"),
84 ("cranelift-module", "Apache-2.0 WITH LLVM-exception"),
85 ("cranelift-native", "Apache-2.0 WITH LLVM-exception"),
86 ("cranelift-object", "Apache-2.0 WITH LLVM-exception"),
87 ("mach", "BSD-2-Clause"),
88 ("regalloc2", "Apache-2.0 WITH LLVM-exception"),
89 ("target-lexicon", "Apache-2.0 WITH LLVM-exception"),
90 ("wasmtime-jit-icache-coherence", "Apache-2.0 WITH LLVM-exception"),
91 // tidy-alphabetical-end
92 ];
93
94 const EXCEPTIONS_BOOTSTRAP: &[(&str, &str)] = &[
95 ("ryu", "Apache-2.0 OR BSL-1.0"), // through serde
96 ];
97
98 /// These are the root crates that are part of the runtime. The licenses for
99 /// these and all their dependencies *must not* be in the exception list.
100 const RUNTIME_CRATES: &[&str] = &["std", "core", "alloc", "test", "panic_abort", "panic_unwind"];
101
102 const PERMITTED_DEPS_LOCATION: &str = concat!(file!(), ":", line!());
103
104 /// Crates rustc is allowed to depend on. Avoid adding to the list if possible.
105 ///
106 /// This list is here to provide a speed-bump to adding a new dependency to
107 /// rustc. Please check with the compiler team before adding an entry.
108 const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
109 // tidy-alphabetical-start
110 "addr2line",
111 "adler",
112 "ahash",
113 "aho-corasick",
114 "allocator-api2", // FIXME: only appears in Cargo.lock due to https://github.com/rust-lang/cargo/issues/10801
115 "annotate-snippets",
116 "ar_archive_writer",
117 "arrayvec",
118 "atty",
119 "autocfg",
120 "bitflags",
121 "block-buffer",
122 "byteorder", // via ruzstd in object in thorin-dwp
123 "cc",
124 "cfg-if",
125 "compiler_builtins",
126 "convert_case", // dependency of derive_more
127 "cpufeatures",
128 "crc32fast",
129 "crossbeam-channel",
130 "crossbeam-deque",
131 "crossbeam-epoch",
132 "crossbeam-utils",
133 "crypto-common",
134 "cstr",
135 "datafrog",
136 "derive_more",
137 "digest",
138 "displaydoc",
139 "dissimilar",
140 "dlmalloc",
141 "either",
142 "elsa",
143 "ena",
144 "equivalent",
145 "expect-test",
146 "fallible-iterator", // dependency of `thorin`
147 "fastrand",
148 "field-offset",
149 "flate2",
150 "fluent-bundle",
151 "fluent-langneg",
152 "fluent-syntax",
153 "fortanix-sgx-abi",
154 "generic-array",
155 "getopts",
156 "getrandom",
157 "gimli",
158 "gsgdt",
159 "hashbrown",
160 "hermit-abi",
161 "icu_list",
162 "icu_locid",
163 "icu_provider",
164 "icu_provider_adapters",
165 "icu_provider_macros",
166 "indexmap",
167 "instant",
168 "intl-memoizer",
169 "intl_pluralrules",
170 "io-lifetimes",
171 "itertools",
172 "itoa",
173 "jobserver",
174 "lazy_static",
175 "libc",
176 "libloading",
177 "linux-raw-sys",
178 "litemap",
179 "lock_api",
180 "log",
181 "matchers",
182 "md-5",
183 "measureme",
184 "memchr",
185 "memmap2",
186 "memoffset",
187 "miniz_oxide",
188 "nu-ansi-term",
189 "num_cpus",
190 "object",
191 "odht",
192 "once_cell",
193 "overload",
194 "parking_lot",
195 "parking_lot_core",
196 "pathdiff",
197 "perf-event-open-sys",
198 "pin-project-lite",
199 "polonius-engine",
200 "ppv-lite86",
201 "proc-macro-hack",
202 "proc-macro2",
203 "psm",
204 "pulldown-cmark",
205 "punycode",
206 "quote",
207 "rand",
208 "rand_chacha",
209 "rand_core",
210 "rand_xorshift",
211 "rand_xoshiro",
212 "redox_syscall",
213 "regex",
214 "regex-automata",
215 "regex-syntax",
216 "rustc-demangle",
217 "rustc-hash",
218 "rustc-rayon",
219 "rustc-rayon-core",
220 "rustc_version",
221 "rustix",
222 "ruzstd", // via object in thorin-dwp
223 "ryu",
224 "scoped-tls",
225 "scopeguard",
226 "self_cell",
227 "semver",
228 "serde",
229 "serde_derive",
230 "serde_json",
231 "sha1",
232 "sha2",
233 "sharded-slab",
234 "smallvec",
235 "snap",
236 "stable_deref_trait",
237 "stacker",
238 "static_assertions",
239 "syn",
240 "synstructure",
241 "tempfile",
242 "termcolor",
243 "termize",
244 "thin-vec",
245 "thiserror",
246 "thiserror-impl",
247 "thorin-dwp",
248 "thread_local",
249 "tinystr",
250 "tinyvec",
251 "tinyvec_macros",
252 "tracing",
253 "tracing-attributes",
254 "tracing-core",
255 "tracing-log",
256 "tracing-subscriber",
257 "tracing-tree",
258 "twox-hash",
259 "type-map",
260 "typenum",
261 "unic-char-property",
262 "unic-char-range",
263 "unic-common",
264 "unic-emoji-char",
265 "unic-langid",
266 "unic-langid-impl",
267 "unic-langid-macros",
268 "unic-langid-macros-impl",
269 "unic-ucd-version",
270 "unicase",
271 "unicode-ident",
272 "unicode-normalization",
273 "unicode-script",
274 "unicode-security",
275 "unicode-width",
276 "unicode-xid",
277 "valuable",
278 "version_check",
279 "wasi",
280 "winapi",
281 "winapi-i686-pc-windows-gnu",
282 "winapi-util",
283 "winapi-x86_64-pc-windows-gnu",
284 "windows",
285 "windows-sys",
286 "windows-targets",
287 "windows_aarch64_gnullvm",
288 "windows_aarch64_msvc",
289 "windows_i686_gnu",
290 "windows_i686_msvc",
291 "windows_x86_64_gnu",
292 "windows_x86_64_gnullvm",
293 "windows_x86_64_msvc",
294 "writeable",
295 "yansi-term", // this is a false-positive: it's only used by rustfmt, but because it's enabled through a feature, tidy thinks it's used by rustc as well.
296 "yoke",
297 "yoke-derive",
298 "zerofrom",
299 "zerofrom-derive",
300 "zerovec",
301 "zerovec-derive",
302 // tidy-alphabetical-end
303 ];
304
305 const PERMITTED_CRANELIFT_DEPENDENCIES: &[&str] = &[
306 // tidy-alphabetical-start
307 "ahash",
308 "anyhow",
309 "arbitrary",
310 "autocfg",
311 "bitflags",
312 "bumpalo",
313 "cfg-if",
314 "cranelift-bforest",
315 "cranelift-codegen",
316 "cranelift-codegen-meta",
317 "cranelift-codegen-shared",
318 "cranelift-control",
319 "cranelift-entity",
320 "cranelift-frontend",
321 "cranelift-isle",
322 "cranelift-jit",
323 "cranelift-module",
324 "cranelift-native",
325 "cranelift-object",
326 "crc32fast",
327 "fallible-iterator",
328 "gimli",
329 "hashbrown",
330 "indexmap",
331 "libc",
332 "libloading",
333 "log",
334 "mach",
335 "memchr",
336 "object",
337 "regalloc2",
338 "region",
339 "rustc-hash",
340 "slice-group-by",
341 "smallvec",
342 "stable_deref_trait",
343 "target-lexicon",
344 "version_check",
345 "wasmtime-jit-icache-coherence",
346 "winapi",
347 "winapi-i686-pc-windows-gnu",
348 "winapi-x86_64-pc-windows-gnu",
349 "windows-sys",
350 "windows-targets",
351 "windows_aarch64_gnullvm",
352 "windows_aarch64_msvc",
353 "windows_i686_gnu",
354 "windows_i686_msvc",
355 "windows_x86_64_gnu",
356 "windows_x86_64_gnullvm",
357 "windows_x86_64_msvc",
358 // tidy-alphabetical-end
359 ];
360
361 /// Dependency checks.
362 ///
363 /// `root` is path to the directory with the root `Cargo.toml` (for the workspace). `cargo` is path
364 /// to the cargo executable.
check(root: &Path, cargo: &Path, bad: &mut bool)365 pub fn check(root: &Path, cargo: &Path, bad: &mut bool) {
366 let mut cmd = cargo_metadata::MetadataCommand::new();
367 cmd.cargo_path(cargo)
368 .manifest_path(root.join("Cargo.toml"))
369 .features(cargo_metadata::CargoOpt::AllFeatures);
370 let metadata = t!(cmd.exec());
371 let runtime_ids = compute_runtime_crates(&metadata);
372 check_license_exceptions(&metadata, EXCEPTIONS, runtime_ids, bad);
373 check_permitted_dependencies(
374 &metadata,
375 "rustc",
376 PERMITTED_RUSTC_DEPENDENCIES,
377 &["rustc_driver", "rustc_codegen_llvm"],
378 bad,
379 );
380
381 // Check cargo independently as it has it's own workspace.
382 let mut cmd = cargo_metadata::MetadataCommand::new();
383 cmd.cargo_path(cargo)
384 .manifest_path(root.join("src/tools/cargo/Cargo.toml"))
385 .features(cargo_metadata::CargoOpt::AllFeatures);
386 let cargo_metadata = t!(cmd.exec());
387 let runtime_ids = HashSet::new();
388 check_license_exceptions(&cargo_metadata, EXCEPTIONS_CARGO, runtime_ids, bad);
389 check_rustfix(&metadata, &cargo_metadata, bad);
390
391 // Check rustc_codegen_cranelift independently as it has it's own workspace.
392 let mut cmd = cargo_metadata::MetadataCommand::new();
393 cmd.cargo_path(cargo)
394 .manifest_path(root.join("compiler/rustc_codegen_cranelift/Cargo.toml"))
395 .features(cargo_metadata::CargoOpt::AllFeatures);
396 let metadata = t!(cmd.exec());
397 let runtime_ids = HashSet::new();
398 check_license_exceptions(&metadata, EXCEPTIONS_CRANELIFT, runtime_ids, bad);
399 check_permitted_dependencies(
400 &metadata,
401 "cranelift",
402 PERMITTED_CRANELIFT_DEPENDENCIES,
403 &["rustc_codegen_cranelift"],
404 bad,
405 );
406
407 let mut cmd = cargo_metadata::MetadataCommand::new();
408 cmd.cargo_path(cargo)
409 .manifest_path(root.join("src/bootstrap/Cargo.toml"))
410 .features(cargo_metadata::CargoOpt::AllFeatures);
411 let metadata = t!(cmd.exec());
412 let runtime_ids = HashSet::new();
413 check_license_exceptions(&metadata, EXCEPTIONS_BOOTSTRAP, runtime_ids, bad);
414 }
415
416 /// Check that all licenses are in the valid list in `LICENSES`.
417 ///
418 /// Packages listed in `exceptions` are allowed for tools.
check_license_exceptions( metadata: &Metadata, exceptions: &[(&str, &str)], runtime_ids: HashSet<&PackageId>, bad: &mut bool, )419 fn check_license_exceptions(
420 metadata: &Metadata,
421 exceptions: &[(&str, &str)],
422 runtime_ids: HashSet<&PackageId>,
423 bad: &mut bool,
424 ) {
425 // Validate the EXCEPTIONS list hasn't changed.
426 for (name, license) in exceptions {
427 // Check that the package actually exists.
428 if !metadata.packages.iter().any(|p| p.name == *name) {
429 tidy_error!(
430 bad,
431 "could not find exception package `{}`\n\
432 Remove from EXCEPTIONS list if it is no longer used.",
433 name
434 );
435 }
436 // Check that the license hasn't changed.
437 for pkg in metadata.packages.iter().filter(|p| p.name == *name) {
438 match &pkg.license {
439 None => {
440 tidy_error!(
441 bad,
442 "dependency exception `{}` does not declare a license expression",
443 pkg.id
444 );
445 }
446 Some(pkg_license) => {
447 if pkg_license.as_str() != *license {
448 println!("dependency exception `{name}` license has changed");
449 println!(" previously `{license}` now `{pkg_license}`");
450 println!(" update EXCEPTIONS for the new license");
451 *bad = true;
452 }
453 }
454 }
455 }
456 }
457
458 let exception_names: Vec<_> = exceptions.iter().map(|(name, _license)| *name).collect();
459
460 // Check if any package does not have a valid license.
461 for pkg in &metadata.packages {
462 if pkg.source.is_none() {
463 // No need to check local packages.
464 continue;
465 }
466 if !runtime_ids.contains(&pkg.id) && exception_names.contains(&pkg.name.as_str()) {
467 continue;
468 }
469 let license = match &pkg.license {
470 Some(license) => license,
471 None => {
472 tidy_error!(bad, "dependency `{}` does not define a license expression", pkg.id);
473 continue;
474 }
475 };
476 if !LICENSES.contains(&license.as_str()) {
477 if pkg.name == "fortanix-sgx-abi" {
478 // This is a specific exception because SGX is considered
479 // "third party". See
480 // https://github.com/rust-lang/rust/issues/62620 for more. In
481 // general, these should never be added.
482 continue;
483 }
484 tidy_error!(bad, "invalid license `{}` in `{}`", license, pkg.id);
485 }
486 }
487 }
488
489 /// Checks the dependency of `restricted_dependency_crates` at the given path. Changes `bad` to
490 /// `true` if a check failed.
491 ///
492 /// Specifically, this checks that the dependencies are on the `permitted_dependencies`.
check_permitted_dependencies( metadata: &Metadata, descr: &str, permitted_dependencies: &[&'static str], restricted_dependency_crates: &[&'static str], bad: &mut bool, )493 fn check_permitted_dependencies(
494 metadata: &Metadata,
495 descr: &str,
496 permitted_dependencies: &[&'static str],
497 restricted_dependency_crates: &[&'static str],
498 bad: &mut bool,
499 ) {
500 let mut has_permitted_dep_error = false;
501 let mut deps = HashSet::new();
502 for to_check in restricted_dependency_crates {
503 let to_check = pkg_from_name(metadata, to_check);
504 use cargo_platform::Cfg;
505 use std::str::FromStr;
506 // We don't expect the compiler to ever run on wasm32, so strip
507 // out those dependencies to avoid polluting the permitted list.
508 deps_of_filtered(metadata, &to_check.id, &mut deps, &|dep_kinds| {
509 dep_kinds.iter().any(|dep_kind| {
510 dep_kind
511 .target
512 .as_ref()
513 .map(|target| {
514 !target.matches(
515 "wasm32-unknown-unknown",
516 &[
517 Cfg::from_str("target_arch=\"wasm32\"").unwrap(),
518 Cfg::from_str("target_os=\"unknown\"").unwrap(),
519 ],
520 )
521 })
522 .unwrap_or(true)
523 })
524 });
525 }
526
527 // Check that the PERMITTED_DEPENDENCIES does not have unused entries.
528 for permitted in permitted_dependencies {
529 if !deps.iter().any(|dep_id| &pkg_from_id(metadata, dep_id).name == permitted) {
530 tidy_error!(
531 bad,
532 "could not find allowed package `{permitted}`\n\
533 Remove from PERMITTED_DEPENDENCIES list if it is no longer used.",
534 );
535 has_permitted_dep_error = true;
536 }
537 }
538
539 // Get in a convenient form.
540 let permitted_dependencies: HashSet<_> = permitted_dependencies.iter().cloned().collect();
541
542 for dep in deps {
543 let dep = pkg_from_id(metadata, dep);
544 // If this path is in-tree, we don't require it to be explicitly permitted.
545 if dep.source.is_some() {
546 if !permitted_dependencies.contains(dep.name.as_str()) {
547 tidy_error!(bad, "Dependency for {descr} not explicitly permitted: {}", dep.id);
548 has_permitted_dep_error = true;
549 }
550 }
551 }
552
553 if has_permitted_dep_error {
554 eprintln!("Go to `{PERMITTED_DEPS_LOCATION}` for the list.");
555 }
556 }
557
558 /// Finds a package with the given name.
pkg_from_name<'a>(metadata: &'a Metadata, name: &'static str) -> &'a Package559 fn pkg_from_name<'a>(metadata: &'a Metadata, name: &'static str) -> &'a Package {
560 let mut i = metadata.packages.iter().filter(|p| p.name == name);
561 let result =
562 i.next().unwrap_or_else(|| panic!("could not find package `{name}` in package list"));
563 assert!(i.next().is_none(), "more than one package found for `{name}`");
564 result
565 }
566
pkg_from_id<'a>(metadata: &'a Metadata, id: &PackageId) -> &'a Package567 fn pkg_from_id<'a>(metadata: &'a Metadata, id: &PackageId) -> &'a Package {
568 metadata.packages.iter().find(|p| &p.id == id).unwrap()
569 }
570
571 /// Finds all the packages that are in the rust runtime.
compute_runtime_crates<'a>(metadata: &'a Metadata) -> HashSet<&'a PackageId>572 fn compute_runtime_crates<'a>(metadata: &'a Metadata) -> HashSet<&'a PackageId> {
573 let mut result = HashSet::new();
574 for name in RUNTIME_CRATES {
575 let id = &pkg_from_name(metadata, name).id;
576 deps_of_filtered(metadata, id, &mut result, &|_| true);
577 }
578 result
579 }
580
581 /// Recursively find all dependencies.
deps_of_filtered<'a>( metadata: &'a Metadata, pkg_id: &'a PackageId, result: &mut HashSet<&'a PackageId>, filter: &dyn Fn(&[DepKindInfo]) -> bool, )582 fn deps_of_filtered<'a>(
583 metadata: &'a Metadata,
584 pkg_id: &'a PackageId,
585 result: &mut HashSet<&'a PackageId>,
586 filter: &dyn Fn(&[DepKindInfo]) -> bool,
587 ) {
588 if !result.insert(pkg_id) {
589 return;
590 }
591 let node = metadata
592 .resolve
593 .as_ref()
594 .unwrap()
595 .nodes
596 .iter()
597 .find(|n| &n.id == pkg_id)
598 .unwrap_or_else(|| panic!("could not find `{pkg_id}` in resolve"));
599 for dep in &node.deps {
600 if !filter(&dep.dep_kinds) {
601 continue;
602 }
603 deps_of_filtered(metadata, &dep.pkg, result, filter);
604 }
605 }
606
direct_deps_of<'a>( metadata: &'a Metadata, pkg_id: &'a PackageId, ) -> impl Iterator<Item = &'a Package>607 fn direct_deps_of<'a>(
608 metadata: &'a Metadata,
609 pkg_id: &'a PackageId,
610 ) -> impl Iterator<Item = &'a Package> {
611 let resolve = metadata.resolve.as_ref().unwrap();
612 let node = resolve.nodes.iter().find(|n| &n.id == pkg_id).unwrap();
613 node.deps.iter().map(|dep| pkg_from_id(metadata, &dep.pkg))
614 }
615
check_rustfix(rust_metadata: &Metadata, cargo_metadata: &Metadata, bad: &mut bool)616 fn check_rustfix(rust_metadata: &Metadata, cargo_metadata: &Metadata, bad: &mut bool) {
617 let cargo = pkg_from_name(cargo_metadata, "cargo");
618 let cargo_rustfix =
619 direct_deps_of(cargo_metadata, &cargo.id).find(|p| p.name == "rustfix").unwrap();
620
621 let compiletest = pkg_from_name(rust_metadata, "compiletest");
622 let compiletest_rustfix =
623 direct_deps_of(rust_metadata, &compiletest.id).find(|p| p.name == "rustfix").unwrap();
624
625 if cargo_rustfix.version != compiletest_rustfix.version {
626 tidy_error!(
627 bad,
628 "cargo's rustfix version {} does not match compiletest's rustfix version {}\n\
629 rustfix should be kept in sync, update the cargo side first, and then update \
630 compiletest along with cargo.",
631 cargo_rustfix.version,
632 compiletest_rustfix.version
633 );
634 }
635 }
636