Skip to content

Commit e5a54f1

Browse files
runningcodeclaude
andcommitted
feat(build): Add CLI parameters to track plugin versions (EME-XXX)
Add support for tracking Gradle and Fastlane plugin versions in build uploads by accepting them as CLI parameters and writing them to the metadata file in the uploaded zip archive. This enables the backend to store plugin version information in the PreprodArtifact database table for size analysis and build distribution tracking. Changes: - Add --gradle-plugin-version and --fastlane-plugin-version CLI parameters - Write plugin versions to .sentry-cli-metadata.txt in uploaded zips - Update write_version_metadata() to accept optional plugin versions - Add test coverage for metadata file generation with plugin versions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent beefb7d commit e5a54f1

File tree

4 files changed

+117
-20
lines changed

4 files changed

+117
-20
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Improvements
66

7+
- The `sentry-cli build upload` command now accepts `--gradle-plugin-version` and `--fastlane-plugin-version` parameters to track plugin versions ([#2994](https://github.com/getsentry/sentry-cli/pull/2994)). These versions are written to the `.sentry-cli-metadata.txt` file in uploaded build archives, enabling the backend to store plugin version information for size analysis and build distribution tracking.
78
- The `sentry-cli build upload` command now automatically detects the correct branch or tag reference in non-PR GitHub Actions workflows ([#2976](https://github.com/getsentry/sentry-cli/pull/2976)). Previously, `--head-ref` was only auto-detected for pull request workflows. Now it works for push, release, and other workflow types by using the `GITHUB_REF_NAME` environment variable.
89

910
## 2.58.2

src/commands/build/upload.rs

Lines changed: 97 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,16 @@ pub fn make_command(command: Command) -> Command {
106106
.long("release-notes")
107107
.help("The release notes to use for the upload.")
108108
)
109+
.arg(
110+
Arg::new("gradle_plugin_version")
111+
.long("gradle-plugin-version")
112+
.help("The version of the Sentry Gradle plugin used to build the artifact.")
113+
)
114+
.arg(
115+
Arg::new("fastlane_plugin_version")
116+
.long("fastlane-plugin-version")
117+
.help("The version of the Sentry Fastlane plugin used to build the artifact.")
118+
)
109119
}
110120

111121
pub fn execute(matches: &ArgMatches) -> Result<()> {
@@ -280,6 +290,10 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
280290

281291
let build_configuration = matches.get_one("build_configuration").map(String::as_str);
282292
let release_notes = matches.get_one("release_notes").map(String::as_str);
293+
let gradle_plugin_version = matches.get_one("gradle_plugin_version").map(String::as_str);
294+
let fastlane_plugin_version = matches
295+
.get_one("fastlane_plugin_version")
296+
.map(String::as_str);
283297

284298
let api = Api::current();
285299
let authenticated_api = api.authenticated()?;
@@ -302,15 +316,22 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
302316

303317
let normalized_zip = if path.is_file() {
304318
debug!("Normalizing file: {}", path.display());
305-
handle_file(path, &byteview)?
319+
handle_file(
320+
path,
321+
&byteview,
322+
gradle_plugin_version,
323+
fastlane_plugin_version,
324+
)?
306325
} else if path.is_dir() {
307326
debug!("Normalizing directory: {}", path.display());
308-
handle_directory(path).with_context(|| {
309-
format!(
310-
"Failed to generate uploadable bundle for directory {}",
311-
path.display()
312-
)
313-
})?
327+
handle_directory(path, gradle_plugin_version, fastlane_plugin_version).with_context(
328+
|| {
329+
format!(
330+
"Failed to generate uploadable bundle for directory {}",
331+
path.display()
332+
)
333+
},
334+
)?
314335
} else {
315336
Err(anyhow!(
316337
"Path {} is neither a file nor a directory, cannot upload",
@@ -400,18 +421,31 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
400421
Ok(())
401422
}
402423

403-
fn handle_file(path: &Path, byteview: &ByteView) -> Result<TempFile> {
424+
fn handle_file(
425+
path: &Path,
426+
byteview: &ByteView,
427+
gradle_plugin_version: Option<&str>,
428+
fastlane_plugin_version: Option<&str>,
429+
) -> Result<TempFile> {
404430
// Handle IPA files by converting them to XCArchive
405431
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
406432
if is_zip_file(byteview) && is_ipa_file(byteview)? {
407433
debug!("Converting IPA file to XCArchive structure");
408434
let archive_temp_dir = TempDir::create()?;
409435
return ipa_to_xcarchive(path, byteview, &archive_temp_dir)
410-
.and_then(|path| handle_directory(&path))
436+
.and_then(|path| {
437+
handle_directory(&path, gradle_plugin_version, fastlane_plugin_version)
438+
})
411439
.with_context(|| format!("Failed to process IPA file {}", path.display()));
412440
}
413441

414-
normalize_file(path, byteview).with_context(|| {
442+
normalize_file(
443+
path,
444+
byteview,
445+
gradle_plugin_version,
446+
fastlane_plugin_version,
447+
)
448+
.with_context(|| {
415449
format!(
416450
"Failed to generate uploadable bundle for file {}",
417451
path.display()
@@ -465,7 +499,12 @@ fn validate_is_supported_build(path: &Path, bytes: &[u8]) -> Result<()> {
465499
}
466500

467501
// For APK and AAB files, we'll copy them directly into the zip
468-
fn normalize_file(path: &Path, bytes: &[u8]) -> Result<TempFile> {
502+
fn normalize_file(
503+
path: &Path,
504+
bytes: &[u8],
505+
gradle_plugin_version: Option<&str>,
506+
fastlane_plugin_version: Option<&str>,
507+
) -> Result<TempFile> {
469508
debug!("Creating normalized zip for file: {}", path.display());
470509

471510
let temp_file = TempFile::create()?;
@@ -489,20 +528,29 @@ fn normalize_file(path: &Path, bytes: &[u8]) -> Result<TempFile> {
489528
zip.start_file(file_name, options)?;
490529
zip.write_all(bytes)?;
491530

492-
write_version_metadata(&mut zip)?;
531+
write_version_metadata(&mut zip, gradle_plugin_version, fastlane_plugin_version)?;
493532

494533
zip.finish()?;
495534
debug!("Successfully created normalized zip for file");
496535
Ok(temp_file)
497536
}
498537

499-
fn handle_directory(path: &Path) -> Result<TempFile> {
538+
fn handle_directory(
539+
path: &Path,
540+
gradle_plugin_version: Option<&str>,
541+
fastlane_plugin_version: Option<&str>,
542+
) -> Result<TempFile> {
500543
let temp_dir = TempDir::create()?;
501544
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
502545
if is_apple_app(path)? {
503546
handle_asset_catalogs(path, temp_dir.path());
504547
}
505-
normalize_directory(path, temp_dir.path())
548+
normalize_directory(
549+
path,
550+
temp_dir.path(),
551+
gradle_plugin_version,
552+
fastlane_plugin_version,
553+
)
506554
}
507555

508556
/// Returns artifact url if upload was successful.
@@ -638,7 +686,7 @@ mod tests {
638686
fs::create_dir_all(test_dir.join("Products"))?;
639687
fs::write(test_dir.join("Products").join("app.txt"), "test content")?;
640688

641-
let result_zip = normalize_directory(&test_dir, temp_dir.path())?;
689+
let result_zip = normalize_directory(&test_dir, temp_dir.path(), None, None)?;
642690
let zip_file = fs::File::open(result_zip.path())?;
643691
let mut archive = ZipArchive::new(zip_file)?;
644692
let file = archive.by_index(0)?;
@@ -654,7 +702,7 @@ mod tests {
654702
let xcarchive_path = Path::new("tests/integration/_fixtures/build/archive.xcarchive");
655703

656704
// Process the XCArchive directory
657-
let result = handle_directory(xcarchive_path)?;
705+
let result = handle_directory(xcarchive_path, None, None)?;
658706

659707
// Verify the resulting zip contains parsed assets
660708
let zip_file = fs::File::open(result.path())?;
@@ -687,7 +735,7 @@ mod tests {
687735
let byteview = ByteView::open(ipa_path)?;
688736

689737
// Process the IPA file - this should work even without asset catalogs
690-
let result = handle_file(ipa_path, &byteview)?;
738+
let result = handle_file(ipa_path, &byteview, None, None)?;
691739

692740
let zip_file = fs::File::open(result.path())?;
693741
let mut archive = ZipArchive::new(zip_file)?;
@@ -724,7 +772,7 @@ mod tests {
724772
let symlink_path = test_dir.join("Products").join("app_link.txt");
725773
symlink("app.txt", &symlink_path)?;
726774

727-
let result_zip = normalize_directory(&test_dir, temp_dir.path())?;
775+
let result_zip = normalize_directory(&test_dir, temp_dir.path(), None, None)?;
728776
let zip_file = fs::File::open(result_zip.path())?;
729777
let mut archive = ZipArchive::new(zip_file)?;
730778

@@ -757,4 +805,35 @@ mod tests {
757805
assert!(has_symlink, "Symlink should be preserved in zip");
758806
Ok(())
759807
}
808+
809+
#[test]
810+
fn test_metadata_includes_plugin_versions() -> Result<()> {
811+
let temp_dir = crate::utils::fs::TempDir::create()?;
812+
let test_dir = temp_dir.path().join("TestApp.xcarchive");
813+
fs::create_dir_all(test_dir.join("Products"))?;
814+
fs::write(test_dir.join("Products").join("app.txt"), "test content")?;
815+
816+
let result_zip =
817+
normalize_directory(&test_dir, temp_dir.path(), Some("4.12.0"), Some("1.2.3"))?;
818+
let zip_file = fs::File::open(result_zip.path())?;
819+
let mut archive = ZipArchive::new(zip_file)?;
820+
821+
// Find and read the metadata file
822+
let metadata_file = archive.by_name(".sentry-cli-metadata.txt")?;
823+
let metadata_content = std::io::read_to_string(metadata_file)?;
824+
825+
assert!(
826+
metadata_content.contains("sentry-cli-version:"),
827+
"Metadata should contain sentry-cli-version"
828+
);
829+
assert!(
830+
metadata_content.contains("gradle-plugin-version: 4.12.0"),
831+
"Metadata should contain gradle-plugin-version"
832+
);
833+
assert!(
834+
metadata_content.contains("fastlane-plugin-version: 1.2.3"),
835+
"Metadata should contain fastlane-plugin-version"
836+
);
837+
Ok(())
838+
}
760839
}

src/utils/build/normalize.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,17 +94,30 @@ fn metadata_file_options() -> SimpleFileOptions {
9494

9595
pub fn write_version_metadata<W: std::io::Write + std::io::Seek>(
9696
zip: &mut ZipWriter<W>,
97+
gradle_plugin_version: Option<&str>,
98+
fastlane_plugin_version: Option<&str>,
9799
) -> Result<()> {
98100
let version = get_version();
99101
zip.start_file(".sentry-cli-metadata.txt", metadata_file_options())?;
100102
writeln!(zip, "sentry-cli-version: {version}")?;
103+
if let Some(gradle_version) = gradle_plugin_version {
104+
writeln!(zip, "gradle-plugin-version: {gradle_version}")?;
105+
}
106+
if let Some(fastlane_version) = fastlane_plugin_version {
107+
writeln!(zip, "fastlane-plugin-version: {fastlane_version}")?;
108+
}
101109
Ok(())
102110
}
103111

104112
// For XCArchive directories, we'll zip the entire directory
105113
// It's important to not change the contents of the directory or the size
106114
// analysis will be wrong and the code signature will break.
107-
pub fn normalize_directory(path: &Path, parsed_assets_path: &Path) -> Result<TempFile> {
115+
pub fn normalize_directory(
116+
path: &Path,
117+
parsed_assets_path: &Path,
118+
gradle_plugin_version: Option<&str>,
119+
fastlane_plugin_version: Option<&str>,
120+
) -> Result<TempFile> {
108121
debug!("Creating normalized zip for directory: {}", path.display());
109122

110123
let temp_file = TempFile::create()?;
@@ -133,7 +146,7 @@ pub fn normalize_directory(path: &Path, parsed_assets_path: &Path) -> Result<Tem
133146
)?;
134147
}
135148

136-
write_version_metadata(&mut zip)?;
149+
write_version_metadata(&mut zip, gradle_plugin_version, fastlane_plugin_version)?;
137150

138151
zip.finish()?;
139152
debug!("Successfully created normalized zip for directory with {file_count} files");

tests/integration/_cases/build/build-upload-help-macos.trycmd

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ Options:
5353
be used.
5454
--release-notes <release_notes>
5555
The release notes to use for the upload.
56+
--gradle-plugin-version <gradle_plugin_version>
57+
The version of the Sentry Gradle plugin used to build the artifact.
58+
--fastlane-plugin-version <fastlane_plugin_version>
59+
The version of the Sentry Fastlane plugin used to build the artifact.
5660
-h, --help
5761
Print help
5862

0 commit comments

Comments
 (0)