Merge "Update the parser to parse validations" into main
diff --git a/tools/crate-updater/src/main.rs b/tools/crate-updater/src/main.rs
index 0b2afc1..79dcf47 100644
--- a/tools/crate-updater/src/main.rs
+++ b/tools/crate-updater/src/main.rs
@@ -28,7 +28,7 @@
 
 #[derive(Parser)]
 struct Cli {
-    /// Absolute path to a repo checkout of aosp-main.
+    /// Absolute path to a repo checkout of main-without-vendor.
     /// It is strongly recommended that you use a source tree dedicated to
     /// running this updater.
     android_root: PathBuf,
@@ -96,7 +96,7 @@
         .run_and_stream_output()?;
 
     Command::new("git")
-        .args(["checkout", "aosp/main"])
+        .args(["checkout", "goog/main"])
         .current_dir(monorepo_path)
         .run_and_stream_output()?;
 
@@ -132,7 +132,7 @@
     let output = Command::new("/google/data/ro/projects/android/ab")
         .args([
             "lkgb",
-            "--branch=aosp-main",
+            "--branch=git_main-without-vendor",
             "--target=aosp_arm64-trunk_staging-userdebug",
             "--raw",
             "--custom_raw_format={o[buildId]}",
diff --git a/tools/external_crates/crate_tool/Cargo.toml b/tools/external_crates/crate_tool/Cargo.toml
index ccb5102..f70ed5c 100644
--- a/tools/external_crates/crate_tool/Cargo.toml
+++ b/tools/external_crates/crate_tool/Cargo.toml
@@ -7,11 +7,11 @@
 
 [dependencies]
 anyhow = "1"
-cargo = "0.76"
+cargo_metadata = "0.19"
 cfg-expr = "0.17"
 chrono = "0.4"
 clap = { version = "4.4.6", features = ["derive"] }
-crates-index = "3.2.0"
+crates-index = "3.8"
 glob = "0.3"
 itertools = "0.11"
 protobuf = "3"
diff --git a/tools/external_crates/crate_tool/src/crate_type.rs b/tools/external_crates/crate_tool/src/crate_type.rs
index 51ba6ec..a1e97e3 100644
--- a/tools/external_crates/crate_tool/src/crate_type.rs
+++ b/tools/external_crates/crate_tool/src/crate_type.rs
@@ -12,12 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use anyhow::{anyhow, Result};
-use cargo::{
-    core::{Manifest, SourceId},
-    util::toml::read_manifest,
-    Config,
-};
+use anyhow::{bail, Result};
+use cargo_metadata::Package;
 use name_and_version::{NameAndVersionRef, NamedAndVersioned};
 use rooted_path::RootedPath;
 use semver::Version;
@@ -26,16 +22,16 @@
 
 #[derive(Debug, Clone)]
 pub struct Crate {
-    manifest: Manifest,
+    metadata: Package,
     path: RootedPath,
 }
 
 impl NamedAndVersioned for Crate {
     fn name(&self) -> &str {
-        self.manifest.name().as_str()
+        self.metadata.name.as_str()
     }
     fn version(&self) -> &Version {
-        self.manifest.version()
+        &self.metadata.version
     }
     fn key(&self) -> NameAndVersionRef {
         NameAndVersionRef::new(self.name(), self.version())
@@ -43,32 +39,30 @@
 }
 
 impl Crate {
-    pub fn new(manifest: Manifest, path: RootedPath) -> Crate {
-        Crate { manifest, path }
+    pub fn new(metadata: Package, path: RootedPath) -> Crate {
+        Crate { metadata, path }
     }
     pub fn from(manifest_dir: RootedPath) -> Result<Crate> {
-        let source_id = SourceId::for_path(manifest_dir.abs())?;
-        let (manifest, _nested) = read_manifest(
-            manifest_dir.join("Cargo.toml").unwrap().as_ref(),
-            source_id,
-            &Config::default()?,
-        )?;
-        match manifest {
-            cargo::core::EitherManifest::Real(r) => Ok(Crate::new(r, manifest_dir)),
-            cargo::core::EitherManifest::Virtual(_) => {
-                Err(anyhow!(CrateError::VirtualCrate(manifest_dir.as_ref().to_path_buf())))
-            }
+        let manifest_path = manifest_dir.abs().join("Cargo.toml");
+        let metadata = cargo_metadata::MetadataCommand::new()
+            .manifest_path(manifest_path)
+            .no_deps()
+            .other_options(["--frozen".to_string()])
+            .exec()?;
+        if metadata.packages.len() != 1 {
+            bail!(CrateError::VirtualCrate(manifest_dir.as_ref().to_path_buf()));
         }
+        Ok(Crate::new(metadata.packages[0].clone(), manifest_dir))
     }
 
     pub fn description(&self) -> &str {
-        self.manifest.metadata().description.as_deref().unwrap_or("")
+        self.metadata.description.as_deref().unwrap_or("")
     }
     pub fn license(&self) -> Option<&str> {
-        self.manifest.metadata().license.as_deref()
+        self.metadata.license.as_deref()
     }
     pub fn repository(&self) -> Option<&str> {
-        self.manifest.metadata().repository.as_deref()
+        self.metadata.repository.as_deref()
     }
     pub fn path(&self) -> &RootedPath {
         &self.path
diff --git a/tools/external_crates/crate_tool/src/lib.rs b/tools/external_crates/crate_tool/src/lib.rs
index ef4537d..30a0593 100644
--- a/tools/external_crates/crate_tool/src/lib.rs
+++ b/tools/external_crates/crate_tool/src/lib.rs
@@ -25,7 +25,6 @@
 mod crate_collection;
 mod crate_type;
 mod crates_io;
-mod license;
 mod managed_crate;
 mod managed_repo;
 mod patch;
diff --git a/tools/external_crates/crate_tool/src/license.rs b/tools/external_crates/crate_tool/src/license.rs
deleted file mode 100644
index ba18d17..0000000
--- a/tools/external_crates/crate_tool/src/license.rs
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (C) 2024 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-use std::{
-    collections::BTreeMap,
-    fs::{remove_file, write},
-    path::Path,
-    sync::LazyLock,
-};
-
-use anyhow::{anyhow, Result};
-use glob::glob;
-use google_metadata::metadata::LicenseType;
-use license_checker::LicenseState;
-use spdx::{LicenseReq, Licensee};
-
-/// Update MODULE_LICENSE_* files in a directory based on the applicable licenses.
-/// These files are typically empty, and their name indicates the type of license that
-/// applies to the code, for example MODULE_LICENSE_APACHE2.
-pub fn update_module_license_files(path: &impl AsRef<Path>, licenses: &LicenseState) -> Result<()> {
-    let path = path.as_ref();
-    for old_module_license_file in glob(
-        path.join("MODULE_LICENSE*").to_str().ok_or(anyhow!("Failed to convert path to string"))?,
-    )? {
-        remove_file(old_module_license_file?)?;
-    }
-    for license in licenses.satisfied.keys().chain(&licenses.unsatisfied) {
-        if let Some(mod_lic) = MODULE_LICENSE_FILES.get(license) {
-            write(path.join(mod_lic), "")?; // Write an empty file. Essentially "touch".
-        }
-    }
-    Ok(())
-}
-
-fn discriminant(lt: LicenseType) -> u8 {
-    // Smaller --> more restricted
-    // Larger --> less restricted
-    match lt {
-        LicenseType::UNKNOWN => 0,
-        LicenseType::BY_EXCEPTION_ONLY => 1,
-        LicenseType::RESTRICTED => 2,
-        LicenseType::RESTRICTED_IF_STATICALLY_LINKED => 3,
-        LicenseType::RECIPROCAL => 4,
-        LicenseType::NOTICE => 5,
-        LicenseType::PERMISSIVE => 6,
-        LicenseType::UNENCUMBERED => 7,
-    }
-}
-
-pub fn most_restrictive_type(licenses: &LicenseState) -> LicenseType {
-    licenses
-        .satisfied
-        .keys()
-        .chain(&licenses.unsatisfied)
-        .map(|req| LICENSE_TYPES.get(req).cloned().unwrap_or(LicenseType::UNKNOWN))
-        .min_by(|a, b| discriminant(*a).cmp(&discriminant(*b)))
-        .unwrap_or(LicenseType::UNKNOWN)
-}
-
-static MODULE_LICENSE_FILES: LazyLock<BTreeMap<LicenseReq, &'static str>> = LazyLock::new(|| {
-    vec![
-        ("Apache-2.0", "MODULE_LICENSE_APACHE2"),
-        ("MIT", "MODULE_LICENSE_MIT"),
-        ("MIT-0", "MODULE_LICENSE_MIT_0"),
-        ("BSD-3-Clause", "MODULE_LICENSE_BSD"),
-        ("BSD-2-Clause", "MODULE_LICENSE_BSD"),
-        ("ISC", "MODULE_LICENSE_ISC"),
-        ("MPL-2.0", "MODULE_LICENSE_MPL"),
-        ("0BSD", "MODULE_LICENSE_PERMISSIVE"),
-        ("Unlicense", "MODULE_LICENSE_PERMISSIVE"),
-        ("Zlib", "MODULE_LICENSE_ZLIB"),
-        ("Unicode-3.0", "MODULE_LICENSE_UNICODE_3"),
-        ("Unicode-DFS-2016", "MODULE_LICENSE_UNICODE"),
-        ("NCSA", "MODULE_LICENSE_NCSA"),
-        ("OpenSSL", "MODULE_LICENSE_OPENSSL"),
-    ]
-    .into_iter()
-    .map(|l| (Licensee::parse(l.0).unwrap().into_req(), l.1))
-    .collect()
-});
-static LICENSE_TYPES: LazyLock<BTreeMap<LicenseReq, LicenseType>> = LazyLock::new(|| {
-    vec![
-        ("Apache-2.0", LicenseType::NOTICE),
-        ("MIT", LicenseType::NOTICE),
-        ("MIT-0", LicenseType::PERMISSIVE),
-        ("BSD-3-Clause", LicenseType::NOTICE),
-        ("BSD-2-Clause", LicenseType::NOTICE),
-        ("ISC", LicenseType::NOTICE),
-        ("MPL-2.0", LicenseType::RECIPROCAL),
-        ("0BSD", LicenseType::PERMISSIVE),
-        ("Unlicense", LicenseType::PERMISSIVE),
-        ("Zlib", LicenseType::NOTICE),
-        ("Unicode-3.0", LicenseType::NOTICE),
-        ("Unicode-DFS-2016", LicenseType::NOTICE),
-        ("NCSA", LicenseType::NOTICE),
-        ("OpenSSL", LicenseType::NOTICE),
-        ("CC0-1.0", LicenseType::UNENCUMBERED),
-    ]
-    .into_iter()
-    .map(|l| (Licensee::parse(l.0).unwrap().into_req(), l.1))
-    .collect()
-});
diff --git a/tools/external_crates/crate_tool/src/managed_crate.rs b/tools/external_crates/crate_tool/src/managed_crate.rs
index adf3fae..181a36a 100644
--- a/tools/external_crates/crate_tool/src/managed_crate.rs
+++ b/tools/external_crates/crate_tool/src/managed_crate.rs
@@ -37,7 +37,6 @@
     copy_dir,
     crate_type::Crate,
     ensure_exists_and_empty,
-    license::{most_restrictive_type, update_module_license_files},
     patch::Patch,
     pseudo_crate::{CargoVendorClean, PseudoCrate},
     SuccessOrError,
@@ -450,7 +449,7 @@
             )?;
         }
 
-        update_module_license_files(&self.temporary_build_directory(), &self.extra.licenses)?;
+        self.extra.licenses.update_module_license_files(&self.temporary_build_directory())?;
         Ok(())
     }
     /// Runs cargo_embargo on the crate in the temporary build directory.
@@ -486,7 +485,7 @@
             self.name(),
             self.extra.vendored_crate.version().to_string(),
             self.extra.vendored_crate.description(),
-            most_restrictive_type(&self.extra.licenses),
+            self.extra.licenses.most_restrictive_type(),
         );
         metadata.write()?;
 
diff --git a/tools/external_crates/crate_tool/src/managed_repo.rs b/tools/external_crates/crate_tool/src/managed_repo.rs
index 3e6b53e..399ecb4 100644
--- a/tools/external_crates/crate_tool/src/managed_repo.rs
+++ b/tools/external_crates/crate_tool/src/managed_repo.rs
@@ -38,7 +38,6 @@
     crate_collection::CrateCollection,
     crate_type::Crate,
     crates_io::{AndroidDependencies, DependencyChanges, SafeVersions},
-    license::{most_restrictive_type, update_module_license_files},
     managed_crate::ManagedCrate,
     pseudo_crate::{CargoVendorDirty, PseudoCrate},
     upgradable::{IsUpgradableTo, MatchesWithCompatibilityRule, SemverCompatibilityRule},
@@ -265,7 +264,7 @@
         println!("  Finding license files");
         let licenses = find_licenses(krate.path().abs(), krate.name(), krate.license())?;
 
-        update_module_license_files(&krate.path().abs(), &licenses)?;
+        licenses.update_module_license_files(&krate.path().abs())?;
 
         println!("  Creating METADATA");
         let metadata = GoogleMetadata::init(
@@ -273,7 +272,7 @@
             krate.name(),
             krate.version().to_string(),
             krate.description(),
-            most_restrictive_type(&licenses),
+            licenses.most_restrictive_type(),
         )?;
         metadata.write()?;
 
@@ -284,7 +283,13 @@
                 .success_or_error()
                 .context("Failed to generate cargo_embargo.json with 'cargo_embargo autoconfig'")?;
         } else {
-            write(krate.path().abs().join("cargo_embargo.json"), "{}")?;
+            write(
+                krate.path().abs().join("cargo_embargo.json"),
+                r#"{
+  "run_cargo": false,
+  "min_sdk_version": "29"
+}"#,
+            )?;
         }
 
         if !licenses.unsatisfied.is_empty() {
diff --git a/tools/external_crates/license_checker/Android.bp b/tools/external_crates/license_checker/Android.bp
index abe5df8..f7b10de 100644
--- a/tools/external_crates/license_checker/Android.bp
+++ b/tools/external_crates/license_checker/Android.bp
@@ -17,6 +17,7 @@
     edition: "2021",
     rustlibs: [
         "libglob",
+        "libgoogle_metadata",
         "libitertools",
         "libspdx",
         "libtextdistance",
@@ -38,6 +39,7 @@
     edition: "2021",
     rustlibs: [
         "libglob",
+        "libgoogle_metadata",
         "libitertools",
         "libspdx",
         "libtextdistance",
diff --git a/tools/external_crates/license_checker/Cargo.toml b/tools/external_crates/license_checker/Cargo.toml
index aa9b7fd..b951f2c 100644
--- a/tools/external_crates/license_checker/Cargo.toml
+++ b/tools/external_crates/license_checker/Cargo.toml
@@ -8,4 +8,5 @@
 itertools = "0.14"
 spdx = "0.10"
 textdistance = "1.1.1"
-thiserror = "1.0"
\ No newline at end of file
+thiserror = "1.0"
+google_metadata = { path = "../google_metadata"}
\ No newline at end of file
diff --git a/tools/external_crates/license_checker/src/file_classifier.rs b/tools/external_crates/license_checker/src/file_classifier.rs
index 10e3be2..e4f5d7c 100644
--- a/tools/external_crates/license_checker/src/file_classifier.rs
+++ b/tools/external_crates/license_checker/src/file_classifier.rs
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use spdx::Licensee;
 use std::{
     collections::BTreeSet,
     fs::read_to_string,
@@ -26,8 +25,8 @@
 pub(crate) struct Classifier {
     file_path: PathBuf,
     contents: String,
-    by_content: OnceLock<BTreeSet<Licensee>>,
-    by_content_fuzzy: OnceLock<Option<Licensee>>,
+    by_content: OnceLock<BTreeSet<spdx::LicenseReq>>,
+    by_content_fuzzy: OnceLock<Option<spdx::LicenseReq>>,
 }
 
 impl Classifier {
@@ -56,13 +55,13 @@
     pub fn file_path(&self) -> &Path {
         self.file_path.as_path()
     }
-    pub fn by_name(&self) -> Option<&Licensee> {
+    pub fn by_name(&self) -> Option<&spdx::LicenseReq> {
         LICENSE_DATA.classify_file_name(self.file_path())
     }
-    pub fn by_content(&self) -> &BTreeSet<Licensee> {
+    pub fn by_content(&self) -> &BTreeSet<spdx::LicenseReq> {
         self.by_content.get_or_init(|| LICENSE_DATA.classify_file_contents(&self.contents))
     }
-    pub fn by_content_fuzzy(&self) -> Option<&Licensee> {
+    pub fn by_content_fuzzy(&self) -> Option<&spdx::LicenseReq> {
         self.by_content_fuzzy
             .get_or_init(|| LICENSE_DATA.classify_file_contents_fuzzy(&self.contents))
             .as_ref()
diff --git a/tools/external_crates/license_checker/src/lib.rs b/tools/external_crates/license_checker/src/lib.rs
index dc4f9e4..6b7f3fe 100644
--- a/tools/external_crates/license_checker/src/lib.rs
+++ b/tools/external_crates/license_checker/src/lib.rs
@@ -16,10 +16,13 @@
 
 use std::{
     collections::{BTreeMap, BTreeSet},
+    fs::{remove_file, write},
     path::{Path, PathBuf},
 };
 
 use file_classifier::Classifier;
+use glob::glob;
+use google_metadata::LicenseType;
 use license_data::{CrateLicenseSpecialCase, License};
 use license_terms::LicenseTerms;
 use licenses::LICENSE_DATA;
@@ -113,14 +116,23 @@
     /// The list of license preferences contains an unknown license
     #[error("The license preference list contains unknown license {0}")]
     LicensePreferenceForUnknownLicense(String),
+    /// Invalid MODULE_LICENSE_* file name.
+    #[error("The MODULE_LICENSE_* file name '{0}' does not begin with 'MODULE_LICENSE_'")]
+    InvalidModuleLicenseFileName(String),
+    /// License type is too restrictive.
+    #[error("The license type {0:?} is too restrictive")]
+    LicenseTypeTooRestrictive(LicenseType),
+    /// I/O error.
+    #[error(transparent)]
+    IoError(#[from] std::io::Error),
 }
 
 /// The result of license file verification, containing a set of acceptable licenses, and the
 /// corresponding license files, if present.
 #[derive(Debug)]
 pub struct LicenseState {
-    /// Unsatisfied licenses. These are licenses that are required by evaluation of SPDX license in
-    /// Cargo.toml, but for which no matching license file was found.
+    /// Unsatisfied licenses. These are licenses that are required by evaluation of the SPDX
+    /// license in Cargo.toml, but for which no matching license file was found.
     pub unsatisfied: BTreeSet<LicenseReq>,
     /// Licenses for which a license file file was found, and the path to that file.
     pub satisfied: BTreeMap<LicenseReq, PathBuf>,
@@ -143,12 +155,11 @@
         let not_required = terms.not_required;
 
         for classifier in file_classifiers {
-            if let Some(licensee) = classifier.by_name() {
-                let req = licensee.clone().into_req();
-                if state.unsatisfied.remove(&req) {
+            if let Some(req) = classifier.by_name() {
+                if state.unsatisfied.remove(req) {
                     state.satisfied.insert(req.clone(), classifier.file_path().to_owned());
-                } else if !state.satisfied.contains_key(&req) {
-                    if not_required.contains(&req) {
+                } else if !state.satisfied.contains_key(req) {
+                    if not_required.contains(req) {
                         state.unneeded.insert(req.clone(), classifier.file_path().to_owned());
                     } else {
                         state.unexpected.insert(req.clone(), classifier.file_path().to_owned());
@@ -159,17 +170,16 @@
 
         if !state.unsatisfied.is_empty() {
             for classifier in file_classifiers {
-                for licensee in classifier.by_content() {
-                    let req = licensee.clone().into_req();
-                    if state.unsatisfied.remove(&req) {
+                for req in classifier.by_content() {
+                    if state.unsatisfied.remove(req) {
                         state.satisfied.insert(req.clone(), classifier.file_path().to_owned());
-                    } else if !state.satisfied.contains_key(&req) && !not_required.contains(&req) {
+                    } else if !state.satisfied.contains_key(req) && !not_required.contains(req) {
                         state.unexpected.insert(req.clone(), classifier.file_path().to_owned());
                     }
                 }
                 if classifier.by_content().len() == 1 {
-                    let req = classifier.by_content().first().unwrap().clone().into_req();
-                    if !state.satisfied.contains_key(&req) && not_required.contains(&req) {
+                    let req = classifier.by_content().first().unwrap();
+                    if !state.satisfied.contains_key(req) && not_required.contains(req) {
                         state.unneeded.insert(req.clone(), classifier.file_path().to_owned());
                     }
                 }
@@ -181,9 +191,8 @@
                 if classifier.by_name().is_some() || !classifier.by_content().is_empty() {
                     continue;
                 }
-                if let Some(licensee) = classifier.by_content_fuzzy() {
-                    let req = licensee.clone().into_req();
-                    if state.unsatisfied.remove(&req) {
+                if let Some(req) = classifier.by_content_fuzzy() {
+                    if state.unsatisfied.remove(req) {
                         state.satisfied.insert(req.clone(), classifier.file_path().to_owned());
                         if state.unsatisfied.is_empty() {
                             break;
@@ -195,6 +204,30 @@
 
         state
     }
+
+    /// Update MODULE_LICENSE_* files in a directory based on the applicable licenses.
+    /// These files are typically empty, and their name indicates the type of license that
+    /// applies to the code, for example MODULE_LICENSE_APACHE2.
+    pub fn update_module_license_files(&self, path: &impl AsRef<Path>) -> Result<(), Error> {
+        let path = path.as_ref();
+        for old_module_license_file in glob(&path.join("MODULE_LICENSE*").to_string_lossy())? {
+            remove_file(old_module_license_file?)?;
+        }
+        for file in self.required_terms().map(|req| LICENSE_DATA.module_license_file(req).unwrap())
+        {
+            write(path.join(file), "")?; // Write an empty file. Essentially "touch".
+        }
+        Ok(())
+    }
+
+    /// Find the most restrictive type of license, for reporting in the METADATA file.
+    pub fn most_restrictive_type(&self) -> LicenseType {
+        LICENSE_DATA.most_restrictive_type(self.required_terms())
+    }
+
+    fn required_terms(&self) -> impl Iterator<Item = &LicenseReq> {
+        self.satisfied.keys().chain(&self.unsatisfied)
+    }
 }
 
 /// Evaluates the license expression for a crate at a given path and returns a minimal set of
diff --git a/tools/external_crates/license_checker/src/license_data.rs b/tools/external_crates/license_checker/src/license_data.rs
index 3330a8f..7399614 100644
--- a/tools/external_crates/license_checker/src/license_data.rs
+++ b/tools/external_crates/license_checker/src/license_data.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+use google_metadata::LicenseType;
+
 /// A license and the means for recognizing it.
 /// We must be able to recognize it either by content, by the `text` field,
 /// or by file name, by the `file_names` field, or both.
@@ -23,13 +25,23 @@
     pub text: Option<&'static str>,
     /// A set of file names, any of which unambiguously identify the license.
     pub file_names: &'static [&'static str],
+    /// The name of the MODULE_LICENSE_* file for this license.
+    pub module_license_file_name: &'static str,
+    /// The license type, per http://go/thirdpartylicenses#types
+    pub license_type: LicenseType,
 }
 
 /// A list of all the licenses we recognize and accept.
 /// Only acceptable licenses should go in this list. Please see go/thirdpartylicenses
 /// for information on what this means.
 pub(crate) static LICENSES: &[License] = &[
-    License { name: "0BSD", text: None, file_names: &["LICENSE-0BSD"] },
+    License {
+        name: "0BSD",
+        text: None,
+        file_names: &["LICENSE-0BSD"],
+        module_license_file_name: "MODULE_LICENSE_PERMISSIVE",
+        license_type: LicenseType::PERMISSIVE,
+    },
     License {
         name: "Apache-2.0",
         text: Some(include_str!("licenses/Apache-2.0.txt")),
@@ -39,58 +51,118 @@
             "LICENSES/Apache-2.0",
             "docs/LICENSE-APACHE",
         ],
+        module_license_file_name: "MODULE_LICENSE_APACHE2",
+        license_type: LicenseType::NOTICE,
     },
     License {
         name: "Apache-2.0 WITH LLVM-exception",
         text: None,
         file_names: &["LICENSE-Apache-2.0_WITH_LLVM-exception"],
+        module_license_file_name: "MODULE_LICENSE_APACHE2",
+        license_type: LicenseType::NOTICE,
     },
     License {
         name: "BSD-2-Clause",
         text: Some(include_str!("licenses/BSD-2-Clause.txt")),
         // Note: LICENSE-BSD is not unambiguous. Could be 2-clause or 3-clause
         file_names: &["LICENSE-BSD-2-Clause", "LICENSE.BSD-2-Clause"],
+        module_license_file_name: "MODULE_LICENSE_BSD",
+        license_type: LicenseType::NOTICE,
     },
     License {
         name: "BSD-3-Clause",
         text: Some(include_str!("licenses/BSD-3-Clause.txt")),
         // Note: LICENSE-BSD is not unambiguous. Could be 2-clause or 3-clause
         file_names: &["LICENSE-BSD-3-Clause"],
+        module_license_file_name: "MODULE_LICENSE_BSD",
+        license_type: LicenseType::NOTICE,
     },
-    License { name: "ISC", text: Some(include_str!("licenses/ISC.txt")), file_names: &[] },
+    License {
+        name: "ISC",
+        text: Some(include_str!("licenses/ISC.txt")),
+        file_names: &[],
+        module_license_file_name: "MODULE_LICENSE_ISC",
+        license_type: LicenseType::NOTICE,
+    },
     License {
         name: "MIT",
         text: Some(include_str!("licenses/MIT.txt")),
         file_names: &["LICENSE-MIT", "LICENSES/MIT", "docs/LICENSE-MIT"],
+        module_license_file_name: "MODULE_LICENSE_MIT",
+        license_type: LicenseType::NOTICE,
     },
-    License { name: "MIT-0", text: Some(include_str!("licenses/MIT-0.txt")), file_names: &[] },
-    License { name: "MPL-2.0", text: Some(include_str!("licenses/MPL-2.0.txt")), file_names: &[] },
-    License { name: "NCSA", text: Some(include_str!("licenses/NCSA.txt")), file_names: &[] },
-    License { name: "OpenSSL", text: Some(include_str!("licenses/OpenSSL.txt")), file_names: &[] },
+    License {
+        name: "MIT-0",
+        text: Some(include_str!("licenses/MIT-0.txt")),
+        file_names: &[],
+        module_license_file_name: "MODULE_LICENSE_MIT_0",
+        license_type: LicenseType::PERMISSIVE,
+    },
+    License {
+        name: "MPL-2.0",
+        text: Some(include_str!("licenses/MPL-2.0.txt")),
+        file_names: &[],
+        module_license_file_name: "MODULE_LICENSE_MPL",
+        license_type: LicenseType::RECIPROCAL,
+    },
+    License {
+        name: "NCSA",
+        text: Some(include_str!("licenses/NCSA.txt")),
+        file_names: &[],
+        module_license_file_name: "MODULE_LICENSE_NCSA",
+        license_type: LicenseType::NOTICE,
+    },
+    License {
+        name: "OpenSSL",
+        text: Some(include_str!("licenses/OpenSSL.txt")),
+        file_names: &[],
+        module_license_file_name: "MODULE_LICENSE_OPENSSL",
+        license_type: LicenseType::NOTICE,
+    },
     License {
         name: "Unicode-3.0",
         text: Some(include_str!("licenses/Unicode-3.0.txt")),
         // Note: LICENSE-UNICODE is not unambiguous
         file_names: &[],
+        module_license_file_name: "MODULE_LICENSE_UNICODE_3",
+        license_type: LicenseType::NOTICE,
     },
     License {
         name: "Unicode-DFS-2016",
         text: Some(include_str!("licenses/Unicode-DFS-2016.txt")),
         // Note: LICENSE-UNICODE is not unambiguous
         file_names: &[],
+        module_license_file_name: "MODULE_LICENSE_UNICODE",
+        license_type: LicenseType::NOTICE,
     },
     License {
         name: "Unlicense",
         text: Some(include_str!("licenses/Unlicense.txt")),
         file_names: &["UNLICENSE"],
+        module_license_file_name: "MODULE_LICENSE_PERMISSIVE",
+        license_type: LicenseType::PERMISSIVE,
     },
     License {
         name: "Zlib",
         text: Some(include_str!("licenses/Zlib.txt")),
         file_names: &["LICENSE-ZLIB"],
+        module_license_file_name: "MODULE_LICENSE_ZLIB",
+        license_type: LicenseType::NOTICE,
     },
-    License { name: "BSL-1.0", text: None, file_names: &["LICENSE-BOOST"] },
-    License { name: "CC0-1.0", text: None, file_names: &["LICENSES/CC0-1.0"] },
+    License {
+        name: "BSL-1.0",
+        text: None,
+        file_names: &["LICENSE-BOOST"],
+        module_license_file_name: "MODULE_LICENSE_BOOST",
+        license_type: LicenseType::NOTICE,
+    },
+    License {
+        name: "CC0-1.0",
+        text: None,
+        file_names: &["LICENSES/CC0-1.0"],
+        module_license_file_name: "MODULE_LICENSE_CC0",
+        license_type: LicenseType::UNENCUMBERED,
+    },
 ];
 
 /// Specifies the order of preference for choosing a license.
diff --git a/tools/external_crates/license_checker/src/license_data_tests.rs b/tools/external_crates/license_checker/src/license_data_tests.rs
index 68a6312..5ab65d9 100644
--- a/tools/external_crates/license_checker/src/license_data_tests.rs
+++ b/tools/external_crates/license_checker/src/license_data_tests.rs
@@ -26,32 +26,32 @@
         );
         assert_eq!(
             LICENSE_DATA.classify_file_name(Path::new("UNLICENSE")),
-            Some(&Licensee::parse("Unlicense").unwrap()),
+            Some(&Licensee::parse("Unlicense").unwrap().into_req()),
             "Unlicense"
         );
         assert_eq!(
             LICENSE_DATA.classify_file_name(Path::new("LICENSE-MIT")),
-            Some(&Licensee::parse("MIT").unwrap()),
+            Some(&Licensee::parse("MIT").unwrap().into_req()),
             "Standard name"
         );
         assert_eq!(
             LICENSE_DATA.classify_file_name(Path::new("LICENSE-APACHE")),
-            Some(&Licensee::parse("Apache-2.0").unwrap()),
+            Some(&Licensee::parse("Apache-2.0").unwrap().into_req()),
             "Standard name"
         );
         assert_eq!(
             LICENSE_DATA.classify_file_name(Path::new("LICENSE-apache")),
-            Some(&Licensee::parse("Apache-2.0").unwrap()),
+            Some(&Licensee::parse("Apache-2.0").unwrap().into_req()),
             "Case-insensitive"
         );
         assert_eq!(
             LICENSE_DATA.classify_file_name(Path::new("LICENSE-APACHE.md")),
-            Some(&Licensee::parse("Apache-2.0").unwrap()),
+            Some(&Licensee::parse("Apache-2.0").unwrap().into_req()),
             ".md extension ignored"
         );
         assert_eq!(
             LICENSE_DATA.classify_file_name(Path::new("LICENSE-0BSD.txt")),
-            Some(&Licensee::parse("0BSD").unwrap()),
+            Some(&Licensee::parse("0BSD").unwrap().into_req()),
             ".txt extension ignored"
         );
         assert!(
@@ -60,12 +60,12 @@
         );
         assert_eq!(
             LICENSE_DATA.classify_file_name(Path::new("LICENSES/MIT")),
-            Some(&Licensee::parse("MIT").unwrap()),
+            Some(&Licensee::parse("MIT").unwrap().into_req()),
             "Subdirectory"
         );
         assert_eq!(
             LICENSE_DATA.classify_file_name(Path::new("LICENSES/Apache-2.0")),
-            Some(&Licensee::parse("Apache-2.0").unwrap()),
+            Some(&Licensee::parse("Apache-2.0").unwrap().into_req()),
             "Subdirectory"
         );
     }
@@ -85,7 +85,7 @@
         assert_eq!(
             LICENSE_DATA
                 .classify_file_contents(include_str!("testdata/LICENSE-MIT-aarch64-paging.txt")),
-            BTreeSet::from([Licensee::parse("MIT").unwrap()])
+            BTreeSet::from([Licensee::parse("MIT").unwrap().into_req()])
         );
     }
 
@@ -97,7 +97,7 @@
         assert_eq!(
             LICENSE_DATA
                 .classify_file_contents_fuzzy(include_str!("testdata/BSD-3-Clause-bindgen.txt")),
-            Some(Licensee::parse("BSD-3-Clause").unwrap())
+            Some(Licensee::parse("BSD-3-Clause").unwrap().into_req())
         );
     }
 
@@ -113,8 +113,8 @@
                 .as_str()
             ),
             BTreeSet::from([
-                Licensee::parse("MIT").unwrap(),
-                Licensee::parse("Apache-2.0").unwrap()
+                Licensee::parse("MIT").unwrap().into_req(),
+                Licensee::parse("Apache-2.0").unwrap().into_req()
             ])
         );
     }
diff --git a/tools/external_crates/license_checker/src/licenses.rs b/tools/external_crates/license_checker/src/licenses.rs
index 8f88de6..1e1af73 100644
--- a/tools/external_crates/license_checker/src/licenses.rs
+++ b/tools/external_crates/license_checker/src/licenses.rs
@@ -19,8 +19,9 @@
     sync::LazyLock,
 };
 
+use google_metadata::LicenseType;
 use itertools::Itertools;
-use spdx::Licensee;
+use spdx::{LicenseReq, Licensee};
 use textdistance::str::ratcliff_obershelp;
 
 use crate::{
@@ -31,10 +32,10 @@
 
 #[derive(Debug)]
 pub(crate) struct Licenses {
-    licenses: BTreeMap<Licensee, ParsedLicense>,
+    licenses: BTreeMap<LicenseReq, ParsedLicense>,
     license_preference: Vec<Licensee>,
     crate_license_special_cases: CrateLicenseSpecialCases,
-    license_file_names: BTreeMap<OsString, Licensee>,
+    license_file_names: BTreeMap<OsString, LicenseReq>,
 }
 
 impl Licenses {
@@ -51,33 +52,38 @@
         let mut license_file_names = BTreeMap::new();
         for license in raw_licenses {
             let parsed = ParsedLicense::try_from(license)?;
-            let licensee = parsed.licensee().clone();
+            let req = parsed.license_req();
             for file_name in parsed.file_names() {
-                if let Some(other) = license_file_names.insert(file_name.clone(), licensee.clone())
-                {
+                if let Some(other) = license_file_names.insert(file_name.clone(), req.clone()) {
                     return Err(Error::DuplicateLicenseFileName {
                         file_name: file_name.to_string_lossy().into_owned(),
-                        license: parsed.licensee().to_string(),
+                        license: parsed.license_req().to_string(),
                         other_license: other.to_string(),
                     });
                 }
             }
-            if licenses.insert(licensee.clone(), parsed).is_some() {
-                return Err(Error::DuplicateLicense(licensee.to_string()));
+            if licenses.insert(req.clone(), parsed).is_some() {
+                return Err(Error::DuplicateLicense(req.to_string()));
             }
         }
 
         let mut ranked_licenses = Vec::new();
         for pref in license_preference {
             let licensee = Licensee::parse(pref)?;
-            if !licenses.contains_key(&licensee) {
+            if !licenses.contains_key(&licensee.clone().into_req()) {
                 return Err(Error::LicensePreferenceForUnknownLicense(pref.to_string()));
             }
-            ranked_licenses.push(licensee);
+            ranked_licenses.push(licensee.clone());
         }
         let unranked_licenses = licenses
-            .keys()
-            .filter_map(|l| if !ranked_licenses.contains(l) { Some(l.clone()) } else { None })
+            .values()
+            .filter_map(|l| {
+                if !ranked_licenses.contains(l.licensee()) {
+                    Some(l.licensee().clone())
+                } else {
+                    None
+                }
+            })
             .collect::<Vec<_>>();
         let license_preference = ranked_licenses.into_iter().chain(unranked_licenses).collect();
 
@@ -127,24 +133,24 @@
         )
     }
 
-    pub fn classify_file_name(&self, file: impl AsRef<Path>) -> Option<&Licensee> {
+    pub fn classify_file_name(&self, file: impl AsRef<Path>) -> Option<&LicenseReq> {
         self.license_file_names.get(&normalize_filename(file))
     }
 
     /// Classify file contents by exact substring match on the license text.
-    pub fn classify_file_contents(&self, contents: &str) -> BTreeSet<Licensee> {
+    pub fn classify_file_contents(&self, contents: &str) -> BTreeSet<LicenseReq> {
         let contents = strip_punctuation(contents);
 
         let mut matches = BTreeSet::new();
         for license in self.licenses.values() {
             if license.is_substring_of(contents.as_str()) {
-                matches.insert(license.licensee().clone());
+                matches.insert(license.license_req());
             }
         }
         matches
     }
 
-    pub fn classify_file_contents_fuzzy(&self, contents: &str) -> Option<Licensee> {
+    pub fn classify_file_contents_fuzzy(&self, contents: &str) -> Option<LicenseReq> {
         let contents = strip_punctuation(contents);
 
         // Fuzzy match. This is expensive, so start with licenses that are closest in length to the file,
@@ -165,13 +171,43 @@
             if let Some(processed_text) = license.processed_text() {
                 let similarity = ratcliff_obershelp(contents.as_str(), processed_text);
                 if similarity > 0.95 {
-                    return Some(license.licensee().clone());
+                    return Some(license.license_req());
                 }
             }
         }
 
         None
     }
+
+    pub fn module_license_file(&self, req: &LicenseReq) -> Option<&'static str> {
+        self.licenses.get(req).map(|l| l.module_license_file_name())
+    }
+
+    pub fn most_restrictive_type<'a>(
+        &self,
+        terms: impl Iterator<Item = &'a LicenseReq>,
+    ) -> LicenseType {
+        let discriminant = |lt: LicenseType| -> u8 {
+            // Smaller --> more restricted
+            // Larger --> less restricted
+            match lt {
+                LicenseType::UNKNOWN => 0,
+                LicenseType::BY_EXCEPTION_ONLY => 1,
+                LicenseType::RESTRICTED => 2,
+                LicenseType::RESTRICTED_IF_STATICALLY_LINKED => 3,
+                LicenseType::RECIPROCAL => 4,
+                LicenseType::NOTICE => 5,
+                LicenseType::PERMISSIVE => 6,
+                LicenseType::UNENCUMBERED => 7,
+            }
+        };
+        terms
+            .map(|req| {
+                self.licenses.get(req).map(|l| l.license_type()).unwrap_or(LicenseType::UNKNOWN)
+            })
+            .min_by(|a, b| discriminant(*a).cmp(&discriminant(*b)))
+            .unwrap_or(LicenseType::UNKNOWN)
+    }
 }
 
 pub(crate) static LICENSE_DATA: LazyLock<Licenses> = LazyLock::new(|| {
@@ -194,9 +230,27 @@
     fn basic() {
         assert!(Licenses::new(
             &[
-                License { name: "Apache-2.0", text: None, file_names: &["LICENSE-APACHE"] },
-                License { name: "MIT", text: None, file_names: &["LICENSE-MIT"] },
-                License { name: "BSD-3-Clause", text: None, file_names: &["LICENSE-BSD-3-Clause"] },
+                License {
+                    name: "Apache-2.0",
+                    text: None,
+                    file_names: &["LICENSE-APACHE"],
+                    module_license_file_name: "MODULE_LICENSE_APACHE2",
+                    license_type: google_metadata::LicenseType::NOTICE,
+                },
+                License {
+                    name: "MIT",
+                    text: None,
+                    file_names: &["LICENSE-MIT"],
+                    module_license_file_name: "MODULE_LICENSE_MIT",
+                    license_type: google_metadata::LicenseType::NOTICE,
+                },
+                License {
+                    name: "BSD-3-Clause",
+                    text: None,
+                    file_names: &["LICENSE-BSD-3-Clause"],
+                    module_license_file_name: "MODULE_LICENSE_BSD",
+                    license_type: google_metadata::LicenseType::NOTICE,
+                },
             ],
             &["Apache-2.0", "MIT"],
             &[],
@@ -214,8 +268,20 @@
         assert!(matches!(
             Licenses::new(
                 &[
-                    License { name: "MIT", text: None, file_names: &["LICENSE-foo"] },
-                    License { name: "MIT", text: None, file_names: &["LICENSE-bar"] }
+                    License {
+                        name: "MIT",
+                        text: None,
+                        file_names: &["LICENSE-foo"],
+                        module_license_file_name: "MODULE_LICENSE_MIT",
+                        license_type: google_metadata::LicenseType::NOTICE,
+                    },
+                    License {
+                        name: "MIT",
+                        text: None,
+                        file_names: &["LICENSE-bar"],
+                        module_license_file_name: "MODULE_LICENSE_MIT",
+                        license_type: google_metadata::LicenseType::NOTICE,
+                    }
                 ],
                 &[],
                 &[],
@@ -229,8 +295,20 @@
         assert!(matches!(
             Licenses::new(
                 &[
-                    License { name: "Apache-2.0", text: Some("foo"), file_names: &[] },
-                    License { name: "MIT", text: Some("foobar"), file_names: &[] }
+                    License {
+                        name: "Apache-2.0",
+                        text: Some("foo"),
+                        file_names: &[],
+                        module_license_file_name: "MODULE_LICENSE_APACHE2",
+                        license_type: google_metadata::LicenseType::NOTICE,
+                    },
+                    License {
+                        name: "MIT",
+                        text: Some("foobar"),
+                        file_names: &[],
+                        module_license_file_name: "MODULE_LICENSE_MIT",
+                        license_type: google_metadata::LicenseType::NOTICE,
+                    }
                 ],
                 &[],
                 &[],
@@ -244,8 +322,20 @@
         assert!(matches!(
             Licenses::new(
                 &[
-                    License { name: "Apache-2.0", text: None, file_names: &["LICENSE"] },
-                    License { name: "MIT", text: None, file_names: &["LICENSE"] }
+                    License {
+                        name: "Apache-2.0",
+                        text: None,
+                        file_names: &["LICENSE"],
+                        module_license_file_name: "MODULE_LICENSE_APACHE2",
+                        license_type: google_metadata::LicenseType::NOTICE,
+                    },
+                    License {
+                        name: "MIT",
+                        text: None,
+                        file_names: &["LICENSE"],
+                        module_license_file_name: "MODULE_LICENSE_MIT",
+                        license_type: google_metadata::LicenseType::NOTICE,
+                    }
                 ],
                 &[],
                 &[],
@@ -257,7 +347,17 @@
     #[test]
     fn unfindable_license_file() {
         assert!(matches!(
-            Licenses::new(&[License { name: "MIT", text: None, file_names: &["foo"] },], &[], &[],),
+            Licenses::new(
+                &[License {
+                    name: "MIT",
+                    text: None,
+                    file_names: &["foo"],
+                    module_license_file_name: "MODULE_LICENSE_MIT",
+                    license_type: google_metadata::LicenseType::NOTICE,
+                },],
+                &[],
+                &[],
+            ),
             Err(Error::LicenseFileNotFindable(_, _))
         ));
     }
@@ -266,7 +366,13 @@
     fn preference_for_unknown_license() {
         assert!(matches!(
             Licenses::new(
-                &[License { name: "MIT", text: None, file_names: &["LICENSE-MIT"] }],
+                &[License {
+                    name: "MIT",
+                    text: None,
+                    file_names: &["LICENSE-MIT"],
+                    module_license_file_name: "MODULE_LICENSE_MIT",
+                    license_type: google_metadata::LicenseType::NOTICE,
+                }],
                 &["foo"],
                 &[],
             ),
@@ -274,7 +380,13 @@
         ));
         assert!(matches!(
             Licenses::new(
-                &[License { name: "MIT", text: None, file_names: &["LICENSE-MIT"] }],
+                &[License {
+                    name: "MIT",
+                    text: None,
+                    file_names: &["LICENSE-MIT"],
+                    module_license_file_name: "MODULE_LICENSE_MIT",
+                    license_type: google_metadata::LicenseType::NOTICE,
+                }],
                 &["Apache-2.0"],
                 &[],
             ),
@@ -286,8 +398,20 @@
     fn evaluate_crate_license() {
         let licenses = Licenses::new(
             &[
-                License { name: "Apache-2.0", text: None, file_names: &["LICENSE-APACHE"] },
-                License { name: "MIT", text: None, file_names: &["LICENSE-MIT"] },
+                License {
+                    name: "Apache-2.0",
+                    text: None,
+                    file_names: &["LICENSE-APACHE"],
+                    module_license_file_name: "MODULE_LICENSE_APACHE2",
+                    license_type: google_metadata::LicenseType::NOTICE,
+                },
+                License {
+                    name: "MIT",
+                    text: None,
+                    file_names: &["LICENSE-MIT"],
+                    module_license_file_name: "MODULE_LICENSE_MIT",
+                    license_type: google_metadata::LicenseType::NOTICE,
+                },
             ],
             &["Apache-2.0", "MIT"],
             &[],
diff --git a/tools/external_crates/license_checker/src/parsed_license_data.rs b/tools/external_crates/license_checker/src/parsed_license_data.rs
index f16c39b..9ebcea9 100644
--- a/tools/external_crates/license_checker/src/parsed_license_data.rs
+++ b/tools/external_crates/license_checker/src/parsed_license_data.rs
@@ -17,6 +17,8 @@
     ffi::{OsStr, OsString},
 };
 
+use google_metadata::LicenseType;
+
 use crate::{
     license_file_finder::is_findable, util::strip_punctuation, CrateLicenseSpecialCase, Error,
     License,
@@ -30,18 +32,31 @@
     processed_text: Option<String>,
     // A set of file names, any of which unambiguously identify the license.
     file_names: BTreeSet<OsString>,
+    /// The name of the MODULE_LICENSE_* file for this license.
+    module_license_file_name: &'static str,
+    /// The license type, per http://go/thirdpartylicenses#types
+    license_type: LicenseType,
 }
 
 impl ParsedLicense {
     pub fn licensee(&self) -> &spdx::Licensee {
         &self.licensee
     }
+    pub fn license_req(&self) -> spdx::LicenseReq {
+        self.licensee.clone().into_req()
+    }
     pub fn processed_text(&self) -> Option<&str> {
         self.processed_text.as_deref()
     }
     pub fn file_names(&self) -> &BTreeSet<OsString> {
         &self.file_names
     }
+    pub fn module_license_file_name(&self) -> &'static str {
+        self.module_license_file_name
+    }
+    pub fn license_type(&self) -> LicenseType {
+        self.license_type
+    }
     pub fn is_substring_of(&self, other: &str) -> bool {
         self.processed_text.as_ref().is_some_and(|text| other.contains(text.as_str()))
     }
@@ -71,10 +86,28 @@
             file_names.insert(OsString::from(license_file.to_uppercase()));
         }
 
+        if !value.module_license_file_name.starts_with("MODULE_LICENSE_") {
+            return Err(Error::InvalidModuleLicenseFileName(
+                value.module_license_file_name.to_string(),
+            ));
+        }
+
+        if matches!(
+            value.license_type,
+            LicenseType::UNKNOWN
+                | LicenseType::BY_EXCEPTION_ONLY
+                | LicenseType::RESTRICTED_IF_STATICALLY_LINKED
+                | LicenseType::RESTRICTED
+        ) {
+            return Err(Error::LicenseTypeTooRestrictive(value.license_type));
+        }
+
         Ok(ParsedLicense {
             licensee: spdx::Licensee::parse(value.name)?,
             processed_text: value.text.map(strip_punctuation),
             file_names,
+            module_license_file_name: value.module_license_file_name,
+            license_type: value.license_type,
         })
     }
 }
@@ -162,7 +195,13 @@
     #[test]
     fn invalid_license_name() {
         assert!(matches!(
-            ParsedLicense::try_from(&License { name: "foo", text: None, file_names: &["LICENSE"] }),
+            ParsedLicense::try_from(&License {
+                name: "foo",
+                text: None,
+                file_names: &["LICENSE"],
+                module_license_file_name: "MODULE_LICENSE_FOO",
+                license_type: LicenseType::NOTICE,
+            }),
             Err(Error::LicenseParseError(_))
         ));
     }
@@ -170,7 +209,13 @@
     #[test]
     fn missing_both_text_and_file_name() {
         assert!(matches!(
-            ParsedLicense::try_from(&License { name: "MIT", text: None, file_names: &[] }),
+            ParsedLicense::try_from(&License {
+                name: "MIT",
+                text: None,
+                file_names: &[],
+                module_license_file_name: "MODULE_LICENSE_MIT",
+                license_type: LicenseType::NOTICE,
+            }),
             Err(Error::LicenseWithoutTextOrFileNames(_))
         ));
     }
@@ -181,18 +226,57 @@
             ParsedLicense::try_from(&License {
                 name: "MIT",
                 text: Some(" "),
-                file_names: &["LICENSE-MIT"]
+                file_names: &["LICENSE-MIT"],
+                module_license_file_name: "MODULE_LICENSE_MIT",
+                license_type: LicenseType::NOTICE,
             }),
             Err(Error::EmptyLicenseText(_))
         ));
     }
 
     #[test]
+    fn invalid_module_license_file() {
+        assert!(matches!(
+            ParsedLicense::try_from(&License {
+                name: "MIT",
+                text: None,
+                file_names: &["LICENSE-MIT"],
+                module_license_file_name: "blah",
+                license_type: LicenseType::NOTICE,
+            }),
+            Err(Error::InvalidModuleLicenseFileName(_))
+        ));
+    }
+
+    #[test]
+    fn restrictive_licenses_rejected() {
+        for license_type in [
+            LicenseType::UNKNOWN,
+            LicenseType::BY_EXCEPTION_ONLY,
+            LicenseType::RESTRICTED,
+            LicenseType::RESTRICTED_IF_STATICALLY_LINKED,
+        ] {
+            assert!(matches!(
+                ParsedLicense::try_from(&License {
+                    name: "MIT",
+                    text: None,
+                    file_names: &["LICENSE-MIT"],
+                    module_license_file_name: "MODULE_LICENSE_MIT",
+                    license_type,
+                }),
+                Err(Error::LicenseTypeTooRestrictive(_))
+            ));
+        }
+    }
+
+    #[test]
     fn is_substring_of() {
         let license: ParsedLicense = ParsedLicense::try_from(&License {
             name: "MIT",
             text: Some("foo"),
             file_names: &["LICENSE-MIT"],
+            module_license_file_name: "MODULE_LICENSE_MIT",
+            license_type: LicenseType::NOTICE,
         })
         .unwrap();
         assert!(license.is_substring_of("foobar"));
@@ -201,6 +285,8 @@
             name: "MIT",
             text: None,
             file_names: &["LICENSE-MIT"],
+            module_license_file_name: "MODULE_LICENSE_MIT",
+            license_type: LicenseType::NOTICE,
         })
         .unwrap();
         assert!(