Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/launchpad/api/update_api_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class AppleAppInfo(BaseModel):
profile_expiration_date: Optional[str] = None
certificate_expiration_date: Optional[str] = None
missing_dsym_binaries: Optional[List[str]] = None
# TODO(EME-423): add "date_built" field once exposed in 'AppleAppInfo'
build_date: Optional[str] = None


class AndroidAppInfo(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion src/launchpad/artifact_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,6 @@ def _get_artifact_type(artifact: Artifact) -> ArtifactType:

apple_app_info = None
if isinstance(app_info, AppleAppInfo):
# TODO(EME-423): add "date_built" field once exposed in 'AppleAppInfo'
apple_app_info = AppleAppInfoModel(
is_simulator=app_info.is_simulator,
codesigning_type=app_info.codesigning_type,
Expand All @@ -475,6 +474,7 @@ def _get_artifact_type(artifact: Artifact) -> ArtifactType:
profile_expiration_date=app_info.profile_expiration_date,
certificate_expiration_date=app_info.certificate_expiration_date,
missing_dsym_binaries=app_info.missing_dsym_binaries,
build_date=app_info.build_date,
)

android_app_info = None
Expand Down
25 changes: 25 additions & 0 deletions src/launchpad/artifacts/apple/zipped_xcarchive.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def __init__(self, path: Path) -> None:
self._extract_dir = self._zip_provider.extract_to_temp_directory()
self._app_bundle_path: Path | None = None
self._plist: dict[str, Any] | None = None
self._archive_plist: dict[str, Any] | None = None
self._provisioning_profile: dict[str, Any] | None = None
self._dsym_info: dict[str, DsymInfo] | None = None

Expand All @@ -89,6 +90,30 @@ def get_plist(self) -> dict[str, Any]:
except Exception as e:
raise RuntimeError("Failed to parse Info.plist") from e

@sentry_sdk.trace
def get_archive_plist(self) -> dict[str, Any] | None:
"""Get the archive-level Info.plist (not the app bundle's Info.plist)."""
if self._archive_plist is not None:
return self._archive_plist

xcarchive_dirs = list(self._extract_dir.glob("*.xcarchive"))
if not xcarchive_dirs:
logger.debug(f"No .xcarchive directory found in {self._extract_dir}")
return None

xcarchive_dir = xcarchive_dirs[0]
plist_path = xcarchive_dir / "Info.plist"

try:
with open(plist_path, "rb") as f:
plist_data = plistlib.load(f)

self._archive_plist = plist_data
return plist_data
except Exception:
logger.debug(f"Failed to parse archive Info.plist at {plist_path}", exc_info=True)
return None

@sentry_sdk.trace
def get_app_icon(self) -> bytes | None:
"""Get the primary app icon, decoded from crushed PNG format."""
Expand Down
8 changes: 8 additions & 0 deletions src/launchpad/size/analyzers/apple.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,13 @@ def _extract_app_info(self, xcarchive: ZippedXCArchive) -> AppleAppInfo:
profile_expiration_date = expiration_date.isoformat()
certificate_expiration_date = self._extract_certificate_expiration_date(provisioning_profile)

build_date = None
archive_plist = xcarchive.get_archive_plist()
if archive_plist:
creation_date = archive_plist.get("CreationDate")
if creation_date:
build_date = creation_date.isoformat()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think this could also be written as

plist = xcarchive.get_archive_plist() or {}
date = plist.get("CreationDate")
build_date = date.isoformat() if date else None

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the if creation_date: branch but keeping the if archive_plist: check, would rather not default to or {} in this case.


supported_platforms = plist.get("CFBundleSupportedPlatforms", [])
is_simulator = "iphonesimulator" in supported_platforms or plist.get("DTPlatformName") == "iphonesimulator"

Expand Down Expand Up @@ -319,6 +326,7 @@ def _extract_app_info(self, xcarchive: ZippedXCArchive) -> AppleAppInfo:
minimum_os_version=plist.get("MinimumOSVersion", "Unknown"),
supported_platforms=supported_platforms,
sdk_version=plist.get("DTSDKName"),
build_date=build_date,
is_simulator=is_simulator,
codesigning_type=codesigning_type,
profile_name=profile_name,
Expand Down
1 change: 1 addition & 0 deletions src/launchpad/size/models/apple.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class AppleAppInfo(BaseAppInfo):
minimum_os_version: str = Field(..., description="Minimum app version")
supported_platforms: List[str] = Field(default_factory=list, description="Supported platforms")
sdk_version: str | None = Field(None, description="App SDK version used for build")
build_date: str | None = Field(None, description="Date when the archive was built (ISO format)")
is_simulator: bool = Field(False, description="If the app is a simulator build")
codesigning_type: str | None = Field(
None, description="Type of codesigning used (development, adhoc, appstore, enterprise)"
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/test_apple_basic_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ def test_basic_info(self, hackernews_xcarchive: Path) -> None:
assert basic_info.minimum_os_version == "17.5"
assert basic_info.supported_platforms == ["iPhoneOS"]
assert basic_info.sdk_version == "iphoneos18.4"
assert basic_info.build_date is not None
assert basic_info.build_date == "2025-05-19T16:15:12"
assert basic_info.is_simulator is False
assert basic_info.codesigning_type == "development"
assert basic_info.is_code_signature_valid is True
Expand Down
Loading