xref: /aosp_15_r20/development/tools/external_crates/google_metadata/src/lib.rs (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1 // Copyright (C) 2024 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Crate for handling Google METADATA files.
16 
17 use std::{
18     fs::{read_to_string, write},
19     path::PathBuf,
20 };
21 
22 use chrono::Datelike;
23 use protobuf::text_format::ParseError;
24 
25 #[cfg(soong)]
26 mod metadata_proto {
27     pub use google_metadata_proto::metadata::Identifier;
28     pub use google_metadata_proto::metadata::LicenseType;
29     pub use google_metadata_proto::metadata::MetaData;
30 }
31 
32 #[cfg(not(soong))]
33 mod metadata_proto {
34     include!(concat!(env!("OUT_DIR"), "/protos/mod.rs"));
35     pub use crate::metadata::Identifier;
36     pub use crate::metadata::LicenseType;
37     pub use crate::metadata::MetaData;
38 }
39 
40 pub use metadata_proto::*;
41 
42 #[allow(missing_docs)]
43 #[derive(thiserror::Error, Debug)]
44 pub enum Error {
45     #[error("File exists: {}", .0.display())]
46     FileExists(PathBuf),
47     #[error("Crate name not set")]
48     CrateNameMissing(),
49     #[error("Crate names don't match: {} in METADATA vs {}", .0, .1)]
50     CrateNameMismatch(String, String),
51     #[error("Glob pattern error")]
52     ParseError(#[from] ParseError),
53     #[error("Write error")]
54     WriteError(#[from] std::io::Error),
55 }
56 
57 /// Wrapper around a Google METADATA file.
58 pub struct GoogleMetadata {
59     path: PathBuf,
60     metadata: MetaData,
61 }
62 
63 impl GoogleMetadata {
64     /// Reads an existing METADATA file.
try_from<P: Into<PathBuf>>(path: P) -> Result<Self, Error>65     pub fn try_from<P: Into<PathBuf>>(path: P) -> Result<Self, Error> {
66         let path = path.into();
67         let metadata = read_to_string(&path)?;
68         let metadata: MetaData = protobuf::text_format::parse_from_str(&metadata)?;
69         Ok(GoogleMetadata { path, metadata })
70     }
71     /// Initializes a new METADATA file.
init<P: Into<PathBuf>, Q: Into<String>, R: Into<String>, S: Into<String>>( path: P, name: Q, version: R, desc: S, license_type: LicenseType, ) -> Result<Self, Error>72     pub fn init<P: Into<PathBuf>, Q: Into<String>, R: Into<String>, S: Into<String>>(
73         path: P,
74         name: Q,
75         version: R,
76         desc: S,
77         license_type: LicenseType,
78     ) -> Result<Self, Error> {
79         let path = path.into();
80         if path.exists() {
81             return Err(Error::FileExists(path));
82         }
83         let mut metadata = GoogleMetadata { path, metadata: MetaData::new() };
84         let name = name.into();
85         metadata.set_date_to_today()?;
86         metadata.metadata.set_name(name.clone());
87         metadata.set_version_and_urls(&name, version)?;
88         let third_party = metadata.metadata.third_party.mut_or_insert_default();
89         third_party.set_homepage(crates_io_homepage(&name));
90         third_party.set_license_type(license_type);
91         metadata.metadata.set_description(desc.into());
92         Ok(metadata)
93     }
94     /// Writes to the METADATA file.
95     ///
96     /// The existing file is overwritten.
write(&self) -> Result<(), Error>97     pub fn write(&self) -> Result<(), Error> {
98         Ok(write(&self.path, protobuf::text_format::print_to_string_pretty(&self.metadata))?)
99     }
100     /// Sets the date fields to today's date.
set_date_to_today(&mut self) -> Result<(), Error>101     pub fn set_date_to_today(&mut self) -> Result<(), Error> {
102         let now = chrono::Utc::now();
103         let date = self
104             .metadata
105             .third_party
106             .mut_or_insert_default()
107             .last_upgrade_date
108             .mut_or_insert_default();
109         date.set_day(now.day().try_into().unwrap());
110         date.set_month(now.month().try_into().unwrap());
111         date.set_year(now.year());
112         Ok(())
113     }
114     /// Sets the version and URL fields.
115     ///
116     /// Sets third_party.homepage and third_party.version, and
117     /// a single "Archive" identifier with crate archive URL and version.
set_version_and_urls<Q: Into<String>>( &mut self, name: impl AsRef<str>, version: Q, ) -> Result<(), Error>118     pub fn set_version_and_urls<Q: Into<String>>(
119         &mut self,
120         name: impl AsRef<str>,
121         version: Q,
122     ) -> Result<(), Error> {
123         let name_in_metadata = self.metadata.name.as_ref().ok_or(Error::CrateNameMissing())?;
124         if name_in_metadata != name.as_ref() {
125             return Err(Error::CrateNameMismatch(
126                 name_in_metadata.clone(),
127                 name.as_ref().to_string(),
128             ));
129         }
130         let third_party = self.metadata.third_party.mut_or_insert_default();
131         third_party.set_homepage(crates_io_homepage(&name));
132         let version = version.into();
133         third_party.set_version(version.clone());
134         let mut identifier = Identifier::new();
135         identifier.set_type("Archive".to_string());
136         identifier.set_value(crate_archive_url(name, &version));
137         identifier.set_version(version);
138         self.metadata.third_party.mut_or_insert_default().identifier.clear();
139         self.metadata.third_party.mut_or_insert_default().identifier.push(identifier);
140         Ok(())
141     }
142     /// Migrate homepage from an identifier to its own field.
migrate_homepage(&mut self) -> bool143     pub fn migrate_homepage(&mut self) -> bool {
144         let mut homepage = None;
145         for (idx, identifier) in self.metadata.third_party.identifier.iter().enumerate() {
146             if identifier.type_.as_ref().unwrap_or(&String::new()).to_lowercase() == "homepage" {
147                 match homepage {
148                     Some(info) => panic!("Homepage set twice? {info:?} {identifier:?}"),
149                     None => homepage = Some((idx, identifier.clone())),
150                 }
151             }
152         }
153         let Some(homepage) = homepage else { return false };
154         self.metadata.third_party.mut_or_insert_default().identifier.remove(homepage.0);
155         self.metadata.third_party.mut_or_insert_default().homepage = homepage.1.value;
156         true
157     }
158     /// Normalize case of 'Archive' identifiers.
migrate_archive(&mut self) -> bool159     pub fn migrate_archive(&mut self) -> bool {
160         let mut updated = false;
161         for identifier in self.metadata.third_party.mut_or_insert_default().identifier.iter_mut() {
162             if identifier.type_ == Some("ARCHIVE".to_string()) {
163                 identifier.type_ = Some("Archive".to_string());
164                 updated = true;
165             }
166         }
167         updated
168     }
169     /// Remove deprecate URL fields.
remove_deprecated_url(&mut self) -> bool170     pub fn remove_deprecated_url(&mut self) -> bool {
171         let updated = !self.metadata.third_party.url.is_empty();
172         self.metadata.third_party.mut_or_insert_default().url.clear();
173         updated
174     }
175 }
176 
crate_archive_url(name: impl AsRef<str>, version: impl AsRef<str>) -> String177 fn crate_archive_url(name: impl AsRef<str>, version: impl AsRef<str>) -> String {
178     format!(
179         "https://static.crates.io/crates/{}/{}-{}.crate",
180         name.as_ref(),
181         name.as_ref(),
182         version.as_ref()
183     )
184 }
crates_io_homepage(name: impl AsRef<str>) -> String185 fn crates_io_homepage(name: impl AsRef<str>) -> String {
186     format!("https://crates.io/crates/{}", name.as_ref())
187 }
188