Преглед изворни кода

Implement dynamic build details capture for TestFlight and Xcode

Jonas Björkert пре 2 година
родитељ
комит
5584b21e84

+ 5 - 0
BuildDetails.plist

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict/>
+</plist>

+ 37 - 0
LoopFollow.xcodeproj/project.pbxproj

@@ -27,6 +27,8 @@
 		DD7E198A2ACDA62600DBD158 /* SensorStart.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7E19892ACDA62600DBD158 /* SensorStart.swift */; };
 		DD7FFAFD2A72953000C3A304 /* EKEventStore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7FFAFC2A72953000C3A304 /* EKEventStore+Extensions.swift */; };
 		DD98F54424BCEFEE0007425A /* ShareClientExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD98F54324BCEFEE0007425A /* ShareClientExtension.swift */; };
+		DDB0AF522BB1A8BE00AFA48B /* BuildDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB0AF512BB1A8BE00AFA48B /* BuildDetails.swift */; };
+		DDB0AF552BB1B24A00AFA48B /* BuildDetails.plist in Resources */ = {isa = PBXBuildFile; fileRef = DDB0AF542BB1B24A00AFA48B /* BuildDetails.plist */; };
 		DDCF979424C0D380002C9752 /* UIViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF979324C0D380002C9752 /* UIViewExtension.swift */; };
 		DDCF979624C1443C002C9752 /* GeneralSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF979524C1443C002C9752 /* GeneralSettingsViewController.swift */; };
 		DDCF979824C1489C002C9752 /* GraphSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF979724C1489C002C9752 /* GraphSettingsViewController.swift */; };
@@ -212,6 +214,9 @@
 		DD7E19892ACDA62600DBD158 /* SensorStart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorStart.swift; sourceTree = "<group>"; };
 		DD7FFAFC2A72953000C3A304 /* EKEventStore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EKEventStore+Extensions.swift"; sourceTree = "<group>"; };
 		DD98F54324BCEFEE0007425A /* ShareClientExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareClientExtension.swift; sourceTree = "<group>"; };
+		DDB0AF502BB1A84500AFA48B /* capture-build-details.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "capture-build-details.sh"; sourceTree = "<group>"; };
+		DDB0AF512BB1A8BE00AFA48B /* BuildDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildDetails.swift; sourceTree = "<group>"; };
+		DDB0AF542BB1B24A00AFA48B /* BuildDetails.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = BuildDetails.plist; sourceTree = "<group>"; };
 		DDCF979324C0D380002C9752 /* UIViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewExtension.swift; sourceTree = "<group>"; };
 		DDCF979524C1443C002C9752 /* GeneralSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsViewController.swift; sourceTree = "<group>"; };
 		DDCF979724C1489C002C9752 /* GraphSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphSettingsViewController.swift; sourceTree = "<group>"; };
@@ -454,6 +459,14 @@
 			path = Extensions;
 			sourceTree = "<group>";
 		};
+		DDB0AF4F2BB1A81F00AFA48B /* Scripts */ = {
+			isa = PBXGroup;
+			children = (
+				DDB0AF502BB1A84500AFA48B /* capture-build-details.sh */,
+			);
+			path = Scripts;
+			sourceTree = "<group>";
+		};
 		FC16A97624995FEE003D6245 /* Application */ = {
 			isa = PBXGroup;
 			children = (
@@ -632,6 +645,8 @@
 		FC97880B2485969B00A7906C = {
 			isa = PBXGroup;
 			children = (
+				DDB0AF542BB1B24A00AFA48B /* BuildDetails.plist */,
+				DDB0AF4F2BB1A81F00AFA48B /* Scripts */,
 				DDCFCAF12B17273200BE5751 /* LoopFollowDisplayNameConfig.xcconfig */,
 				FC3AE7B3249E8E0E00AAE1E0 /* LoopFollow.xcdatamodeld */,
 				FC5A5C3C2497B229009C550E /* Config.xcconfig */,
@@ -676,6 +691,7 @@
 				FCD2A27C24C9D044009F7B7B /* Globals.swift */,
 				FC8589BE252B54F500C8FC73 /* Mobileprovision.swift */,
 				DD07B5C829E2F9C400C6A635 /* NightscoutUtils.swift */,
+				DDB0AF512BB1A8BE00AFA48B /* BuildDetails.swift */,
 			);
 			path = helpers;
 			sourceTree = "<group>";
@@ -710,6 +726,7 @@
 				FC9788112485969B00A7906C /* Frameworks */,
 				FC9788122485969B00A7906C /* Resources */,
 				04DA71CCA0280FA5FA2DF7A6 /* [CP] Embed Pods Frameworks */,
+				DDB0AF532BB1AA0900AFA48B /* Capture Build Details */,
 			);
 			buildRules = (
 			);
@@ -786,6 +803,7 @@
 				FC7CE53F248ABE37001F83B8 /* Siri_Alert_Glucose_Dropping_Fast.caf in Resources */,
 				FC7CE561248ABE37001F83B8 /* Wrong_Answer.caf in Resources */,
 				FCC6885C2489559400A0279D /* blank.wav in Resources */,
+				DDB0AF552BB1B24A00AFA48B /* BuildDetails.plist in Resources */,
 				FC7CE528248ABE37001F83B8 /* Cartoon_Bounce_To_Ceiling.caf in Resources */,
 				FC7CE57B248ABE37001F83B8 /* Alert_Tone_Busy.caf in Resources */,
 				FC7CE51B248ABE37001F83B8 /* Thunder_Sound_FX.caf in Resources */,
@@ -924,6 +942,24 @@
 			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
 			showEnvVarsInLog = 0;
 		};
+		DDB0AF532BB1AA0900AFA48B /* Capture Build Details */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+			);
+			name = "Capture Build Details";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${SRCROOT}/Scripts/capture-build-details.sh\"\n";
+		};
 /* End PBXShellScriptBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */
@@ -947,6 +983,7 @@
 				DD7E19862ACDA59700DBD158 /* BGCheck.swift in Sources */,
 				FC16A97D24996747003D6245 /* Alarms.swift in Sources */,
 				FC16A97B249966A3003D6245 /* AlarmSound.swift in Sources */,
+				DDB0AF522BB1A8BE00AFA48B /* BuildDetails.swift in Sources */,
 				DD493AE72ACF23CF009A6922 /* DeviceStatus.swift in Sources */,
 				FCFEECA2248857A600402A7F /* SettingsViewController.swift in Sources */,
 				FCFEECA02488157B00402A7F /* Chart.swift in Sources */,

+ 45 - 4
LoopFollow/ViewControllers/SettingsViewController.swift

@@ -41,6 +41,50 @@ class SettingsViewController: FormViewController {
         
     }
    
+    // Determine if the build is from TestFlight
+    func isTestFlightBuild() -> Bool {
+#if targetEnvironment(simulator)
+        return false
+#else
+        if Bundle.main.url(forResource: "embedded", withExtension: "mobileprovision") != nil {
+            return false
+        }
+        guard let receiptName = Bundle.main.appStoreReceiptURL?.lastPathComponent else {
+            return false
+        }
+        return "sandboxReceipt".caseInsensitiveCompare(receiptName) == .orderedSame
+#endif
+    }
+    
+    // Get the build date from the build details
+    func buildDate() -> Date? {
+        let dateFormatter = DateFormatter()
+        dateFormatter.dateFormat = "EEE MMM d HH:mm:ss 'UTC' yyyy"
+        dateFormatter.locale = Locale(identifier: "en_US_POSIX")
+        dateFormatter.timeZone = TimeZone(identifier: "UTC")
+        
+        guard let dateString = BuildDetails.default.buildDateString,
+              let date = dateFormatter.date(from: dateString) else {
+            return nil
+        }
+        return date
+    }
+    
+    // Calculate the expiration date based on the build type
+    func calculateExpirationDate() -> Date {
+        if isTestFlightBuild(), let buildDate = buildDate() {
+            // For TestFlight, add 90 days to the build date
+            return Calendar.current.date(byAdding: .day, value: 90, to: buildDate)!
+        } else {
+            // For Xcode builds, use the provisioning profile's expiration date
+            if let provision = MobileProvision.read() {
+                return provision.expirationDate
+            } else {
+                return Date() // Fallback to current date if unable to read provisioning profile
+            }
+        }
+    }
+    
    override func viewDidLoad() {
         super.viewDidLoad()
         if UserDefaultsRepository.forceDarkMode.value {
@@ -49,10 +93,7 @@ class SettingsViewController: FormViewController {
        UserDefaultsRepository.showNS.value = false
        UserDefaultsRepository.showDex.value = false
     
-        var expiration: Date = Date()
-        if let provision = MobileProvision.read() {
-            expiration = provision.expirationDate
-        }
+       let expiration = calculateExpirationDate()
                         
         form
         +++ Section(header: "Data Settings", footer: "")

+ 29 - 0
LoopFollow/helpers/BuildDetails.swift

@@ -0,0 +1,29 @@
+//
+//  BuildDetails.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2024-03-25.
+//  Copyright © 2024 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+
+class BuildDetails {
+    static var `default` = BuildDetails()
+    
+    let dict: [String: Any]
+    
+    init() {
+        guard let url = Bundle.main.url(forResource: "BuildDetails", withExtension: "plist"),
+              let data = try? Data(contentsOf: url),
+              let parsed = try? PropertyListSerialization.propertyList(from: data, format: nil) as? [String: Any] else {
+            dict = [:]
+            return
+        }
+        dict = parsed
+    }
+    
+    var buildDateString: String? {
+        return dict["com-loopkit-Loop-build-date"] as? String
+    }
+}

+ 20 - 0
Scripts/capture-build-details.sh

@@ -0,0 +1,20 @@
+#!/bin/sh -e
+
+#  capture-build-details.sh
+#  LoopFollow
+#
+#  Created by Jonas Björkert on 2024-03-25.
+#  Copyright © 2024 Jon Fawcett. All rights reserved.
+
+info_plist_path="${BUILT_PRODUCTS_DIR}/${CONTENTS_FOLDER_PATH}/BuildDetails.plist"
+
+# Ensure the path to BuildDetails.plist is valid.
+if [ "${info_plist_path}" == "/" -o ! -e "${info_plist_path}" ]; then
+  echo "ERROR: BuildDetails.plist file does not exist at path: ${info_plist_path}" >&2
+  exit 1
+fi
+
+echo "Gathering build date..."
+
+# Capture the current date and write it to BuildDetails.plist
+plutil -replace com-loopkit-Loop-build-date -string "$(date)" "${info_plist_path}"