sentry-cli 3.4.0__tar.gz → 3.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/Cargo.lock +1 -1
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/Cargo.toml +1 -1
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/PKG-INFO +1 -1
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/sentry_cli.egg-info/PKG-INFO +1 -1
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/data_types/snapshots.rs +2 -2
- sentry_cli-3.4.1/src/commands/debug_files/bundle_jvm.rs +535 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/file_search.rs +14 -0
- sentry_cli-3.4.0/src/commands/debug_files/bundle_jvm.rs +0 -250
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/LICENSE +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/MANIFEST.in +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/README.md +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/apple-catalog-parsing/AGENTS.md +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/apple-catalog-parsing/CLAUDE.md +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/apple-catalog-parsing/Cargo.toml +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/apple-catalog-parsing/build.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/apple-catalog-parsing/native/swift/AssetCatalogParser/Package.swift +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/apple-catalog-parsing/native/swift/AssetCatalogParser/Sources/AssetCatalogParser/AssetCatalogReader.swift +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/apple-catalog-parsing/native/swift/AssetCatalogParser/Sources/ObjcSupport/include/safeValueForKey.h +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/apple-catalog-parsing/native/swift/AssetCatalogParser/Sources/ObjcSupport/safeValueForKey.m +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/apple-catalog-parsing/native/swift/AssetCatalogParser/Tests/AssetCatalogParserTests/AssetCatalogParserTests.swift +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/apple-catalog-parsing/native/swift/AssetCatalogParser/Tests/AssetCatalogParserTests/Resources/test.xcarchive/Info.plist +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/apple-catalog-parsing/native/swift/AssetCatalogParser/Tests/AssetCatalogParserTests/Resources/test.xcarchive/Products/Applications/DemoApp.app/Assets.car +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/apple-catalog-parsing/src/asset_catalog.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/apple-catalog-parsing/src/lib.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/build.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/pyproject.toml +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/sentry_cli.egg-info/SOURCES.txt +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/sentry_cli.egg-info/dependency_links.txt +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/sentry_cli.egg-info/top_level.txt +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/setup.cfg +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/setup.py +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/AGENTS.md +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/CLAUDE.md +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/connection_manager.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/data_types/chunking/artifact.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/data_types/chunking/build.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/data_types/chunking/compression.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/data_types/chunking/dif.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/data_types/chunking/file_state.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/data_types/chunking/hash_algorithm.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/data_types/chunking/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/data_types/chunking/upload/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/data_types/chunking/upload/options.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/data_types/code_mappings.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/data_types/deploy.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/data_types/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/encoding.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/envelopes_api.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/errors/api_error.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/errors/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/errors/sentry_error.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/pagination.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/api/serialization.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/bashsupport.sh +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/bash_hook.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/build/download.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/build/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/build/snapshots.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/build/upload.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/code_mappings/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/code_mappings/upload.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/dart_symbol_map/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/dart_symbol_map/upload.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/debug_files/bundle_sources.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/debug_files/check.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/debug_files/find.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/debug_files/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/debug_files/print_sources.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/debug_files/upload.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/deploys/list.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/deploys/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/deploys/new.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/derive_parser.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/events/list.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/events/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/info.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/issues/list.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/issues/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/issues/mute.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/issues/resolve.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/issues/unresolve.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/login.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/logs/list.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/logs/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/monitors/list.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/monitors/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/monitors/run.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/organizations/list.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/organizations/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/proguard/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/proguard/upload.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/proguard/uuid.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/projects/list.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/projects/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/react_native/gradle.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/react_native/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/react_native/xcode.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/releases/archive.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/releases/delete.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/releases/finalize.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/releases/info.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/releases/list.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/releases/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/releases/new.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/releases/propose_version.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/releases/restore.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/releases/set_commits.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/repos/list.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/repos/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/send_envelope.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/send_event.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/send_metric/common_args.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/send_metric/increment.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/send_metric/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/send_metric/set.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/sourcemaps/inject.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/sourcemaps/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/sourcemaps/resolve.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/sourcemaps/upload.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/uninstall.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/update.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/upload_dif.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/upload_dsym.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/commands/upload_proguard.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/config.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/constants.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/main.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/android.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/args.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/auth_token/auth_token_impl.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/auth_token/error.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/auth_token/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/auth_token/org_auth_token.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/auth_token/redacting.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/auth_token/test.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/auth_token/user_auth_token.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/build/apple.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/build/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/build/normalize.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/build/validation.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/build_vcs.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/chunks/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/chunks/options.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/chunks/types.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/chunks/upload.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/ci.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/cordova.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/dif.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/dif_upload/error.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/dif_upload/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/event.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/file_upload.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/formatting.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/fs.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/http.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/logging.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/non_empty.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/progress.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/proguard/mapping.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/proguard/mod.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/proguard/upload.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/releases.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/retry.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/snapshots/sentry_cli__utils__vcs__tests__generate_patch_default_twenty.snap +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/snapshots/sentry_cli__utils__vcs__tests__generate_patch_ignore_missing.snap +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/snapshots/sentry_cli__utils__vcs__tests__generate_patch_set_base.snap +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/snapshots/sentry_cli__utils__vcs__tests__generate_patch_set_previous_commit.snap +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/snapshots/sentry_cli__utils__vcs__tests__get_commits_from_git.snap +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/source_bundle.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/sourcemaps/inject.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/sourcemaps.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/system.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/ui.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/update.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/value_parsers.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/vcs.rs +0 -0
- {sentry_cli-3.4.0 → sentry_cli-3.4.1}/src/utils/xcode.rs +0 -0
|
@@ -40,8 +40,8 @@ pub struct SnapshotsManifest<'a> {
|
|
|
40
40
|
// Keep in sync with https://github.com/getsentry/sentry/blob/master/src/sentry/preprod/snapshots/manifest.py
|
|
41
41
|
/// Metadata for a single image in a snapshot manifest.
|
|
42
42
|
///
|
|
43
|
-
/// CLI-managed fields (`
|
|
44
|
-
///
|
|
43
|
+
/// CLI-managed fields (`width`, `height`) override any identically named
|
|
44
|
+
/// fields provided by user sidecar metadata.
|
|
45
45
|
#[derive(Debug, Serialize)]
|
|
46
46
|
pub struct ImageMetadata {
|
|
47
47
|
#[serde(flatten)]
|
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
#![expect(clippy::unwrap_used, reason = "contains legacy code which uses unwrap")]
|
|
2
|
+
|
|
3
|
+
use crate::config::Config;
|
|
4
|
+
use crate::utils::args::ArgExt as _;
|
|
5
|
+
use crate::utils::file_search::{ReleaseFileMatch, ReleaseFileSearch};
|
|
6
|
+
use crate::utils::file_upload::SourceFile;
|
|
7
|
+
use crate::utils::fs::path_as_url;
|
|
8
|
+
use crate::utils::source_bundle::{self, BundleContext};
|
|
9
|
+
use anyhow::{bail, Context as _, Result};
|
|
10
|
+
use clap::{Arg, ArgAction, ArgMatches, Command};
|
|
11
|
+
use log::{debug, warn};
|
|
12
|
+
use regex::Regex;
|
|
13
|
+
use sentry::types::DebugId;
|
|
14
|
+
use std::collections::hash_map::Entry;
|
|
15
|
+
use std::collections::{BTreeMap, HashMap};
|
|
16
|
+
use std::ffi::OsStr;
|
|
17
|
+
use std::fs;
|
|
18
|
+
use std::path::{Path, PathBuf};
|
|
19
|
+
use std::str::FromStr as _;
|
|
20
|
+
use std::sync::{Arc, LazyLock};
|
|
21
|
+
use symbolic::debuginfo::sourcebundle::SourceFileType;
|
|
22
|
+
|
|
23
|
+
const JVM_EXTENSIONS: &[&str] = &[
|
|
24
|
+
"java", "kt", "scala", "sc", "groovy", "gvy", "gy", "gsh", "clj", "cljc",
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
/// Directory names that mark the root of a JVM source set (i.e. the parent of
|
|
28
|
+
/// the package hierarchy). Matches the Gradle/Maven convention
|
|
29
|
+
/// `src/<sourceset>/<lang>/<package>/...`.
|
|
30
|
+
const SOURCE_SET_LANGS: &[&str] = &["java", "kotlin", "scala", "groovy", "clojure"];
|
|
31
|
+
|
|
32
|
+
static SOURCE_SET_PREFIX_RE: LazyLock<Regex> = LazyLock::new(|| {
|
|
33
|
+
let langs = SOURCE_SET_LANGS.join("|");
|
|
34
|
+
Regex::new(&format!(
|
|
35
|
+
r"(?:^|[/\\])src[/\\][^/\\]+[/\\](?:{langs})[/\\](.+)$"
|
|
36
|
+
))
|
|
37
|
+
.expect("valid regex")
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/// Strips the `[<module>/]src/<sourceset>/<lang>/` prefix from a relative source
|
|
41
|
+
/// path so the remaining portion matches what Symbolicator looks up by URL
|
|
42
|
+
/// (e.g. `io/sentry/android/core/ANRWatchDog.java`). This is needed because
|
|
43
|
+
/// JVM stack traces reference classes by their package path, with no knowledge
|
|
44
|
+
/// of the containing Gradle module or source-set layout on disk.
|
|
45
|
+
///
|
|
46
|
+
/// Returns the path unchanged if no `src/<sourceset>/<lang>/` segment is found.
|
|
47
|
+
fn strip_source_set_prefix(relative_path: &Path) -> PathBuf {
|
|
48
|
+
relative_path
|
|
49
|
+
.to_str()
|
|
50
|
+
.and_then(|s| SOURCE_SET_PREFIX_RE.captures(s))
|
|
51
|
+
.map(|caps| PathBuf::from(&caps[1]))
|
|
52
|
+
.unwrap_or_else(|| relative_path.to_path_buf())
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Builds the Symbolicator-compatible URL for a relative source path
|
|
56
|
+
/// (e.g. `~/io/sentry/android/core/ANRWatchDog.jvm`).
|
|
57
|
+
fn build_source_url(relative_path: &Path) -> String {
|
|
58
|
+
let package_path = strip_source_set_prefix(relative_path);
|
|
59
|
+
let package_path_jvm_ext = package_path.with_extension("jvm");
|
|
60
|
+
format!("~/{}", path_as_url(&package_path_jvm_ext))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Turns walked source files into `SourceFile`s for bundling, filtering out
|
|
64
|
+
/// build-output directories and deduplicating by URL.
|
|
65
|
+
///
|
|
66
|
+
/// Android build variants can contribute the same FQCN from different source
|
|
67
|
+
/// sets (e.g. `src/main/` and `src/debug/` both defining `com.example.Foo`).
|
|
68
|
+
/// After stripping, both map to the same URL — this keeps the first-seen
|
|
69
|
+
/// entry and warns the user about the rest.
|
|
70
|
+
fn build_source_files(sources: Vec<ReleaseFileMatch>) -> Vec<SourceFile> {
|
|
71
|
+
let candidates = sources.into_iter().filter_map(|source| {
|
|
72
|
+
let local_path = source.path.strip_prefix(&source.base_path).unwrap();
|
|
73
|
+
if is_in_ambiguous_build_dir(local_path) {
|
|
74
|
+
debug!("excluding (build output): {}", source.path.display());
|
|
75
|
+
return None;
|
|
76
|
+
}
|
|
77
|
+
let url = build_source_url(local_path);
|
|
78
|
+
Some((url, source))
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
let mut seen_urls: HashMap<String, usize> = HashMap::new();
|
|
82
|
+
let mut files: Vec<SourceFile> = Vec::new();
|
|
83
|
+
|
|
84
|
+
for (url, source) in candidates {
|
|
85
|
+
match seen_urls.entry(url) {
|
|
86
|
+
Entry::Occupied(existing) => {
|
|
87
|
+
warn!(
|
|
88
|
+
"URL collision on {}: skipping '{}' (already bundled from '{}'). \
|
|
89
|
+
Use --exclude to drop the unwanted source set \
|
|
90
|
+
(e.g. --exclude='**/src/debug/**').",
|
|
91
|
+
existing.key(),
|
|
92
|
+
source.path.display(),
|
|
93
|
+
files[*existing.get()].path.display(),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
Entry::Vacant(slot) => {
|
|
97
|
+
let url = slot.key().clone();
|
|
98
|
+
slot.insert(files.len());
|
|
99
|
+
files.push(SourceFile {
|
|
100
|
+
url,
|
|
101
|
+
path: source.path,
|
|
102
|
+
contents: Arc::new(source.contents),
|
|
103
|
+
ty: SourceFileType::Source,
|
|
104
|
+
headers: BTreeMap::new(),
|
|
105
|
+
messages: vec![],
|
|
106
|
+
already_uploaded: false,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
files
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Safe to exclude globally — can never be valid JVM package names.
|
|
115
|
+
const SAFE_EXCLUDES: &[&str] = &[
|
|
116
|
+
".cxx",
|
|
117
|
+
".eclipse",
|
|
118
|
+
".fleet",
|
|
119
|
+
".gradle",
|
|
120
|
+
".idea",
|
|
121
|
+
".kotlin",
|
|
122
|
+
".mvn",
|
|
123
|
+
".settings",
|
|
124
|
+
".vscode",
|
|
125
|
+
"node_modules",
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
/// Common build output dirs that could also be valid JVM package names
|
|
129
|
+
/// (e.g. `com.example.build`). Only excluded outside of `src/` directories.
|
|
130
|
+
const AMBIGUOUS_EXCLUDES: &[&str] = &["bin", "build", "out", "target"];
|
|
131
|
+
|
|
132
|
+
/// Checks *all* ambiguous directories in the path and excludes if any of them
|
|
133
|
+
/// is not under a `src/` ancestor. Handles nested cases like
|
|
134
|
+
/// `build/src/main/java/com/example/target/Foo.java` — inner `target` is under
|
|
135
|
+
/// `src`, but outer `build` is not, so the file is excluded.
|
|
136
|
+
fn is_in_ambiguous_build_dir(relative_path: &Path) -> bool {
|
|
137
|
+
for ancestor in relative_path.ancestors() {
|
|
138
|
+
let Some(name) = ancestor.file_name().and_then(|n| n.to_str()) else {
|
|
139
|
+
continue;
|
|
140
|
+
};
|
|
141
|
+
if AMBIGUOUS_EXCLUDES.contains(&name) {
|
|
142
|
+
// Check if any ancestor *above* this directory is named "src".
|
|
143
|
+
let has_src_above = ancestor
|
|
144
|
+
.ancestors()
|
|
145
|
+
.skip(1) // skip the ambiguous dir itself
|
|
146
|
+
.any(|a| a.file_name() == Some(OsStr::new("src")));
|
|
147
|
+
if !has_src_above {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
false
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
pub fn make_command(command: Command) -> Command {
|
|
156
|
+
command
|
|
157
|
+
.hide(true) // experimental for now
|
|
158
|
+
.about(
|
|
159
|
+
"Create a source bundle for the given JVM based source files (e.g. Java, Kotlin, ...)",
|
|
160
|
+
)
|
|
161
|
+
.org_arg()
|
|
162
|
+
.project_arg(false)
|
|
163
|
+
.arg(
|
|
164
|
+
Arg::new("path")
|
|
165
|
+
.value_name("PATH")
|
|
166
|
+
.required(true)
|
|
167
|
+
.value_parser(clap::builder::PathBufValueParser::new())
|
|
168
|
+
.help("The directory containing source files to bundle."),
|
|
169
|
+
)
|
|
170
|
+
.arg(
|
|
171
|
+
Arg::new("output")
|
|
172
|
+
.long("output")
|
|
173
|
+
.value_name("PATH")
|
|
174
|
+
.required(true)
|
|
175
|
+
.value_parser(clap::builder::PathBufValueParser::new())
|
|
176
|
+
.help("The path to the output folder."),
|
|
177
|
+
)
|
|
178
|
+
.arg(
|
|
179
|
+
Arg::new("debug_id")
|
|
180
|
+
.long("debug-id")
|
|
181
|
+
.value_name("UUID")
|
|
182
|
+
.required(true)
|
|
183
|
+
.value_parser(DebugId::from_str)
|
|
184
|
+
.help("Debug ID (UUID) to use for the source bundle."),
|
|
185
|
+
)
|
|
186
|
+
.arg(
|
|
187
|
+
Arg::new("exclude")
|
|
188
|
+
.long("exclude")
|
|
189
|
+
.value_name("PATTERN")
|
|
190
|
+
.action(ArgAction::Append)
|
|
191
|
+
.help(
|
|
192
|
+
"Glob pattern to exclude files/directories. Can be repeated. \
|
|
193
|
+
By default, common build output and IDE directories are excluded \
|
|
194
|
+
(build, .gradle, target, .idea, .vscode, out, bin, etc.).",
|
|
195
|
+
),
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
pub fn execute(matches: &ArgMatches) -> Result<()> {
|
|
200
|
+
let config = Config::current();
|
|
201
|
+
let org = config.get_org(matches)?;
|
|
202
|
+
let project = config.get_project(matches).ok();
|
|
203
|
+
|
|
204
|
+
let context = BundleContext::new(&org).with_projects(project.as_slice());
|
|
205
|
+
let path = matches.get_one::<PathBuf>("path").unwrap();
|
|
206
|
+
let output_path = matches.get_one::<PathBuf>("output").unwrap();
|
|
207
|
+
let debug_id = matches.get_one::<DebugId>("debug_id").unwrap();
|
|
208
|
+
let out = output_path.join(format!("{debug_id}.zip"));
|
|
209
|
+
|
|
210
|
+
if !path.exists() {
|
|
211
|
+
bail!("Given path does not exist: {}", path.display())
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if !path.is_dir() {
|
|
215
|
+
bail!("Given path is not a directory: {}", path.display())
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if !output_path.exists() {
|
|
219
|
+
fs::create_dir_all(output_path).context(format!(
|
|
220
|
+
"Failed to create output directory {}",
|
|
221
|
+
output_path.display()
|
|
222
|
+
))?;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let all_excludes = SAFE_EXCLUDES
|
|
226
|
+
.iter()
|
|
227
|
+
.copied()
|
|
228
|
+
.chain(
|
|
229
|
+
matches
|
|
230
|
+
.get_many::<String>("exclude")
|
|
231
|
+
.into_iter()
|
|
232
|
+
.flatten()
|
|
233
|
+
.map(|s| s.as_str()),
|
|
234
|
+
)
|
|
235
|
+
.map(|v| format!("!{v}"));
|
|
236
|
+
|
|
237
|
+
let sources = ReleaseFileSearch::new(path.clone())
|
|
238
|
+
.extensions(JVM_EXTENSIONS.iter().copied())
|
|
239
|
+
.ignores(all_excludes)
|
|
240
|
+
.respect_ignores(true)
|
|
241
|
+
.sort_entries(true)
|
|
242
|
+
.collect_files()?;
|
|
243
|
+
|
|
244
|
+
let files = build_source_files(sources);
|
|
245
|
+
|
|
246
|
+
let tempfile = source_bundle::build(context, files, Some(*debug_id))
|
|
247
|
+
.context("Unable to create source bundle")?;
|
|
248
|
+
|
|
249
|
+
fs::copy(tempfile.path(), &out).context("Unable to write source bundle")?;
|
|
250
|
+
println!("Created {}", out.display());
|
|
251
|
+
|
|
252
|
+
Ok(())
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
#[cfg(test)]
|
|
256
|
+
mod log_capture {
|
|
257
|
+
use log::{Level, LevelFilter, Log, Metadata, Record};
|
|
258
|
+
use std::cell::RefCell;
|
|
259
|
+
use std::sync::Once;
|
|
260
|
+
|
|
261
|
+
thread_local! {
|
|
262
|
+
static BUFFER: RefCell<Vec<(Level, String)>> = const { RefCell::new(Vec::new()) };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
struct CaptureLogger;
|
|
266
|
+
|
|
267
|
+
impl Log for CaptureLogger {
|
|
268
|
+
fn enabled(&self, _: &Metadata) -> bool {
|
|
269
|
+
true
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
fn log(&self, record: &Record) {
|
|
273
|
+
BUFFER.with(|b| {
|
|
274
|
+
b.borrow_mut()
|
|
275
|
+
.push((record.level(), record.args().to_string()))
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
fn flush(&self) {}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
static LOGGER: CaptureLogger = CaptureLogger;
|
|
283
|
+
|
|
284
|
+
/// Installs the capture logger (once per process) and clears this
|
|
285
|
+
/// thread's buffer so a test starts from a clean slate.
|
|
286
|
+
pub fn setup() {
|
|
287
|
+
static ONCE: Once = Once::new();
|
|
288
|
+
ONCE.call_once(|| {
|
|
289
|
+
let _ = log::set_logger(&LOGGER);
|
|
290
|
+
log::set_max_level(LevelFilter::Trace);
|
|
291
|
+
});
|
|
292
|
+
BUFFER.with(|b| b.borrow_mut().clear());
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
pub fn warnings() -> Vec<String> {
|
|
296
|
+
BUFFER.with(|b| {
|
|
297
|
+
b.borrow()
|
|
298
|
+
.iter()
|
|
299
|
+
.filter(|(lvl, _)| *lvl == Level::Warn)
|
|
300
|
+
.map(|(_, msg)| msg.clone())
|
|
301
|
+
.collect()
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
#[cfg(test)]
|
|
307
|
+
mod tests {
|
|
308
|
+
use super::*;
|
|
309
|
+
use std::path::Path;
|
|
310
|
+
|
|
311
|
+
#[test]
|
|
312
|
+
fn test_excludes_build_output_at_module_root() {
|
|
313
|
+
assert!(is_in_ambiguous_build_dir(Path::new(
|
|
314
|
+
"app/build/generated/Foo.java"
|
|
315
|
+
)));
|
|
316
|
+
assert!(is_in_ambiguous_build_dir(Path::new(
|
|
317
|
+
"build/generated/Foo.java"
|
|
318
|
+
)));
|
|
319
|
+
assert!(is_in_ambiguous_build_dir(Path::new(
|
|
320
|
+
"module/target/classes/Foo.java"
|
|
321
|
+
)));
|
|
322
|
+
assert!(is_in_ambiguous_build_dir(Path::new("bin/Foo.class")));
|
|
323
|
+
assert!(is_in_ambiguous_build_dir(Path::new(
|
|
324
|
+
"out/production/Foo.java"
|
|
325
|
+
)));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
#[test]
|
|
329
|
+
fn test_keeps_source_packages_under_src() {
|
|
330
|
+
assert!(!is_in_ambiguous_build_dir(Path::new(
|
|
331
|
+
"src/main/java/com/example/build/Builder.java"
|
|
332
|
+
)));
|
|
333
|
+
assert!(!is_in_ambiguous_build_dir(Path::new(
|
|
334
|
+
"app/src/main/java/com/example/target/Target.java"
|
|
335
|
+
)));
|
|
336
|
+
assert!(!is_in_ambiguous_build_dir(Path::new(
|
|
337
|
+
"src/main/kotlin/com/example/out/Output.kt"
|
|
338
|
+
)));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
#[test]
|
|
342
|
+
fn test_excludes_build_dir_containing_src() {
|
|
343
|
+
// build/src/... should still be excluded — src is *inside* build, not above it
|
|
344
|
+
assert!(is_in_ambiguous_build_dir(Path::new(
|
|
345
|
+
"build/src/main/java/Foo.java"
|
|
346
|
+
)));
|
|
347
|
+
assert!(is_in_ambiguous_build_dir(Path::new(
|
|
348
|
+
"app/build/src/generated/Foo.java"
|
|
349
|
+
)));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
#[test]
|
|
353
|
+
fn test_excludes_nested_ambiguous_dirs_under_build() {
|
|
354
|
+
// build/src/.../target/ — inner `target` is under src, but outer `build` is not
|
|
355
|
+
assert!(is_in_ambiguous_build_dir(Path::new(
|
|
356
|
+
"build/src/main/java/com/example/target/Foo.java"
|
|
357
|
+
)));
|
|
358
|
+
assert!(is_in_ambiguous_build_dir(Path::new(
|
|
359
|
+
"target/src/main/java/com/example/out/Foo.java"
|
|
360
|
+
)));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
#[test]
|
|
364
|
+
fn test_strip_source_set_prefix_drops_module_and_source_set() {
|
|
365
|
+
assert_eq!(
|
|
366
|
+
strip_source_set_prefix(Path::new(
|
|
367
|
+
"sentry-android-core/src/main/java/io/sentry/android/core/ANRWatchDog.java"
|
|
368
|
+
)),
|
|
369
|
+
Path::new("io/sentry/android/core/ANRWatchDog.java")
|
|
370
|
+
);
|
|
371
|
+
assert_eq!(
|
|
372
|
+
strip_source_set_prefix(Path::new("src/main/kotlin/com/example/Foo.kt")),
|
|
373
|
+
Path::new("com/example/Foo.kt")
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
#[test]
|
|
378
|
+
fn test_strip_source_set_prefix_kt_under_java_source_set() {
|
|
379
|
+
// Mixed Java/Kotlin projects commonly place .kt files under src/main/java/
|
|
380
|
+
// — stripping is driven by the directory name, not the file extension.
|
|
381
|
+
assert_eq!(
|
|
382
|
+
strip_source_set_prefix(Path::new("src/main/java/com/example/Foo.kt")),
|
|
383
|
+
Path::new("com/example/Foo.kt")
|
|
384
|
+
);
|
|
385
|
+
assert_eq!(
|
|
386
|
+
strip_source_set_prefix(Path::new(
|
|
387
|
+
"app/src/main/java/io/sentry/android/core/ANRWatchDog.kt"
|
|
388
|
+
)),
|
|
389
|
+
Path::new("io/sentry/android/core/ANRWatchDog.kt")
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
#[test]
|
|
394
|
+
fn test_strip_source_set_prefix_handles_nested_modules() {
|
|
395
|
+
assert_eq!(
|
|
396
|
+
strip_source_set_prefix(Path::new(
|
|
397
|
+
"sentry-opentelemetry/sentry-opentelemetry-agent/src/main/java/io/sentry/opentelemetry/agent/Foo.java"
|
|
398
|
+
)),
|
|
399
|
+
Path::new("io/sentry/opentelemetry/agent/Foo.java")
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
#[test]
|
|
404
|
+
fn test_strip_source_set_prefix_handles_android_variants() {
|
|
405
|
+
assert_eq!(
|
|
406
|
+
strip_source_set_prefix(Path::new("app/src/debug/java/com/example/Foo.java")),
|
|
407
|
+
Path::new("com/example/Foo.java")
|
|
408
|
+
);
|
|
409
|
+
assert_eq!(
|
|
410
|
+
strip_source_set_prefix(Path::new("lib/src/release/kotlin/com/example/Bar.kt")),
|
|
411
|
+
Path::new("com/example/Bar.kt")
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
#[test]
|
|
416
|
+
fn test_strip_source_set_prefix_supports_scala_and_groovy() {
|
|
417
|
+
assert_eq!(
|
|
418
|
+
strip_source_set_prefix(Path::new("mod/src/main/scala/com/example/Foo.scala")),
|
|
419
|
+
Path::new("com/example/Foo.scala")
|
|
420
|
+
);
|
|
421
|
+
assert_eq!(
|
|
422
|
+
strip_source_set_prefix(Path::new("mod/src/main/groovy/com/example/Foo.groovy")),
|
|
423
|
+
Path::new("com/example/Foo.groovy")
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
#[test]
|
|
428
|
+
fn test_strip_source_set_prefix_supports_clojure() {
|
|
429
|
+
assert_eq!(
|
|
430
|
+
strip_source_set_prefix(Path::new("mod/src/main/clojure/com/example/foo.clj")),
|
|
431
|
+
Path::new("com/example/foo.clj")
|
|
432
|
+
);
|
|
433
|
+
assert_eq!(
|
|
434
|
+
strip_source_set_prefix(Path::new("mod/src/main/clojure/com/example/foo.cljc")),
|
|
435
|
+
Path::new("com/example/foo.cljc")
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
#[test]
|
|
440
|
+
fn test_strip_source_set_prefix_handles_default_package() {
|
|
441
|
+
assert_eq!(
|
|
442
|
+
strip_source_set_prefix(Path::new("src/main/java/NoPackage.java")),
|
|
443
|
+
Path::new("NoPackage.java")
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
#[test]
|
|
448
|
+
fn test_strip_source_set_prefix_falls_back_when_no_match() {
|
|
449
|
+
// No `src/<sourceset>/<lang>/` triplet — path is returned unchanged.
|
|
450
|
+
assert_eq!(
|
|
451
|
+
strip_source_set_prefix(Path::new("sources/com/example/Foo.java")),
|
|
452
|
+
Path::new("sources/com/example/Foo.java")
|
|
453
|
+
);
|
|
454
|
+
assert_eq!(
|
|
455
|
+
strip_source_set_prefix(Path::new("Foo.java")),
|
|
456
|
+
Path::new("Foo.java")
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
#[test]
|
|
461
|
+
fn test_strip_source_set_prefix_does_not_match_package_named_like_lang() {
|
|
462
|
+
// `kotlin` as a package name (under `src/main/java/`) must not be
|
|
463
|
+
// mistaken for the source-set language dir.
|
|
464
|
+
assert_eq!(
|
|
465
|
+
strip_source_set_prefix(Path::new("src/main/java/com/example/kotlin/Foo.java")),
|
|
466
|
+
Path::new("com/example/kotlin/Foo.java")
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
#[test]
|
|
471
|
+
fn test_keeps_files_without_ambiguous_dirs() {
|
|
472
|
+
assert!(!is_in_ambiguous_build_dir(Path::new(
|
|
473
|
+
"src/main/java/com/example/Foo.java"
|
|
474
|
+
)));
|
|
475
|
+
assert!(!is_in_ambiguous_build_dir(Path::new("Foo.java")));
|
|
476
|
+
assert!(!is_in_ambiguous_build_dir(Path::new(
|
|
477
|
+
"app/src/main/java/Foo.java"
|
|
478
|
+
)));
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
fn fake_source(base: &str, relative: &str) -> ReleaseFileMatch {
|
|
482
|
+
ReleaseFileMatch {
|
|
483
|
+
base_path: PathBuf::from(base),
|
|
484
|
+
path: PathBuf::from(base).join(relative),
|
|
485
|
+
contents: Vec::new(),
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
#[test]
|
|
490
|
+
fn test_build_source_files_warns_on_collision_for_android_variants() {
|
|
491
|
+
// Sources arrive pre-sorted from `ReleaseFileSearch` (which configures
|
|
492
|
+
// `WalkBuilder::sort_by_file_name`); the first-seen wins in the dedup.
|
|
493
|
+
log_capture::setup();
|
|
494
|
+
|
|
495
|
+
let debug_src = fake_source("/app", "src/debug/java/com/example/Config.java");
|
|
496
|
+
let main_src = fake_source("/app", "src/main/java/com/example/Config.java");
|
|
497
|
+
let kept_display = debug_src.path.display().to_string();
|
|
498
|
+
let skipped_display = main_src.path.display().to_string();
|
|
499
|
+
|
|
500
|
+
let files = build_source_files(vec![debug_src, main_src]);
|
|
501
|
+
|
|
502
|
+
assert_eq!(files.len(), 1);
|
|
503
|
+
assert_eq!(files[0].url, "~/com/example/Config.jvm");
|
|
504
|
+
assert_eq!(files[0].path.display().to_string(), kept_display);
|
|
505
|
+
|
|
506
|
+
let warnings = log_capture::warnings();
|
|
507
|
+
assert_eq!(warnings.len(), 1);
|
|
508
|
+
let msg = &warnings[0];
|
|
509
|
+
assert!(
|
|
510
|
+
msg.contains("URL collision on ~/com/example/Config.jvm"),
|
|
511
|
+
"{msg}"
|
|
512
|
+
);
|
|
513
|
+
assert!(
|
|
514
|
+
msg.contains(&skipped_display),
|
|
515
|
+
"missing skipped path '{skipped_display}' in: {msg}"
|
|
516
|
+
);
|
|
517
|
+
assert!(
|
|
518
|
+
msg.contains(&kept_display),
|
|
519
|
+
"missing kept path '{kept_display}' in: {msg}"
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
#[test]
|
|
524
|
+
fn test_build_source_files_keeps_distinct_urls() {
|
|
525
|
+
log_capture::setup();
|
|
526
|
+
|
|
527
|
+
let sources = vec![
|
|
528
|
+
fake_source("/app", "src/main/java/com/example/Foo.java"),
|
|
529
|
+
fake_source("/app", "src/main/java/com/example/Bar.java"),
|
|
530
|
+
];
|
|
531
|
+
let files = build_source_files(sources);
|
|
532
|
+
assert_eq!(files.len(), 2);
|
|
533
|
+
assert!(log_capture::warnings().is_empty());
|
|
534
|
+
}
|
|
535
|
+
}
|
|
@@ -21,6 +21,7 @@ pub struct ReleaseFileSearch {
|
|
|
21
21
|
ignore_file: Option<String>,
|
|
22
22
|
decompress: bool,
|
|
23
23
|
respect_ignores: bool,
|
|
24
|
+
sort_entries: bool,
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
#[derive(Eq, PartialEq, Hash)]
|
|
@@ -39,6 +40,7 @@ impl ReleaseFileSearch {
|
|
|
39
40
|
ignores: BTreeSet::new(),
|
|
40
41
|
decompress: false,
|
|
41
42
|
respect_ignores: false,
|
|
43
|
+
sort_entries: false,
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -85,6 +87,14 @@ impl ReleaseFileSearch {
|
|
|
85
87
|
self
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
/// When enabled, directory entries are yielded in sorted order, making
|
|
91
|
+
/// the walk deterministic across filesystems. Opt-in because callers that
|
|
92
|
+
/// don't care about order pay no sort cost.
|
|
93
|
+
pub fn sort_entries(&mut self, sort: bool) -> &mut Self {
|
|
94
|
+
self.sort_entries = sort;
|
|
95
|
+
self
|
|
96
|
+
}
|
|
97
|
+
|
|
88
98
|
pub fn collect_file(path: PathBuf) -> Result<ReleaseFileMatch> {
|
|
89
99
|
// NOTE: `collect_file` currently do not handle gzip decompression,
|
|
90
100
|
// as its mostly used for 3rd tools like xcode or gradle.
|
|
@@ -114,6 +124,10 @@ impl ReleaseFileSearch {
|
|
|
114
124
|
let mut builder = WalkBuilder::new(&self.path);
|
|
115
125
|
builder.follow_links(true);
|
|
116
126
|
|
|
127
|
+
if self.sort_entries {
|
|
128
|
+
builder.sort_by_file_name(|a, b| a.cmp(b));
|
|
129
|
+
}
|
|
130
|
+
|
|
117
131
|
if !self.respect_ignores {
|
|
118
132
|
builder.git_exclude(false).git_ignore(false).ignore(false);
|
|
119
133
|
}
|