simics_package/package/
mod.rs1use crate::{Error, IspmMetadata, PackageArtifacts, PackageInfo, PackageSpec, Result};
8use cargo_subcommand::Subcommand;
9use flate2::{write::GzEncoder, Compression};
10#[cfg(unix)]
11use std::time::SystemTime;
12use std::{
13 fs::write,
14 path::{Path, PathBuf},
15};
16use tar::{Builder, Header};
17use typed_builder::TypedBuilder;
18
19#[cfg(unix)]
20pub const HOST_DIRNAME: &str = "linux64";
22
23#[cfg(windows)]
24pub const HOST_DIRNAME: &str = "win64";
26
27#[derive(TypedBuilder, Debug, Clone)]
28pub struct Package {
31 pub spec: PackageSpec,
34 pub target_profile_dir: PathBuf,
36}
37
38impl Package {
39 pub const INNER_PACKAGE_FILENAME: &'static str = "package.tar.gz";
41 pub const METADATA_FILENAME: &'static str = "ispm-metadata";
43 pub const ADDON_TYPE: &'static str = "addon";
45 pub const COMPRESSION_LEVEL: u32 = 6;
47
48 pub fn from_subcommand(subcommand: &Subcommand) -> Result<Self> {
51 let target_profile_dir = subcommand.build_dir(subcommand.target());
52
53 let spec = PackageSpec::from_subcommand(subcommand)?
54 .with_artifacts(&PackageArtifacts::from_subcommand(subcommand)?);
55
56 Ok(Self {
57 spec,
58 target_profile_dir,
59 })
60 }
61
62 pub fn package_dirname(&self) -> Result<String> {
66 if self.spec.typ == Self::ADDON_TYPE {
67 Ok(format!(
68 "simics-{}-{}",
69 self.spec.package_name, self.spec.version
70 ))
71 } else {
72 Err(Error::NonAddonPackage)
73 }
74 }
75
76 pub fn full_package_name(&self) -> String {
78 format!("{}-{}", self.spec.package_name, self.spec.host)
79 }
80
81 pub fn package_name(&self) -> String {
84 format!(
85 "simics-pkg-{}-{}",
86 self.spec.package_number, self.spec.version
87 )
88 }
89
90 pub fn package_name_with_host(&self) -> String {
92 format!("{}-{}", self.package_name(), self.spec.host)
93 }
94
95 pub fn package_filename(&self) -> String {
97 format!("{}.ispm", self.package_name_with_host())
98 }
99
100 #[cfg(unix)]
101 pub fn set_header_common(header: &mut Header) -> Result<()> {
104 use libc::{getgid, getpwuid, getuid};
105 use std::ffi::CStr;
106
107 header.set_mtime(
108 SystemTime::now()
109 .duration_since(SystemTime::UNIX_EPOCH)?
110 .as_secs(),
111 );
112 header.set_uid(unsafe { getuid() } as u64);
113 header.set_gid(unsafe { getgid() } as u64);
114 let username = unsafe {
115 CStr::from_ptr(
116 getpwuid(getuid())
117 .as_ref()
118 .ok_or_else(|| Error::PackageMetadataFieldNotFound {
119 field_name: "username".to_string(),
120 })?
121 .pw_name,
122 )
123 }
124 .to_str()?
125 .to_string();
126 let groupname = unsafe {
127 CStr::from_ptr(
128 getpwuid(getuid())
129 .as_ref()
130 .ok_or_else(|| Error::PackageMetadataFieldNotFound {
131 field_name: "groupname".to_string(),
132 })?
133 .pw_name,
134 )
135 }
136 .to_str()?
137 .to_string();
138 header.set_username(&username)?;
139 header.set_groupname(&groupname)?;
140 header.set_mode(0o755);
141
142 Ok(())
143 }
144
145 #[cfg(windows)]
146 pub fn set_header_common(_header: &mut Header) -> Result<()> {
148 Ok(())
149 }
150
151 pub fn create_inner_tarball(&self) -> Result<(Vec<u8>, usize)> {
153 let tar_gz = Vec::new();
154 let encoder = GzEncoder::new(tar_gz, Compression::new(Self::COMPRESSION_LEVEL));
155 let mut tar = Builder::new(encoder);
156 let mut uncompressed_size = 0;
159
160 let package_info = PackageInfo::from(&self.spec);
162 let package_info_string = serde_yaml::to_string(&package_info)? + &package_info.files();
163 let package_info_data = package_info_string.as_bytes();
164 uncompressed_size += package_info_data.len();
165 let mut metadata_header = Header::new_gnu();
166 metadata_header.set_size(package_info_data.len() as u64);
167 Self::set_header_common(&mut metadata_header)?;
168 tar.append_data(
169 &mut metadata_header,
170 PathBuf::from(self.package_dirname()?)
171 .join("packageinfo")
172 .join(self.full_package_name()),
173 package_info_data,
174 )?;
175 self.spec.files.iter().try_for_each(|(pkg_loc, src_loc)| {
176 let src_path = PathBuf::from(src_loc);
177 uncompressed_size += src_path.metadata()?.len() as usize;
178 tar.append_path_with_name(src_path, pkg_loc)?;
179 Ok::<(), Error>(())
180 })?;
181
182 tar.finish()?;
183
184 Ok((tar.into_inner()?.finish()?, uncompressed_size))
185 }
186
187 pub fn create_tarball(&self) -> Result<Vec<u8>> {
190 let tar_gz = Vec::new();
191 let encoder = GzEncoder::new(tar_gz, Compression::new(Self::COMPRESSION_LEVEL));
192 let mut tar = Builder::new(encoder);
193 let (inner_tarball, uncompressed_size) = self.create_inner_tarball()?;
194
195 let mut ispm_metadata = IspmMetadata::from(&self.spec);
196 ispm_metadata.uncompressed_size = uncompressed_size;
200
201 let ispm_metadata_string = serde_json::to_string(&ispm_metadata)?;
202 let ispm_metadata_data = ispm_metadata_string.as_bytes();
203 let mut ispm_metadata_header = Header::new_gnu();
204 ispm_metadata_header.set_size(ispm_metadata_data.len() as u64);
205 Self::set_header_common(&mut ispm_metadata_header)?;
206 tar.append_data(
207 &mut ispm_metadata_header,
208 Self::METADATA_FILENAME,
209 ispm_metadata_data,
210 )?;
211
212 let mut inner_tarball_header = Header::new_gnu();
213 inner_tarball_header.set_size(inner_tarball.len() as u64);
214 Self::set_header_common(&mut inner_tarball_header)?;
215 tar.append_data(
216 &mut inner_tarball_header,
217 Self::INNER_PACKAGE_FILENAME,
218 inner_tarball.as_slice(),
219 )?;
220
221 tar.finish()?;
222
223 Ok(tar.into_inner()?.finish()?)
224 }
225
226 pub fn build<P>(&mut self, output: P) -> Result<PathBuf>
229 where
230 P: AsRef<Path>,
231 {
232 let package_dirname = PathBuf::from(self.package_dirname()?);
233
234 self.spec.files.iter_mut().try_for_each(|pkg_src_loc| {
239 pkg_src_loc.0 = package_dirname
240 .join(&pkg_src_loc.0)
241 .to_str()
242 .ok_or_else(|| Error::PathConversionError {
243 path: package_dirname.join(&pkg_src_loc.0),
244 })?
245 .to_string();
246 Ok::<(), Error>(())
247 })?;
248
249 let tarball = self.create_tarball()?;
250 let path = output.as_ref().join(self.package_filename());
251
252 write(&path, tarball).map_err(|e| Error::WritePackageError {
253 path: path.clone(),
254 source: e,
255 })?;
256
257 Ok(path)
258 }
259}