|
1 | 1 | use bytes::Bytes; |
2 | | -use camino::Utf8Path; |
3 | 2 | use camino::Utf8PathBuf; |
| 3 | +use flate2::read::GzDecoder; |
4 | 4 | use futures_util::StreamExt; |
5 | 5 | use futures_util::TryStreamExt; |
6 | 6 | use reqwest::Client; |
7 | 7 | use rv_lockfile::datatypes::GemSection; |
| 8 | +use rv_lockfile::datatypes::GemVersion; |
8 | 9 | use rv_lockfile::datatypes::GemfileDotLock; |
9 | 10 | use rv_lockfile::datatypes::Spec; |
10 | 11 | use url::Url; |
@@ -82,7 +83,7 @@ fn install_gems(downloaded: Vec<Downloaded>) -> Result<()> { |
82 | 83 | let bundle_path = find_bundle_path()?; |
83 | 84 | // 2. Unpack all the tarballs |
84 | 85 | for download in downloaded { |
85 | | - download.unpack_tarball(&bundle_path)?; |
| 86 | + download.unpack_tarball(bundle_path.clone())?; |
86 | 87 | } |
87 | 88 | // 3. Generate binstubs into DIR/bin/ |
88 | 89 | // 4. Handle compiling native extensions for gems with native extensions |
@@ -131,37 +132,65 @@ struct Downloaded<'i> { |
131 | 132 | } |
132 | 133 |
|
133 | 134 | impl<'i> Downloaded<'i> { |
134 | | - fn unpack_tarball(self, bundle_path: &Utf8Path) -> Result<()> { |
| 135 | + fn unpack_tarball(self, bundle_path: Utf8PathBuf) -> Result<()> { |
| 136 | + eprintln!("Unpacking {bundle_path}"); |
135 | 137 | // Unpack the tarball into DIR/gems/ |
136 | | - // each inner tarball inside a .gem goes into a directory that uses |
137 | | - // the gem's name tuple of NAME-VERSION(-PLATFORM), like this: |
138 | | - // nokogiri-1.18.10-arm64-darwin racc-1.8.1 rack-3.2.3 rake-13.3.0 |
139 | | - // So first let's find the directory: |
140 | | - let name = self.spec.gem_version.name; |
141 | | - let version = self.spec.gem_version.version; |
142 | | - let this_gem_dir = format!("{name}/{version}"); |
143 | | - let gem_dst = bundle_path.join("gems").join(this_gem_dir); |
144 | | - std::fs::create_dir_all(&gem_dst)?; |
| 138 | + // It should contain a metadata zip, and a data zip |
| 139 | + // (and optionally, a checksum zip). |
| 140 | + let GemVersion { name, version } = self.spec.gem_version; |
| 141 | + let nameversion = format!("{name}-{version}"); |
145 | 142 |
|
146 | 143 | // Then unpack the tarball into it. |
147 | 144 | let contents = std::io::Cursor::new(self.contents); |
148 | 145 | let mut archive = tar::Archive::new(contents); |
149 | | - eprintln!("Unpacking gem tarball to {gem_dst}"); |
150 | 146 | for e in archive.entries()? { |
151 | | - let mut entry = e?; |
| 147 | + let entry = e?; |
152 | 148 | let entry_path = entry.path()?; |
153 | 149 | match entry_path.display().to_string().as_str() { |
154 | 150 | "metadata.gz" => { |
155 | | - // Idk what to do with this. |
| 151 | + eprintln!("\tData archive"); |
| 152 | + // Unzip the metadata file, |
| 153 | + // then write it to |
| 154 | + // BUNDLEPATH/specifications/name-version.gemspec |
| 155 | + |
| 156 | + // First, create the destination. |
| 157 | + let metadata_dir = bundle_path.join("specifications/"); |
| 158 | + std::fs::create_dir_all(&metadata_dir)?; |
| 159 | + let filename = format!("{nameversion}.gemspec"); |
| 160 | + let dst_path = metadata_dir.join(filename); |
| 161 | + let mut dst = std::fs::File::create(dst_path)?; |
| 162 | + |
| 163 | + // Then write the (unzipped) source into the destination. |
| 164 | + let mut unzipped_contents = GzDecoder::new(entry); |
| 165 | + std::io::copy(&mut unzipped_contents, &mut dst)?; |
156 | 166 | } |
157 | 167 | "data.tar.gz" => { |
158 | | - // TODO: Unpack this data |
| 168 | + // for every ENTRY in the data tar, unpack it to |
| 169 | + // data.tar.gz => BUNDLEPATH/gems/name-version/ENTRY |
| 170 | + let data_dir: std::path::PathBuf = |
| 171 | + bundle_path.join("gems").join(&nameversion).into(); |
| 172 | + std::fs::create_dir_all(&data_dir)?; |
| 173 | + let mut gem_data_archive = tar::Archive::new(GzDecoder::new(entry)); |
| 174 | + eprintln!("\tData archive"); |
| 175 | + for e in gem_data_archive.entries()? { |
| 176 | + let mut entry = e?; |
| 177 | + let entry_path = entry.path()?; |
| 178 | + let dst = data_dir.join(entry_path); |
| 179 | + |
| 180 | + eprintln!("\t\tUnpacking to {}", dst.display()); |
| 181 | + // Not sure if this is strictly necessary, or if we can know the |
| 182 | + // intermediate directories ahead of time. |
| 183 | + if let Some(dst_parent) = dst.parent() { |
| 184 | + std::fs::create_dir_all(dst_parent)?; |
| 185 | + } |
| 186 | + entry.unpack(dst)?; |
| 187 | + } |
159 | 188 | } |
160 | 189 | "checksums.yaml.gz" => { |
161 | 190 | // TODO: Validate these checksums |
162 | 191 | } |
163 | | - _ => { |
164 | | - // Unknown entry, just ignore it. |
| 192 | + other => { |
| 193 | + eprintln!("Unknown dir {other}") |
165 | 194 | } |
166 | 195 | } |
167 | 196 | } |
|
0 commit comments