Skip to content

Commit edd0d61

Browse files
ref(uploads): Split build_artifact_bundle into separate module (#2922)
### Description Move `build_artifact_bundle` (and functions only it calls) out of `crate::utils::file_upload` and into a new module called `crate::utils::source_bundle`, also renaming the function to `build`. This change only moves code around; it is done in preparation for the next PR. ### Issues - See #2927 - See [CLI-220](https://linear.app/getsentry/issue/CLI-220/dont-call-chunk-upload-for-bundle-jvm)
1 parent 6436402 commit edd0d61

File tree

3 files changed

+230
-213
lines changed

3 files changed

+230
-213
lines changed

src/utils/file_upload.rs

Lines changed: 4 additions & 213 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,22 @@
22
use std::collections::{BTreeMap, HashMap};
33
use std::ffi::OsStr;
44
use std::fmt::{self, Display};
5-
use std::io::BufWriter;
65
use std::path::PathBuf;
76
use std::str;
87
use std::sync::Arc;
98
use std::time::{Duration, Instant};
109

1110
use anyhow::{anyhow, bail, Result};
1211
use console::style;
13-
use log::info;
1412
use parking_lot::RwLock;
1513
use rayon::prelude::*;
1614
use rayon::ThreadPoolBuilder;
1715
use sentry::types::DebugId;
1816
use sha1_smol::Digest;
1917
use symbolic::common::ByteView;
2018
use symbolic::debuginfo::js;
21-
use symbolic::debuginfo::sourcebundle::{
22-
SourceBundleErrorKind, SourceBundleWriter, SourceFileInfo, SourceFileType,
23-
};
19+
use symbolic::debuginfo::sourcebundle::SourceFileType;
2420
use thiserror::Error;
25-
use url::Url;
2621

2722
use crate::api::NewRelease;
2823
use crate::api::{Api, ChunkServerOptions, ChunkUploadCapability};
@@ -31,6 +26,7 @@ use crate::utils::chunks::{upload_chunks, Chunk, ASSEMBLE_POLL_INTERVAL};
3126
use crate::utils::fs::{get_sha1_checksums, TempFile};
3227
use crate::utils::non_empty::NonEmptySlice;
3328
use crate::utils::progress::{ProgressBar, ProgressBarMode, ProgressStyle};
29+
use crate::utils::source_bundle;
3430

3531
use super::file_search::ReleaseFileMatch;
3632

@@ -415,7 +411,7 @@ impl<'a> FileUpload<'a> {
415411
}
416412

417413
pub fn build_jvm_bundle(&self, debug_id: Option<DebugId>) -> Result<TempFile> {
418-
build_artifact_bundle(self.context, &self.files, debug_id)
414+
source_bundle::build(self.context, &self.files, debug_id)
419415
}
420416
}
421417

@@ -622,7 +618,7 @@ fn upload_files_chunked(
622618
files: &SourceFiles,
623619
options: &ChunkServerOptions,
624620
) -> Result<()> {
625-
let archive = build_artifact_bundle(context, files, None)?;
621+
let archive = source_bundle::build(context, files, None)?;
626622

627623
let progress_style =
628624
ProgressStyle::default_spinner().template("{spinner} Optimizing bundle for upload...");
@@ -678,137 +674,6 @@ fn upload_files_chunked(
678674
poll_assemble(checksum, &checksums, context, options)
679675
}
680676

681-
/// Creates a debug id from a map of source files by hashing each file's
682-
/// URL, contents, type, and headers.
683-
fn build_debug_id(files: &SourceFiles) -> DebugId {
684-
let mut hash = sha1_smol::Sha1::new();
685-
for source_file in files.values() {
686-
hash.update(source_file.url.as_bytes());
687-
hash.update(&source_file.contents);
688-
hash.update(format!("{:?}", source_file.ty).as_bytes());
689-
690-
for (key, value) in &source_file.headers {
691-
hash.update(key.as_bytes());
692-
hash.update(value.as_bytes());
693-
}
694-
}
695-
696-
let mut sha1_bytes = [0u8; 16];
697-
sha1_bytes.copy_from_slice(&hash.digest().bytes()[..16]);
698-
DebugId::from_uuid(uuid::Builder::from_sha1_bytes(sha1_bytes).into_uuid())
699-
}
700-
701-
fn build_artifact_bundle(
702-
context: &UploadContext,
703-
files: &SourceFiles,
704-
debug_id: Option<DebugId>,
705-
) -> Result<TempFile> {
706-
let progress_style = ProgressStyle::default_bar().template(
707-
"{prefix:.dim} Bundling files for upload... {msg:.dim}\
708-
\n{wide_bar} {pos}/{len}",
709-
);
710-
711-
let pb = ProgressBar::new(files.len());
712-
pb.set_style(progress_style);
713-
pb.set_prefix(">");
714-
715-
let archive = TempFile::create()?;
716-
let mut bundle = SourceBundleWriter::start(BufWriter::new(archive.open()?))?;
717-
718-
// artifact bundles get a random UUID as debug id
719-
let debug_id = debug_id.unwrap_or_else(|| build_debug_id(files));
720-
bundle.set_attribute("debug_id", debug_id.to_string());
721-
722-
if let Some(note) = context.note {
723-
bundle.set_attribute("note", note.to_owned());
724-
}
725-
726-
bundle.set_attribute("org".to_owned(), context.org.to_owned());
727-
if let Some([project]) = context.projects.as_deref() {
728-
// Only set project if there is exactly one project
729-
bundle.set_attribute("project".to_owned(), project);
730-
}
731-
if let Some(release) = context.release {
732-
bundle.set_attribute("release".to_owned(), release.to_owned());
733-
}
734-
if let Some(dist) = context.dist {
735-
bundle.set_attribute("dist".to_owned(), dist.to_owned());
736-
}
737-
738-
let mut bundle_file_count = 0;
739-
740-
for file in files.values() {
741-
pb.inc(1);
742-
pb.set_message(&file.url);
743-
744-
let mut info = SourceFileInfo::new();
745-
info.set_ty(file.ty);
746-
info.set_url(file.url.clone());
747-
for (k, v) in &file.headers {
748-
info.add_header(k.clone(), v.clone());
749-
}
750-
751-
let bundle_path = url_to_bundle_path(&file.url)?;
752-
if let Err(e) = bundle.add_file(bundle_path, file.contents.as_slice(), info) {
753-
if e.kind() == SourceBundleErrorKind::ReadFailed {
754-
info!(
755-
"Skipping {} because it is not valid UTF-8.",
756-
file.path.display()
757-
);
758-
continue;
759-
} else {
760-
return Err(e.into());
761-
}
762-
}
763-
bundle_file_count += 1;
764-
}
765-
766-
bundle.finish()?;
767-
768-
pb.finish_with_duration("Bundling");
769-
770-
println!(
771-
"{} Bundled {} {} for upload",
772-
style(">").dim(),
773-
style(bundle_file_count).yellow(),
774-
match bundle_file_count {
775-
1 => "file",
776-
_ => "files",
777-
}
778-
);
779-
780-
println!(
781-
"{} Bundle ID: {}",
782-
style(">").dim(),
783-
style(debug_id).yellow(),
784-
);
785-
786-
Ok(archive)
787-
}
788-
789-
fn url_to_bundle_path(url: &str) -> Result<String> {
790-
let base = Url::parse("http://~").expect("this url is valid");
791-
let url = if let Some(rest) = url.strip_prefix("~/") {
792-
base.join(rest)?
793-
} else {
794-
base.join(url)?
795-
};
796-
797-
let mut path = url.path().to_owned();
798-
if let Some(fragment) = url.fragment() {
799-
path = format!("{path}#{fragment}");
800-
}
801-
if path.starts_with('/') {
802-
path.remove(0);
803-
}
804-
805-
Ok(match url.host_str() {
806-
Some("~") => format!("_/_/{path}"),
807-
Some(host) => format!("{}/{host}/{path}", url.scheme()),
808-
None => format!("{}/_/{path}", url.scheme()),
809-
})
810-
}
811-
812677
fn print_upload_context_details(context: &UploadContext) {
813678
println!(
814679
"{} {}",
@@ -853,77 +718,3 @@ fn is_hermes_bytecode(slice: &[u8]) -> bool {
853718
const HERMES_MAGIC: [u8; 8] = [0xC6, 0x1F, 0xBC, 0x03, 0xC1, 0x03, 0x19, 0x1F];
854719
slice.starts_with(&HERMES_MAGIC)
855720
}
856-
857-
#[cfg(test)]
858-
mod tests {
859-
use sha1_smol::Sha1;
860-
861-
use super::*;
862-
863-
#[test]
864-
fn test_url_to_bundle_path() {
865-
assert_eq!(url_to_bundle_path("~/bar").unwrap(), "_/_/bar");
866-
assert_eq!(url_to_bundle_path("~/foo/bar").unwrap(), "_/_/foo/bar");
867-
assert_eq!(
868-
url_to_bundle_path("~/dist/js/bundle.js.map").unwrap(),
869-
"_/_/dist/js/bundle.js.map"
870-
);
871-
assert_eq!(
872-
url_to_bundle_path("~/babel.config.js").unwrap(),
873-
"_/_/babel.config.js"
874-
);
875-
876-
assert_eq!(url_to_bundle_path("~/#/bar").unwrap(), "_/_/#/bar");
877-
assert_eq!(url_to_bundle_path("~/foo/#/bar").unwrap(), "_/_/foo/#/bar");
878-
assert_eq!(
879-
url_to_bundle_path("~/dist/#js/bundle.js.map").unwrap(),
880-
"_/_/dist/#js/bundle.js.map"
881-
);
882-
assert_eq!(
883-
url_to_bundle_path("~/#foo/babel.config.js").unwrap(),
884-
"_/_/#foo/babel.config.js"
885-
);
886-
}
887-
888-
#[test]
889-
fn build_artifact_bundle_deterministic() {
890-
let projects_slice = &["wat-project".into()];
891-
let context = UploadContext {
892-
org: "wat-org",
893-
projects: Some(projects_slice.into()),
894-
release: None,
895-
dist: None,
896-
note: None,
897-
wait: false,
898-
max_wait: DEFAULT_MAX_WAIT,
899-
chunk_upload_options: None,
900-
};
901-
902-
let source_files = ["bundle.min.js.map", "vendor.min.js.map"]
903-
.into_iter()
904-
.map(|name| {
905-
let file = SourceFile {
906-
url: format!("~/{name}"),
907-
path: format!("tests/integration/_fixtures/{name}").into(),
908-
contents: std::fs::read(format!("tests/integration/_fixtures/{name}"))
909-
.unwrap()
910-
.into(),
911-
ty: SourceFileType::SourceMap,
912-
headers: Default::default(),
913-
messages: Default::default(),
914-
already_uploaded: false,
915-
};
916-
(format!("~/{name}"), file)
917-
})
918-
.collect();
919-
920-
let file = build_artifact_bundle(&context, &source_files, None).unwrap();
921-
922-
let buf = std::fs::read(file.path()).unwrap();
923-
let hash = Sha1::from(buf);
924-
assert_eq!(
925-
hash.digest().to_string(),
926-
"f0e25ae149b711c510148e022ebc883ad62c7c4c"
927-
);
928-
}
929-
}

src/utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub mod progress;
2121
pub mod proguard;
2222
pub mod releases;
2323
pub mod retry;
24+
pub mod source_bundle;
2425
pub mod sourcemaps;
2526
pub mod system;
2627
pub mod ui;

0 commit comments

Comments
 (0)