forked from trent_larson/crowd-funder-for-time-pwa
merge(master): big merge for qrcode-reboot
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 48;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -20,7 +20,7 @@
|
||||
/* Begin PBXFileReference section */
|
||||
2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = "<group>"; };
|
||||
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
|
||||
504EC3041FED79650016851F /* Time Safari.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Time Safari.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
@@ -65,7 +65,7 @@
|
||||
504EC3051FED79650016851F /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
504EC3041FED79650016851F /* Time Safari.app */,
|
||||
504EC3041FED79650016851F /* App.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -97,23 +97,24 @@
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
504EC3031FED79650016851F /* Time Safari */ = {
|
||||
504EC3031FED79650016851F /* App */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "Time Safari" */;
|
||||
buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */;
|
||||
buildPhases = (
|
||||
6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */,
|
||||
504EC3001FED79650016851F /* Sources */,
|
||||
504EC3011FED79650016851F /* Frameworks */,
|
||||
504EC3021FED79650016851F /* Resources */,
|
||||
9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */,
|
||||
012076E8FFE4BF260A79B034 /* Fix Privacy Manifest */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Time Safari";
|
||||
name = App;
|
||||
productName = App;
|
||||
productReference = 504EC3041FED79650016851F /* Time Safari.app */;
|
||||
productReference = 504EC3041FED79650016851F /* App.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
@@ -122,8 +123,9 @@
|
||||
504EC2FC1FED79650016851F /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 0920;
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 920;
|
||||
LastUpgradeCheck = 1630;
|
||||
TargetAttributes = {
|
||||
504EC3031FED79650016851F = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
@@ -132,7 +134,7 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "Time Safari" */;
|
||||
buildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */;
|
||||
compatibilityVersion = "Xcode 8.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
@@ -141,13 +143,11 @@
|
||||
Base,
|
||||
);
|
||||
mainGroup = 504EC2FB1FED79650016851F;
|
||||
packageReferences = (
|
||||
);
|
||||
productRefGroup = 504EC3051FED79650016851F /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
504EC3031FED79650016851F /* Time Safari */,
|
||||
504EC3031FED79650016851F /* App */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -169,6 +169,26 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
012076E8FFE4BF260A79B034 /* Fix Privacy Manifest */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Fix Privacy Manifest";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PROJECT_DIR}/app_privacy_manifest_fixer/fixer.sh\" ";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -249,6 +269,7 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -256,8 +277,10 @@
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -267,8 +290,10 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 7XVXYPEQYJ;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
@@ -306,6 +331,7 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -313,8 +339,10 @@
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -324,8 +352,10 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 7XVXYPEQYJ;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
@@ -337,7 +367,8 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
@@ -348,20 +379,24 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 18;
|
||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Time Safari";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.4.7;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -371,26 +406,30 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 18;
|
||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Time Safari";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.4.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
504EC2FF1FED79650016851F /* Build configuration list for PBXProject "Time Safari" */ = {
|
||||
504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
504EC3141FED79650016851F /* Debug */,
|
||||
@@ -399,7 +438,7 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "Time Safari" */ = {
|
||||
504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
504EC3171FED79650016851F /* Debug */,
|
||||
2
ios/App/App.xcworkspace/contents.xcworkspacedata
generated
2
ios/App/App.xcworkspace/contents.xcworkspacedata
generated
@@ -2,7 +2,7 @@
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Time Safari.xcodeproj">
|
||||
location = "group:App.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>TimeSafari</string>
|
||||
<string>TimeSafari</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -22,6 +22,10 @@
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Time Safari allows you to take photos, and also scan QR codes from contacts.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Time Safari allows you to upload photos.</string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
|
||||
20
ios/App/App/entitlements.mac.plist
Normal file
20
ios/App/App/entitlements.mac.plist
Normal file
@@ -0,0 +1,20 @@
|
||||
<?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>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.debugger</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.audio-input</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
<key>com.apple.security.personal-information.addressbook</key>
|
||||
<true/>
|
||||
<key>com.apple.security.personal-information.calendars</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -11,7 +11,12 @@ install! 'cocoapods', :disable_input_output_paths => true
|
||||
def capacitor_pods
|
||||
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||
pod 'CapacitorMlkitBarcodeScanning', :path => '../../node_modules/@capacitor-mlkit/barcode-scanning'
|
||||
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
|
||||
pod 'CapacitorCamera', :path => '../../node_modules/@capacitor/camera'
|
||||
pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem'
|
||||
pod 'CapacitorShare', :path => '../../node_modules/@capacitor/share'
|
||||
pod 'CapawesomeCapacitorFilePicker', :path => '../../node_modules/@capawesome/capacitor-file-picker'
|
||||
end
|
||||
|
||||
target 'App' do
|
||||
|
||||
@@ -1,28 +1,144 @@
|
||||
PODS:
|
||||
- Capacitor (6.2.0):
|
||||
- Capacitor (6.2.1):
|
||||
- CapacitorCordova
|
||||
- CapacitorApp (6.0.2):
|
||||
- Capacitor
|
||||
- CapacitorCordova (6.2.0)
|
||||
- CapacitorCamera (6.1.2):
|
||||
- Capacitor
|
||||
- CapacitorCordova (6.2.1)
|
||||
- CapacitorFilesystem (6.0.3):
|
||||
- Capacitor
|
||||
- CapacitorMlkitBarcodeScanning (6.2.0):
|
||||
- Capacitor
|
||||
- GoogleMLKit/BarcodeScanning (= 5.0.0)
|
||||
- CapacitorShare (6.0.3):
|
||||
- Capacitor
|
||||
- CapawesomeCapacitorFilePicker (6.2.0):
|
||||
- Capacitor
|
||||
- GoogleDataTransport (9.4.1):
|
||||
- GoogleUtilities/Environment (~> 7.7)
|
||||
- nanopb (< 2.30911.0, >= 2.30908.0)
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- GoogleMLKit/BarcodeScanning (5.0.0):
|
||||
- GoogleMLKit/MLKitCore
|
||||
- MLKitBarcodeScanning (~> 4.0.0)
|
||||
- GoogleMLKit/MLKitCore (5.0.0):
|
||||
- MLKitCommon (~> 10.0.0)
|
||||
- GoogleToolboxForMac/DebugUtils (2.3.2):
|
||||
- GoogleToolboxForMac/Defines (= 2.3.2)
|
||||
- GoogleToolboxForMac/Defines (2.3.2)
|
||||
- GoogleToolboxForMac/Logger (2.3.2):
|
||||
- GoogleToolboxForMac/Defines (= 2.3.2)
|
||||
- "GoogleToolboxForMac/NSData+zlib (2.3.2)":
|
||||
- GoogleToolboxForMac/Defines (= 2.3.2)
|
||||
- "GoogleToolboxForMac/NSDictionary+URLArguments (2.3.2)":
|
||||
- GoogleToolboxForMac/DebugUtils (= 2.3.2)
|
||||
- GoogleToolboxForMac/Defines (= 2.3.2)
|
||||
- "GoogleToolboxForMac/NSString+URLArguments (= 2.3.2)"
|
||||
- "GoogleToolboxForMac/NSString+URLArguments (2.3.2)"
|
||||
- GoogleUtilities/Environment (7.13.3):
|
||||
- GoogleUtilities/Privacy
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- GoogleUtilities/Logger (7.13.3):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Privacy (7.13.3)
|
||||
- GoogleUtilities/UserDefaults (7.13.3):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilitiesComponents (1.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GTMSessionFetcher/Core (3.5.0)
|
||||
- MLImage (1.0.0-beta5)
|
||||
- MLKitBarcodeScanning (4.0.0):
|
||||
- MLKitCommon (~> 10.0)
|
||||
- MLKitVision (~> 6.0)
|
||||
- MLKitCommon (10.0.0):
|
||||
- GoogleDataTransport (~> 9.0)
|
||||
- GoogleToolboxForMac/Logger (~> 2.1)
|
||||
- "GoogleToolboxForMac/NSData+zlib (~> 2.1)"
|
||||
- "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)"
|
||||
- GoogleUtilities/UserDefaults (~> 7.0)
|
||||
- GoogleUtilitiesComponents (~> 1.0)
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 1.1)
|
||||
- MLKitVision (6.0.0):
|
||||
- GoogleToolboxForMac/Logger (~> 2.1)
|
||||
- "GoogleToolboxForMac/NSData+zlib (~> 2.1)"
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 1.1)
|
||||
- MLImage (= 1.0.0-beta5)
|
||||
- MLKitCommon (~> 10.0)
|
||||
- nanopb (2.30910.0):
|
||||
- nanopb/decode (= 2.30910.0)
|
||||
- nanopb/encode (= 2.30910.0)
|
||||
- nanopb/decode (2.30910.0)
|
||||
- nanopb/encode (2.30910.0)
|
||||
- PromisesObjC (2.4.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- "Capacitor (from `../../node_modules/@capacitor/ios`)"
|
||||
- "CapacitorApp (from `../../node_modules/@capacitor/app`)"
|
||||
- "CapacitorCamera (from `../../node_modules/@capacitor/camera`)"
|
||||
- "CapacitorCordova (from `../../node_modules/@capacitor/ios`)"
|
||||
- "CapacitorFilesystem (from `../../node_modules/@capacitor/filesystem`)"
|
||||
- "CapacitorMlkitBarcodeScanning (from `../../node_modules/@capacitor-mlkit/barcode-scanning`)"
|
||||
- "CapacitorShare (from `../../node_modules/@capacitor/share`)"
|
||||
- "CapawesomeCapacitorFilePicker (from `../../node_modules/@capawesome/capacitor-file-picker`)"
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- GoogleDataTransport
|
||||
- GoogleMLKit
|
||||
- GoogleToolboxForMac
|
||||
- GoogleUtilities
|
||||
- GoogleUtilitiesComponents
|
||||
- GTMSessionFetcher
|
||||
- MLImage
|
||||
- MLKitBarcodeScanning
|
||||
- MLKitCommon
|
||||
- MLKitVision
|
||||
- nanopb
|
||||
- PromisesObjC
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Capacitor:
|
||||
:path: "../../node_modules/@capacitor/ios"
|
||||
CapacitorApp:
|
||||
:path: "../../node_modules/@capacitor/app"
|
||||
CapacitorCamera:
|
||||
:path: "../../node_modules/@capacitor/camera"
|
||||
CapacitorCordova:
|
||||
:path: "../../node_modules/@capacitor/ios"
|
||||
CapacitorFilesystem:
|
||||
:path: "../../node_modules/@capacitor/filesystem"
|
||||
CapacitorMlkitBarcodeScanning:
|
||||
:path: "../../node_modules/@capacitor-mlkit/barcode-scanning"
|
||||
CapacitorShare:
|
||||
:path: "../../node_modules/@capacitor/share"
|
||||
CapawesomeCapacitorFilePicker:
|
||||
:path: "../../node_modules/@capawesome/capacitor-file-picker"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Capacitor: 05d35014f4425b0740fc8776481f6a369ad071bf
|
||||
Capacitor: c95400d761e376be9da6be5a05f226c0e865cebf
|
||||
CapacitorApp: e1e6b7d05e444d593ca16fd6d76f2b7c48b5aea7
|
||||
CapacitorCordova: b33e7f4aa4ed105dd43283acdd940964374a87d9
|
||||
CapacitorCamera: 9bc7b005d0e6f1d5f525b8137045b60cffffce79
|
||||
CapacitorCordova: 8d93e14982f440181be7304aa9559ca631d77fff
|
||||
CapacitorFilesystem: 59270a63c60836248812671aa3b15df673fbaf74
|
||||
CapacitorMlkitBarcodeScanning: 7652be9c7922f39203a361de735d340ae37e134e
|
||||
CapacitorShare: d2a742baec21c8f3b92b361a2fbd2401cdd8288e
|
||||
CapawesomeCapacitorFilePicker: c40822f0a39f86855321943c7829d52bca7f01bd
|
||||
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
|
||||
GoogleMLKit: 90ba06e028795a50261f29500d238d6061538711
|
||||
GoogleToolboxForMac: 8bef7c7c5cf7291c687cf5354f39f9db6399ad34
|
||||
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
|
||||
GoogleUtilitiesComponents: 679b2c881db3b615a2777504623df6122dd20afe
|
||||
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
|
||||
MLImage: 1824212150da33ef225fbd3dc49f184cf611046c
|
||||
MLKitBarcodeScanning: 9cb0ec5ec65bbb5db31de4eba0a3289626beab4e
|
||||
MLKitCommon: afcd11b6c0735066a0dde8b4bf2331f6197cbca2
|
||||
MLKitVision: 90922bca854014a856f8b649d1f1f04f63fd9c79
|
||||
nanopb: 438bc412db1928dac798aa6fd75726007be04262
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
|
||||
PODFILE CHECKSUM: 4233f5c5f414604460ff96d372542c311b0fb7a8
|
||||
PODFILE CHECKSUM: 7e7e09e6937de7f015393aecf2cf7823645689b3
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
5
ios/App/app_privacy_manifest_fixer/.gitignore
vendored
Normal file
5
ios/App/app_privacy_manifest_fixer/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Build
|
||||
/Build/
|
||||
58
ios/App/app_privacy_manifest_fixer/CHANGELOG.md
Normal file
58
ios/App/app_privacy_manifest_fixer/CHANGELOG.md
Normal file
@@ -0,0 +1,58 @@
|
||||
## 1.4.1
|
||||
- Fix macOS app re-signing issue.
|
||||
- Automatically enable Hardened Runtime in macOS codesign.
|
||||
- Add clean script.
|
||||
|
||||
## 1.4.0
|
||||
- Support for macOS app ([#9](https://github.com/crasowas/app_privacy_manifest_fixer/issues/9)).
|
||||
|
||||
## 1.3.11
|
||||
- Fix install issue by skipping `PBXAggregateTarget` ([#4](https://github.com/crasowas/app_privacy_manifest_fixer/issues/4)).
|
||||
|
||||
## 1.3.10
|
||||
- Fix app re-signing issue.
|
||||
- Enhance Build Phases script robustness.
|
||||
|
||||
## 1.3.9
|
||||
- Add log file output.
|
||||
|
||||
## 1.3.8
|
||||
- Add version info to privacy access report.
|
||||
- Remove empty tables from privacy access report.
|
||||
|
||||
## 1.3.7
|
||||
- Enhance API symbols analysis with strings tool.
|
||||
- Improve performance of API usage analysis.
|
||||
|
||||
## 1.3.5
|
||||
- Fix issue with inaccurate privacy manifest search.
|
||||
- Disable dependency analysis to force the script to run on every build.
|
||||
- Add placeholder for privacy access report.
|
||||
- Update build output directory naming convention.
|
||||
- Add examples for privacy access report.
|
||||
|
||||
## 1.3.0
|
||||
- Add privacy access report generation.
|
||||
|
||||
## 1.2.3
|
||||
- Fix issue with relative path parameter.
|
||||
- Add support for all application targets.
|
||||
|
||||
## 1.2.1
|
||||
- Fix backup issue with empty user templates directory.
|
||||
|
||||
## 1.2.0
|
||||
- Add uninstall script.
|
||||
|
||||
## 1.1.2
|
||||
- Remove `Templates/.gitignore` to track `UserTemplates`.
|
||||
- Fix incorrect use of `App.xcprivacy` template in `App.framework`.
|
||||
|
||||
## 1.1.0
|
||||
- Add logs for latest release fetch failure.
|
||||
- Fix issue with converting published time to local time.
|
||||
- Disable showing environment variables in the build log.
|
||||
- Add `--install-builds-only` command line option.
|
||||
|
||||
## 1.0.0
|
||||
- Initial version.
|
||||
80
ios/App/app_privacy_manifest_fixer/Common/constants.sh
Executable file
80
ios/App/app_privacy_manifest_fixer/Common/constants.sh
Executable file
@@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright (c) 2025, crasowas.
|
||||
#
|
||||
# Use of this source code is governed by a MIT-style license
|
||||
# that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
set -e
|
||||
|
||||
# Prevent duplicate loading
|
||||
if [ -n "$CONSTANTS_SH_LOADED" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
readonly CONSTANTS_SH_LOADED=1
|
||||
|
||||
# File name of the privacy manifest
|
||||
readonly PRIVACY_MANIFEST_FILE_NAME="PrivacyInfo.xcprivacy"
|
||||
|
||||
# Common privacy manifest template file names
|
||||
readonly APP_TEMPLATE_FILE_NAME="AppTemplate.xcprivacy"
|
||||
readonly FRAMEWORK_TEMPLATE_FILE_NAME="FrameworkTemplate.xcprivacy"
|
||||
|
||||
# Universal delimiter
|
||||
readonly DELIMITER=":"
|
||||
|
||||
# Space escape symbol for handling space in path
|
||||
readonly SPACE_ESCAPE="\u0020"
|
||||
|
||||
# Default value when the version cannot be retrieved
|
||||
readonly UNKNOWN_VERSION="unknown"
|
||||
|
||||
# Categories of required reason APIs
|
||||
readonly API_CATEGORIES=(
|
||||
"NSPrivacyAccessedAPICategoryFileTimestamp"
|
||||
"NSPrivacyAccessedAPICategorySystemBootTime"
|
||||
"NSPrivacyAccessedAPICategoryDiskSpace"
|
||||
"NSPrivacyAccessedAPICategoryActiveKeyboards"
|
||||
"NSPrivacyAccessedAPICategoryUserDefaults"
|
||||
)
|
||||
|
||||
# Symbol of the required reason APIs and their categories
|
||||
#
|
||||
# See also:
|
||||
# * https://developer.apple.com/documentation/bundleresources/describing-use-of-required-reason-api
|
||||
# * https://github.com/Wooder/ios_17_required_reason_api_scanner/blob/main/required_reason_api_binary_scanner.sh
|
||||
readonly API_SYMBOLS=(
|
||||
# NSPrivacyAccessedAPICategoryFileTimestamp
|
||||
"NSPrivacyAccessedAPICategoryFileTimestamp${DELIMITER}getattrlist"
|
||||
"NSPrivacyAccessedAPICategoryFileTimestamp${DELIMITER}getattrlistbulk"
|
||||
"NSPrivacyAccessedAPICategoryFileTimestamp${DELIMITER}fgetattrlist"
|
||||
"NSPrivacyAccessedAPICategoryFileTimestamp${DELIMITER}stat"
|
||||
"NSPrivacyAccessedAPICategoryFileTimestamp${DELIMITER}fstat"
|
||||
"NSPrivacyAccessedAPICategoryFileTimestamp${DELIMITER}fstatat"
|
||||
"NSPrivacyAccessedAPICategoryFileTimestamp${DELIMITER}lstat"
|
||||
"NSPrivacyAccessedAPICategoryFileTimestamp${DELIMITER}getattrlistat"
|
||||
"NSPrivacyAccessedAPICategoryFileTimestamp${DELIMITER}NSFileCreationDate"
|
||||
"NSPrivacyAccessedAPICategoryFileTimestamp${DELIMITER}NSFileModificationDate"
|
||||
"NSPrivacyAccessedAPICategoryFileTimestamp${DELIMITER}NSURLContentModificationDateKey"
|
||||
"NSPrivacyAccessedAPICategoryFileTimestamp${DELIMITER}NSURLCreationDateKey"
|
||||
# NSPrivacyAccessedAPICategorySystemBootTime
|
||||
"NSPrivacyAccessedAPICategorySystemBootTime${DELIMITER}systemUptime"
|
||||
"NSPrivacyAccessedAPICategorySystemBootTime${DELIMITER}mach_absolute_time"
|
||||
# NSPrivacyAccessedAPICategoryDiskSpace
|
||||
"NSPrivacyAccessedAPICategoryDiskSpace${DELIMITER}statfs"
|
||||
"NSPrivacyAccessedAPICategoryDiskSpace${DELIMITER}statvfs"
|
||||
"NSPrivacyAccessedAPICategoryDiskSpace${DELIMITER}fstatfs"
|
||||
"NSPrivacyAccessedAPICategoryDiskSpace${DELIMITER}fstatvfs"
|
||||
"NSPrivacyAccessedAPICategoryDiskSpace${DELIMITER}NSFileSystemFreeSize"
|
||||
"NSPrivacyAccessedAPICategoryDiskSpace${DELIMITER}NSFileSystemSize"
|
||||
"NSPrivacyAccessedAPICategoryDiskSpace${DELIMITER}NSURLVolumeAvailableCapacityKey"
|
||||
"NSPrivacyAccessedAPICategoryDiskSpace${DELIMITER}NSURLVolumeAvailableCapacityForImportantUsageKey"
|
||||
"NSPrivacyAccessedAPICategoryDiskSpace${DELIMITER}NSURLVolumeAvailableCapacityForOpportunisticUsageKey"
|
||||
"NSPrivacyAccessedAPICategoryDiskSpace${DELIMITER}NSURLVolumeTotalCapacityKey"
|
||||
# NSPrivacyAccessedAPICategoryActiveKeyboards
|
||||
"NSPrivacyAccessedAPICategoryActiveKeyboards${DELIMITER}activeInputModes"
|
||||
# NSPrivacyAccessedAPICategoryUserDefaults
|
||||
"NSPrivacyAccessedAPICategoryUserDefaults${DELIMITER}NSUserDefaults"
|
||||
)
|
||||
125
ios/App/app_privacy_manifest_fixer/Common/utils.sh
Executable file
125
ios/App/app_privacy_manifest_fixer/Common/utils.sh
Executable file
@@ -0,0 +1,125 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright (c) 2025, crasowas.
|
||||
#
|
||||
# Use of this source code is governed by a MIT-style license
|
||||
# that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
set -e
|
||||
|
||||
# Prevent duplicate loading
|
||||
if [ -n "$UTILS_SH_LOADED" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
readonly UTILS_SH_LOADED=1
|
||||
|
||||
# Absolute path of the script and the tool's root directory
|
||||
script_path="$(realpath "${BASH_SOURCE[0]}")"
|
||||
tool_root_path="$(dirname "$(dirname "$script_path")")"
|
||||
|
||||
# Load common constants
|
||||
source "$tool_root_path/Common/constants.sh"
|
||||
|
||||
# Print the elements of an array along with their indices
|
||||
function print_array() {
|
||||
local -a array=("$@")
|
||||
|
||||
for ((i=0; i<${#array[@]}; i++)); do
|
||||
echo "[$i] $(decode_path "${array[i]}")"
|
||||
done
|
||||
}
|
||||
|
||||
# Split a string into substrings using a specified delimiter
|
||||
function split_string_by_delimiter() {
|
||||
local string="$1"
|
||||
local -a substrings=()
|
||||
|
||||
IFS="$DELIMITER" read -ra substrings <<< "$string"
|
||||
|
||||
echo "${substrings[@]}"
|
||||
}
|
||||
|
||||
# Encode a path string by replacing space with an escape character
|
||||
function encode_path() {
|
||||
echo "$1" | sed "s/ /$SPACE_ESCAPE/g"
|
||||
}
|
||||
|
||||
# Decode a path string by replacing encoded character with space
|
||||
function decode_path() {
|
||||
echo "$1" | sed "s/$SPACE_ESCAPE/ /g"
|
||||
}
|
||||
|
||||
# Get the dependency name by removing common suffixes
|
||||
function get_dependency_name() {
|
||||
local path="$1"
|
||||
|
||||
local dir_name="$(basename "$path")"
|
||||
# Remove `.app`, `.framework`, and `.xcframework` suffixes
|
||||
local dep_name="${dir_name%.*}"
|
||||
|
||||
echo "$dep_name"
|
||||
}
|
||||
|
||||
# Get the executable name from the specified `Info.plist` file
|
||||
function get_plist_executable() {
|
||||
local plist_file="$1"
|
||||
|
||||
if [ ! -f "$plist_file" ]; then
|
||||
echo ""
|
||||
else
|
||||
/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$plist_file" 2>/dev/null || echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# Get the version from the specified `Info.plist` file
|
||||
function get_plist_version() {
|
||||
local plist_file="$1"
|
||||
|
||||
if [ ! -f "$plist_file" ]; then
|
||||
echo "$UNKNOWN_VERSION"
|
||||
else
|
||||
/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$plist_file" 2>/dev/null || echo "$UNKNOWN_VERSION"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get the path of the specified framework version
|
||||
function get_framework_path() {
|
||||
local path="$1"
|
||||
local version_path="$2"
|
||||
|
||||
if [ -z "$version_path" ]; then
|
||||
echo "$path"
|
||||
else
|
||||
echo "$path/$version_path"
|
||||
fi
|
||||
}
|
||||
|
||||
# Search for privacy manifest files in the specified directory
|
||||
function search_privacy_manifest_files() {
|
||||
local path="$1"
|
||||
local -a privacy_manifest_files=()
|
||||
|
||||
# Create a temporary file to store search results
|
||||
local temp_file="$(mktemp)"
|
||||
|
||||
# Ensure the temporary file is deleted on script exit
|
||||
trap "rm -f $temp_file" EXIT
|
||||
|
||||
# Find privacy manifest files within the specified directory and store the results in the temporary file
|
||||
find "$path" -type f -name "$PRIVACY_MANIFEST_FILE_NAME" -print0 2>/dev/null > "$temp_file"
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
privacy_manifest_files+=($(encode_path "$file"))
|
||||
done < "$temp_file"
|
||||
|
||||
echo "${privacy_manifest_files[@]}"
|
||||
}
|
||||
|
||||
# Get the privacy manifest file with the shortest path
|
||||
function get_privacy_manifest_file() {
|
||||
local privacy_manifest_file="$(printf "%s\n" "$@" | awk '{print length, $0}' | sort -n | head -n1 | cut -d ' ' -f2-)"
|
||||
|
||||
echo "$(decode_path "$privacy_manifest_file")"
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
# Copyright (c) 2024, crasowas.
|
||||
#
|
||||
# Use of this source code is governed by a MIT-style license
|
||||
# that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
require 'xcodeproj'
|
||||
|
||||
RUN_SCRIPT_PHASE_NAME = 'Fix Privacy Manifest'
|
||||
|
||||
if ARGV.length < 2
|
||||
puts "Usage: ruby xcode_install_helper.rb <project_path> <script_content> [install_builds_only (true|false)]"
|
||||
exit 1
|
||||
end
|
||||
|
||||
project_path = ARGV[0]
|
||||
run_script_content = ARGV[1]
|
||||
install_builds_only = ARGV[2] == 'true'
|
||||
|
||||
# Find the first .xcodeproj file in the project directory
|
||||
xcodeproj_path = Dir.glob(File.join(project_path, "*.xcodeproj")).first
|
||||
|
||||
# Validate the .xcodeproj file existence
|
||||
unless xcodeproj_path
|
||||
puts "Error: No .xcodeproj file found in the specified directory."
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Open the Xcode project file
|
||||
begin
|
||||
project = Xcodeproj::Project.open(xcodeproj_path)
|
||||
rescue StandardError => e
|
||||
puts "Error: Unable to open the project file - #{e.message}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Process all targets in the project
|
||||
project.targets.each do |target|
|
||||
# Skip PBXAggregateTarget
|
||||
if target.is_a?(Xcodeproj::Project::Object::PBXAggregateTarget)
|
||||
puts "Skipping aggregate target: #{target.name}."
|
||||
next
|
||||
end
|
||||
|
||||
# Check if the target is a native application target
|
||||
if target.product_type == 'com.apple.product-type.application'
|
||||
puts "Processing target: #{target.name}..."
|
||||
|
||||
# Check for an existing Run Script phase with the specified name
|
||||
existing_phase = target.shell_script_build_phases.find { |phase| phase.name == RUN_SCRIPT_PHASE_NAME }
|
||||
|
||||
# Remove the existing Run Script phase if found
|
||||
if existing_phase
|
||||
puts " - Removing existing Run Script."
|
||||
target.build_phases.delete(existing_phase)
|
||||
end
|
||||
|
||||
# Add the new Run Script phase at the end
|
||||
puts " - Adding new Run Script."
|
||||
new_phase = target.new_shell_script_build_phase(RUN_SCRIPT_PHASE_NAME)
|
||||
new_phase.shell_script = run_script_content
|
||||
# Disable showing environment variables in the build log
|
||||
new_phase.show_env_vars_in_log = '0'
|
||||
# Run only for deployment post-processing if install_builds_only is true
|
||||
new_phase.run_only_for_deployment_postprocessing = install_builds_only ? '1' : '0'
|
||||
# Disable dependency analysis to force the script to run on every build, unless restricted to deployment builds by post-processing setting
|
||||
new_phase.always_out_of_date = '1'
|
||||
else
|
||||
puts "Skipping non-application target: #{target.name}."
|
||||
end
|
||||
end
|
||||
|
||||
# Save the project file
|
||||
begin
|
||||
project.save
|
||||
puts "Successfully added the Run Script phase: '#{RUN_SCRIPT_PHASE_NAME}'."
|
||||
rescue StandardError => e
|
||||
puts "Error: Unable to save the project file - #{e.message}"
|
||||
exit 1
|
||||
end
|
||||
@@ -0,0 +1,63 @@
|
||||
# Copyright (c) 2024, crasowas.
|
||||
#
|
||||
# Use of this source code is governed by a MIT-style license
|
||||
# that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
require 'xcodeproj'
|
||||
|
||||
RUN_SCRIPT_PHASE_NAME = 'Fix Privacy Manifest'
|
||||
|
||||
if ARGV.length < 1
|
||||
puts "Usage: ruby xcode_uninstall_helper.rb <project_path>"
|
||||
exit 1
|
||||
end
|
||||
|
||||
project_path = ARGV[0]
|
||||
|
||||
# Find the first .xcodeproj file in the project directory
|
||||
xcodeproj_path = Dir.glob(File.join(project_path, "*.xcodeproj")).first
|
||||
|
||||
# Validate the .xcodeproj file existence
|
||||
unless xcodeproj_path
|
||||
puts "Error: No .xcodeproj file found in the specified directory."
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Open the Xcode project file
|
||||
begin
|
||||
project = Xcodeproj::Project.open(xcodeproj_path)
|
||||
rescue StandardError => e
|
||||
puts "Error: Unable to open the project file - #{e.message}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Process all targets in the project
|
||||
project.targets.each do |target|
|
||||
# Check if the target is an application target
|
||||
if target.product_type == 'com.apple.product-type.application'
|
||||
puts "Processing target: #{target.name}..."
|
||||
|
||||
# Check for an existing Run Script phase with the specified name
|
||||
existing_phase = target.shell_script_build_phases.find { |phase| phase.name == RUN_SCRIPT_PHASE_NAME }
|
||||
|
||||
# Remove the existing Run Script phase if found
|
||||
if existing_phase
|
||||
puts " - Removing existing Run Script."
|
||||
target.build_phases.delete(existing_phase)
|
||||
else
|
||||
puts " - No existing Run Script found."
|
||||
end
|
||||
else
|
||||
puts "Skipping non-application target: #{target.name}."
|
||||
end
|
||||
end
|
||||
|
||||
# Save the project file
|
||||
begin
|
||||
project.save
|
||||
puts "Successfully removed the Run Script phase: '#{RUN_SCRIPT_PHASE_NAME}'."
|
||||
rescue StandardError => e
|
||||
puts "Error: Unable to save the project file - #{e.message}"
|
||||
exit 1
|
||||
end
|
||||
21
ios/App/app_privacy_manifest_fixer/LICENSE
Normal file
21
ios/App/app_privacy_manifest_fixer/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 crasowas
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
240
ios/App/app_privacy_manifest_fixer/README.md
Normal file
240
ios/App/app_privacy_manifest_fixer/README.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# App Privacy Manifest Fixer
|
||||
|
||||
[](https://github.com/crasowas/app_privacy_manifest_fixer/releases/latest)
|
||||

|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
**English | [简体中文](./README.zh-CN.md)**
|
||||
|
||||
This tool is an automation solution based on Shell scripts, designed to analyze and fix the privacy manifest of iOS/macOS apps to ensure compliance with App Store requirements. It leverages the [App Store Privacy Manifest Analyzer](https://github.com/crasowas/app_store_required_privacy_manifest_analyser) to analyze API usage within the app and its dependencies, and generate or fix the `PrivacyInfo.xcprivacy` file.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- **Non-Intrusive Integration**: No need to modify the source code or adjust the project structure.
|
||||
- **Fast Installation and Uninstallation**: Quickly install or uninstall the tool with a single command.
|
||||
- **Automatic Analysis and Fixes**: Automatically analyzes API usage and fixes privacy manifest issues during the project build.
|
||||
- **Flexible Template Customization**: Supports custom privacy manifest templates for apps and frameworks, accommodating various usage scenarios.
|
||||
- **Privacy Access Report**: Automatically generates a report displaying the `NSPrivacyAccessedAPITypes` declarations for the app and SDKs.
|
||||
- **Effortless Version Upgrades**: Provides an upgrade script for quick updates to the latest version.
|
||||
|
||||
## 📥 Installation
|
||||
|
||||
### Download the Tool
|
||||
|
||||
1. Download the [latest release](https://github.com/crasowas/app_privacy_manifest_fixer/releases/latest).
|
||||
2. Extract the downloaded file:
|
||||
- The extracted directory is usually named `app_privacy_manifest_fixer-xxx` (where `xxx` is the version number).
|
||||
- It is recommended to rename it to `app_privacy_manifest_fixer` or use the full directory name in subsequent paths.
|
||||
- **It is advised to move the directory to your iOS/macOS project to avoid path-related issues on different devices, and to easily customize the privacy manifest template for each project**.
|
||||
|
||||
### ⚡ Automatic Installation (Recommended)
|
||||
|
||||
1. **Navigate to the tool's directory**:
|
||||
|
||||
```shell
|
||||
cd /path/to/app_privacy_manifest_fixer
|
||||
```
|
||||
|
||||
2. **Run the installation script**:
|
||||
|
||||
```shell
|
||||
sh install.sh <project_path>
|
||||
```
|
||||
|
||||
- For Flutter projects, `project_path` should be the path to the `ios/macos` directory within the Flutter project.
|
||||
- If the installation command is run again, the tool will first remove any existing installation (if present). To modify command-line options, simply rerun the installation command without the need to uninstall first.
|
||||
|
||||
### Manual Installation
|
||||
|
||||
If you prefer not to use the installation script, you can manually add the `Fix Privacy Manifest` task to the Xcode **Build Phases**. Follow these steps:
|
||||
|
||||
#### 1. Add the Script in Xcode
|
||||
|
||||
- Open your iOS/macOS project in Xcode, go to the **TARGETS** tab, and select your app target.
|
||||
- Navigate to **Build Phases**, click the **+** button, and select **New Run Script Phase**.
|
||||
- Rename the newly created **Run Script** to `Fix Privacy Manifest`.
|
||||
- In the Shell script box, add the following code:
|
||||
|
||||
```shell
|
||||
# Use relative path (recommended): if `app_privacy_manifest_fixer` is within the project directory
|
||||
"$PROJECT_DIR/path/to/app_privacy_manifest_fixer/fixer.sh"
|
||||
|
||||
# Use absolute path: if `app_privacy_manifest_fixer` is outside the project directory
|
||||
# "/absolute/path/to/app_privacy_manifest_fixer/fixer.sh"
|
||||
```
|
||||
|
||||
**Modify `path/to` or `absolute/path/to` as needed, and ensure the paths are correct. Remove or comment out the unused lines accordingly.**
|
||||
|
||||
#### 2. Adjust the Script Execution Order
|
||||
|
||||
**Move this script after all other Build Phases to ensure the privacy manifest is fixed after all resource copying and build tasks are completed**.
|
||||
|
||||
### Build Phases Screenshot
|
||||
|
||||
Below is a screenshot of the Xcode Build Phases configuration after successful automatic/manual installation (with no command-line options enabled):
|
||||
|
||||

|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
After installation, the tool will automatically run with each project build, and the resulting application bundle will include the fixes.
|
||||
|
||||
If the `--install-builds-only` command-line option is enabled during installation, the tool will only run during the installation of the build.
|
||||
|
||||
### Xcode Build Log Screenshot
|
||||
|
||||
Below is a screenshot of the log output from the tool during the project build (by default, it will be saved to the `app_privacy_manifest_fixer/Build` directory, unless the `-s` command-line option is enabled):
|
||||
|
||||

|
||||
|
||||
## 📖 Usage
|
||||
|
||||
### Command Line Options
|
||||
|
||||
- **Force overwrite existing privacy manifest (Not recommended)**:
|
||||
|
||||
```shell
|
||||
sh install.sh <project_path> -f
|
||||
```
|
||||
|
||||
Enabling the `-f` option will force the tool to generate a new privacy manifest based on the API usage analysis and privacy manifest template, overwriting the existing privacy manifest. By default (without `-f`), the tool only fixes missing privacy manifests.
|
||||
|
||||
- **Silent mode**:
|
||||
|
||||
```shell
|
||||
sh install.sh <project_path> -s
|
||||
```
|
||||
|
||||
Enabling the `-s` option disables output during the fix process. The tool will no longer copy the generated `*.app`, automatically generate the privacy access report, or output the fix logs. By default (without `-s`), these outputs are stored in the `app_privacy_manifest_fixer/Build` directory.
|
||||
|
||||
- **Run only during installation builds (Recommended)**:
|
||||
|
||||
```shell
|
||||
sh install.sh <project_path> --install-builds-only
|
||||
```
|
||||
|
||||
Enabling the `--install-builds-only` option makes the tool run only during installation builds (such as the **Archive** operation), optimizing build performance for daily development. If you manually installed, this option is ineffective, and you need to manually check the **"For install builds only"** option.
|
||||
|
||||
**Note**: If the iOS/macOS project is built in a development environment (where the generated app contains `*.debug.dylib` files), the tool's API usage analysis results may be inaccurate.
|
||||
|
||||
### Upgrade the Tool
|
||||
|
||||
To update to the latest version, run the following command:
|
||||
|
||||
```shell
|
||||
sh upgrade.sh
|
||||
```
|
||||
|
||||
### Uninstall the Tool
|
||||
|
||||
To quickly uninstall the tool, run the following command:
|
||||
|
||||
```shell
|
||||
sh uninstall.sh <project_path>
|
||||
```
|
||||
|
||||
### Clean the Tool-Generated Files
|
||||
|
||||
To remove files generated by the tool, run the following command:
|
||||
|
||||
```shell
|
||||
sh clean.sh
|
||||
```
|
||||
|
||||
## 🔥 Privacy Manifest Templates
|
||||
|
||||
The privacy manifest templates are stored in the [`Templates`](https://github.com/crasowas/app_privacy_manifest_fixer/tree/main/Templates) directory, with the default templates already included in the root directory.
|
||||
|
||||
**How can you customize the privacy manifests for apps or SDKs? Simply use [custom templates](#custom-templates)!**
|
||||
|
||||
### Template Types
|
||||
|
||||
The templates are categorized as follows:
|
||||
- **AppTemplate.xcprivacy**: A privacy manifest template for the app.
|
||||
- **FrameworkTemplate.xcprivacy**: A generic privacy manifest template for frameworks.
|
||||
- **FrameworkName.xcprivacy**: A privacy manifest template for a specific framework, available only in the `Templates/UserTemplates` directory.
|
||||
|
||||
### Template Priority
|
||||
|
||||
For an app, the priority of privacy manifest templates is as follows:
|
||||
- `Templates/UserTemplates/AppTemplate.xcprivacy` > `Templates/AppTemplate.xcprivacy`
|
||||
|
||||
For a specific framework, the priority of privacy manifest templates is as follows:
|
||||
- `Templates/UserTemplates/FrameworkName.xcprivacy` > `Templates/UserTemplates/FrameworkTemplate.xcprivacy` > `Templates/FrameworkTemplate.xcprivacy`
|
||||
|
||||
### Default Templates
|
||||
|
||||
The default templates are located in the `Templates` root directory and currently include the following templates:
|
||||
- `Templates/AppTemplate.xcprivacy`
|
||||
- `Templates/FrameworkTemplate.xcprivacy`
|
||||
|
||||
These templates will be modified based on the API usage analysis results, especially the `NSPrivacyAccessedAPIType` entries, to generate new privacy manifests for fixes, ensuring compliance with App Store requirements.
|
||||
|
||||
**If adjustments to the privacy manifest template are needed, such as in the following scenarios, avoid directly modifying the default templates. Instead, use a custom template. If a custom template with the same name exists, it will take precedence over the default template for fixes.**
|
||||
- Generating a non-compliant privacy manifest due to inaccurate API usage analysis.
|
||||
- Modifying the reason declared in the template.
|
||||
- Adding declarations for collected data.
|
||||
|
||||
The privacy access API categories and their associated declared reasons in `AppTemplate.xcprivacy` are listed below:
|
||||
|
||||
| [NSPrivacyAccessedAPIType](https://developer.apple.com/documentation/bundleresources/app-privacy-configuration/nsprivacyaccessedapitypes/nsprivacyaccessedapitype) | [NSPrivacyAccessedAPITypeReasons](https://developer.apple.com/documentation/bundleresources/app-privacy-configuration/nsprivacyaccessedapitypes/nsprivacyaccessedapitypereasons) |
|
||||
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| NSPrivacyAccessedAPICategoryFileTimestamp | C617.1: Inside app or group container |
|
||||
| NSPrivacyAccessedAPICategorySystemBootTime | 35F9.1: Measure time on-device |
|
||||
| NSPrivacyAccessedAPICategoryDiskSpace | E174.1: Write or delete file on-device |
|
||||
| NSPrivacyAccessedAPICategoryActiveKeyboards | 54BD.1: Customize UI on-device |
|
||||
| NSPrivacyAccessedAPICategoryUserDefaults | CA92.1: Access info from same app |
|
||||
|
||||
The privacy access API categories and their associated declared reasons in `FrameworkTemplate.xcprivacy` are listed below:
|
||||
|
||||
| [NSPrivacyAccessedAPIType](https://developer.apple.com/documentation/bundleresources/app-privacy-configuration/nsprivacyaccessedapitypes/nsprivacyaccessedapitype) | [NSPrivacyAccessedAPITypeReasons](https://developer.apple.com/documentation/bundleresources/app-privacy-configuration/nsprivacyaccessedapitypes/nsprivacyaccessedapitypereasons) |
|
||||
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| NSPrivacyAccessedAPICategoryFileTimestamp | 0A2A.1: 3rd-party SDK wrapper on-device |
|
||||
| NSPrivacyAccessedAPICategorySystemBootTime | 35F9.1: Measure time on-device |
|
||||
| NSPrivacyAccessedAPICategoryDiskSpace | E174.1: Write or delete file on-device |
|
||||
| NSPrivacyAccessedAPICategoryActiveKeyboards | 54BD.1: Customize UI on-device |
|
||||
| NSPrivacyAccessedAPICategoryUserDefaults | C56D.1: 3rd-party SDK wrapper on-device |
|
||||
|
||||
### Custom Templates
|
||||
|
||||
To create custom templates, place them in the `Templates/UserTemplates` directory with the following structure:
|
||||
- `Templates/UserTemplates/AppTemplate.xcprivacy`
|
||||
- `Templates/UserTemplates/FrameworkTemplate.xcprivacy`
|
||||
- `Templates/UserTemplates/FrameworkName.xcprivacy`
|
||||
|
||||
Among these templates, only `FrameworkTemplate.xcprivacy` will be modified based on the API usage analysis results to adjust the `NSPrivacyAccessedAPIType` entries, thereby generating a new privacy manifest for framework fixes. The other templates will remain unchanged and will be directly used for fixes.
|
||||
|
||||
**Important Notes:**
|
||||
- The template for a specific framework must follow the naming convention `FrameworkName.xcprivacy`, where `FrameworkName` should match the name of the framework. For example, the template for `Flutter.framework` should be named `Flutter.xcprivacy`.
|
||||
- For macOS frameworks, the naming convention should be `FrameworkName.Version.xcprivacy`, where the version name is added to distinguish different versions. For a single version macOS framework, the `Version` is typically `A`.
|
||||
- The name of an SDK may not exactly match the name of the framework. To determine the correct framework name, check the `Frameworks` directory in the application bundle after building the project.
|
||||
|
||||
## 📑 Privacy Access Report
|
||||
|
||||
By default, the tool automatically generates privacy access reports for both the original and fixed versions of the app during each project build, and stores the reports in the `app_privacy_manifest_fixer/Build` directory.
|
||||
|
||||
If you need to manually generate a privacy access report for a specific app, run the following command:
|
||||
|
||||
```shell
|
||||
sh Report/report.sh <app_path> <report_output_path>
|
||||
# <app_path>: Path to the app (e.g., /path/to/App.app)
|
||||
# <report_output_path>: Path to save the report file (e.g., /path/to/report.html)
|
||||
```
|
||||
|
||||
**Note**: The report generated by the tool currently only includes the privacy access section (`NSPrivacyAccessedAPITypes`). To view the data collection section (`NSPrivacyCollectedDataTypes`), please use Xcode to generate the `PrivacyReport`.
|
||||
|
||||
### Sample Report Screenshots
|
||||
|
||||
| Original App Report (report-original.html) | Fixed App Report (report.html) |
|
||||
|------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|
|
||||
|  |  |
|
||||
|
||||
## 💡 Important Considerations
|
||||
|
||||
- If the latest version of the SDK supports a privacy manifest, please upgrade as soon as possible to avoid unnecessary risks.
|
||||
- This tool is a temporary solution and should not replace proper SDK management practices.
|
||||
- Before submitting your app for review, ensure that the privacy manifest fix complies with the latest App Store requirements.
|
||||
|
||||
## 🙌 Contributing
|
||||
|
||||
Contributions in any form are welcome, including code optimizations, bug fixes, documentation improvements, and more. Please follow the project's guidelines and maintain a consistent coding style. Thank you for your support!
|
||||
240
ios/App/app_privacy_manifest_fixer/README.zh-CN.md
Normal file
240
ios/App/app_privacy_manifest_fixer/README.zh-CN.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# App Privacy Manifest Fixer
|
||||
|
||||
[](https://github.com/crasowas/app_privacy_manifest_fixer/releases/latest)
|
||||

|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
**[English](./README.md) | 简体中文**
|
||||
|
||||
本工具是一个基于 Shell 脚本的自动化解决方案,旨在分析和修复 iOS/macOS App 的隐私清单,确保 App 符合 App Store 的要求。它利用 [App Store Privacy Manifest Analyzer](https://github.com/crasowas/app_store_required_privacy_manifest_analyser) 对 App 及其依赖项进行 API 使用分析,并生成或修复`PrivacyInfo.xcprivacy`文件。
|
||||
|
||||
## ✨ 功能特点
|
||||
|
||||
- **非侵入式集成**:无需修改源码或调整项目结构。
|
||||
- **极速安装与卸载**:一行命令即可快速完成工具的安装或卸载。
|
||||
- **自动分析与修复**:项目构建时自动分析 API 使用情况并修复隐私清单问题。
|
||||
- **灵活定制模板**:支持自定义 App 和 Framework 的隐私清单模板,满足多种使用场景。
|
||||
- **隐私访问报告**:自动生成报告用于查看 App 和 SDK 的`NSPrivacyAccessedAPITypes`声明情况。
|
||||
- **版本轻松升级**:提供升级脚本快速更新至最新版本。
|
||||
|
||||
## 📥 安装
|
||||
|
||||
### 下载工具
|
||||
|
||||
1. 下载[最新发布版本](https://github.com/crasowas/app_privacy_manifest_fixer/releases/latest)。
|
||||
2. 解压下载的文件:
|
||||
- 解压后的目录通常为`app_privacy_manifest_fixer-xxx`(其中`xxx`是版本号)。
|
||||
- 建议重命名为`app_privacy_manifest_fixer`,或在后续路径中使用完整目录名。
|
||||
- **建议将该目录移动至 iOS/macOS 项目中,以避免因路径问题在不同设备上运行时出现错误,同时便于为每个项目单独自定义隐私清单模板**。
|
||||
|
||||
### ⚡ 自动安装(推荐)
|
||||
|
||||
1. **切换到工具所在目录**:
|
||||
|
||||
```shell
|
||||
cd /path/to/app_privacy_manifest_fixer
|
||||
```
|
||||
|
||||
2. **运行以下安装脚本**:
|
||||
|
||||
```shell
|
||||
sh install.sh <project_path>
|
||||
```
|
||||
|
||||
- 如果是 Flutter 项目,`project_path`应为 Flutter 项目中的`ios/macos`目录路径。
|
||||
- 重复运行安装命令时,工具会先移除现有安装(如果有)。若需修改命令行选项,只需重新运行安装命令,无需先卸载。
|
||||
|
||||
### 手动安装
|
||||
|
||||
如果不使用安装脚本,可以手动添加`Fix Privacy Manifest`任务到 Xcode 的 **Build Phases** 完成安装。安装步骤如下:
|
||||
|
||||
#### 1. 在 Xcode 中添加脚本
|
||||
|
||||
- 用 Xcode 打开你的 iOS/macOS 项目,进入 **TARGETS** 选项卡,选择你的 App 目标。
|
||||
- 进入 **Build Phases**,点击 **+** 按钮,选择 **New Run Script Phase**。
|
||||
- 将新建的 **Run Script** 重命名为`Fix Privacy Manifest`。
|
||||
- 在 Shell 脚本框中添加以下代码:
|
||||
|
||||
```shell
|
||||
# 使用相对路径(推荐):如果`app_privacy_manifest_fixer`在项目目录内
|
||||
"$PROJECT_DIR/path/to/app_privacy_manifest_fixer/fixer.sh"
|
||||
|
||||
# 使用绝对路径:如果`app_privacy_manifest_fixer`不在项目目录内
|
||||
# "/absolute/path/to/app_privacy_manifest_fixer/fixer.sh"
|
||||
```
|
||||
|
||||
**请根据实际情况修改`path/to`或`absolute/path/to`,并确保路径正确。同时,删除或注释掉不适用的行**。
|
||||
|
||||
#### 2. 调整脚本执行顺序
|
||||
|
||||
**将该脚本移动到所有其他 Build Phases 之后,确保隐私清单在所有资源拷贝和编译任务完成后再进行修复**。
|
||||
|
||||
### Build Phases 截图
|
||||
|
||||
下面是自动/手动安装成功后的 Xcode Build Phases 配置截图(未启用任何命令行选项):
|
||||
|
||||

|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
安装后,工具将在每次构建项目时自动运行,构建完成后得到的 App 包已经是修复后的结果。
|
||||
|
||||
如果启用`--install-builds-only`命令行选项安装,工具将仅在安装构建时运行。
|
||||
|
||||
### Xcode Build Log 截图
|
||||
|
||||
下面是项目构建时工具输出的日志截图(默认会存储到`app_privacy_manifest_fixer/Build`目录,除非启用`-s`命令行选项):
|
||||
|
||||

|
||||
|
||||
## 📖 使用方法
|
||||
|
||||
### 命令行选项
|
||||
|
||||
- **强制覆盖现有隐私清单(不推荐)**:
|
||||
|
||||
```shell
|
||||
sh install.sh <project_path> -f
|
||||
```
|
||||
|
||||
启用`-f`选项后,工具会根据 API 使用分析结果和隐私清单模板生成新的隐私清单,并强制覆盖现有隐私清单。默认情况下(未启用`-f`),工具仅修复缺失的隐私清单。
|
||||
|
||||
- **静默模式**:
|
||||
|
||||
```shell
|
||||
sh install.sh <project_path> -s
|
||||
```
|
||||
|
||||
启用`-s`选项后,工具将禁用修复时的输出,不再复制构建生成的`*.app`、自动生成隐私访问报告或输出修复日志。默认情况下(未启用`-s`),这些输出存储在`app_privacy_manifest_fixer/Build`目录。
|
||||
|
||||
- **仅在安装构建时运行(推荐)**:
|
||||
|
||||
```shell
|
||||
sh install.sh <project_path> --install-builds-only
|
||||
```
|
||||
|
||||
启用`--install-builds-only`选项后,工具仅在执行安装构建(如 **Archive** 操作)时运行,以优化日常开发时的构建性能。如果你是手动安装的,该命令行选项无效,需要手动勾选 **"For install builds only"** 选项。
|
||||
|
||||
**注意**:如果 iOS/macOS 项目在开发环境构建(生成的 App 包含`*.debug.dylib`文件),工具的 API 使用分析结果可能不准确。
|
||||
|
||||
### 升级工具
|
||||
|
||||
要更新至最新版本,请运行以下命令:
|
||||
|
||||
```shell
|
||||
sh upgrade.sh
|
||||
```
|
||||
|
||||
### 卸载工具
|
||||
|
||||
要快速卸载本工具,请运行以下命令:
|
||||
|
||||
```shell
|
||||
sh uninstall.sh <project_path>
|
||||
```
|
||||
|
||||
### 清理工具生成的文件
|
||||
|
||||
要删除工具生成的文件,请运行以下命令:
|
||||
|
||||
```shell
|
||||
sh clean.sh
|
||||
```
|
||||
|
||||
## 🔥 隐私清单模板
|
||||
|
||||
隐私清单模板存储在[`Templates`](https://github.com/crasowas/app_privacy_manifest_fixer/tree/main/Templates)目录,其中根目录已经包含默认模板。
|
||||
|
||||
**如何为 App 或 SDK 自定义隐私清单?只需使用[自定义模板](#自定义模板)!**
|
||||
|
||||
### 模板类型
|
||||
|
||||
模板分为以下几类:
|
||||
- **AppTemplate.xcprivacy**:App 的隐私清单模板。
|
||||
- **FrameworkTemplate.xcprivacy**:通用的 Framework 隐私清单模板。
|
||||
- **FrameworkName.xcprivacy**:特定的 Framework 隐私清单模板,仅在`Templates/UserTemplates`目录有效。
|
||||
|
||||
### 模板优先级
|
||||
|
||||
对于 App,隐私清单模板的优先级如下:
|
||||
- `Templates/UserTemplates/AppTemplate.xcprivacy` > `Templates/AppTemplate.xcprivacy`
|
||||
|
||||
对于特定的 Framework,隐私清单模板的优先级如下:
|
||||
- `Templates/UserTemplates/FrameworkName.xcprivacy` > `Templates/UserTemplates/FrameworkTemplate.xcprivacy` > `Templates/FrameworkTemplate.xcprivacy`
|
||||
|
||||
### 默认模板
|
||||
|
||||
默认模板位于`Templates`根目录,目前包括以下模板:
|
||||
- `Templates/AppTemplate.xcprivacy`
|
||||
- `Templates/FrameworkTemplate.xcprivacy`
|
||||
|
||||
这些模板将根据 API 使用分析结果进行修改,特别是`NSPrivacyAccessedAPIType`条目将被调整,以生成新的隐私清单用于修复,确保符合 App Store 要求。
|
||||
|
||||
**如果需要调整隐私清单模板,例如以下场景,请避免直接修改默认模板,而是使用自定义模板。如果存在相同名称的自定义模板,它将优先于默认模板用于修复。**
|
||||
- 由于 API 使用分析结果不准确,生成了不合规的隐私清单。
|
||||
- 需要修改模板中声明的理由。
|
||||
- 需要声明收集的数据。
|
||||
|
||||
`AppTemplate.xcprivacy`中隐私访问 API 类别及其对应声明的理由如下:
|
||||
|
||||
| [NSPrivacyAccessedAPIType](https://developer.apple.com/documentation/bundleresources/app-privacy-configuration/nsprivacyaccessedapitypes/nsprivacyaccessedapitype) | [NSPrivacyAccessedAPITypeReasons](https://developer.apple.com/documentation/bundleresources/app-privacy-configuration/nsprivacyaccessedapitypes/nsprivacyaccessedapitypereasons) |
|
||||
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| NSPrivacyAccessedAPICategoryFileTimestamp | C617.1: Inside app or group container |
|
||||
| NSPrivacyAccessedAPICategorySystemBootTime | 35F9.1: Measure time on-device |
|
||||
| NSPrivacyAccessedAPICategoryDiskSpace | E174.1: Write or delete file on-device |
|
||||
| NSPrivacyAccessedAPICategoryActiveKeyboards | 54BD.1: Customize UI on-device |
|
||||
| NSPrivacyAccessedAPICategoryUserDefaults | CA92.1: Access info from same app |
|
||||
|
||||
`FrameworkTemplate.xcprivacy`中隐私访问 API 类别及其对应声明的理由如下:
|
||||
|
||||
| [NSPrivacyAccessedAPIType](https://developer.apple.com/documentation/bundleresources/app-privacy-configuration/nsprivacyaccessedapitypes/nsprivacyaccessedapitype) | [NSPrivacyAccessedAPITypeReasons](https://developer.apple.com/documentation/bundleresources/app-privacy-configuration/nsprivacyaccessedapitypes/nsprivacyaccessedapitypereasons) |
|
||||
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| NSPrivacyAccessedAPICategoryFileTimestamp | 0A2A.1: 3rd-party SDK wrapper on-device |
|
||||
| NSPrivacyAccessedAPICategorySystemBootTime | 35F9.1: Measure time on-device |
|
||||
| NSPrivacyAccessedAPICategoryDiskSpace | E174.1: Write or delete file on-device |
|
||||
| NSPrivacyAccessedAPICategoryActiveKeyboards | 54BD.1: Customize UI on-device |
|
||||
| NSPrivacyAccessedAPICategoryUserDefaults | C56D.1: 3rd-party SDK wrapper on-device |
|
||||
|
||||
### 自定义模板
|
||||
|
||||
要创建自定义模板,请将其放在`Templates/UserTemplates`目录,结构如下:
|
||||
- `Templates/UserTemplates/AppTemplate.xcprivacy`
|
||||
- `Templates/UserTemplates/FrameworkTemplate.xcprivacy`
|
||||
- `Templates/UserTemplates/FrameworkName.xcprivacy`
|
||||
|
||||
在这些模板中,只有`FrameworkTemplate.xcprivacy`会根据 API 使用分析结果对`NSPrivacyAccessedAPIType`条目进行调整,以生成新的隐私清单用于 Framework 修复。其他模板保持不变,将直接用于修复。
|
||||
|
||||
**重要说明:**
|
||||
- 特定的 Framework 模板必须遵循命名规范`FrameworkName.xcprivacy`,其中`FrameworkName`需与 Framework 的名称匹配。例如`Flutter.framework`的模板应命名为`Flutter.xcprivacy`。
|
||||
- 对于 macOS Framework,应遵循命名规范`FrameworkName.Version.xcprivacy`,额外增加版本名称用于区分不同的版本。对于单一版本的 macOS Framework,`Version`通常为`A`。
|
||||
- SDK 的名称可能与 Framework 的名称不完全一致。要确定正确的 Framework 名称,请在构建项目后检查 App 包中的`Frameworks`目录。
|
||||
|
||||
## 📑 隐私访问报告
|
||||
|
||||
默认情况下,工具会自动在每次构建时为原始 App 和修复后的 App 生成隐私访问报告,并存储到`app_privacy_manifest_fixer/Build`目录。
|
||||
|
||||
如果需要手动为特定 App 生成隐私访问报告,请运行以下命令:
|
||||
|
||||
```shell
|
||||
sh Report/report.sh <app_path> <report_output_path>
|
||||
# <app_path>: App路径(例如:/path/to/App.app)
|
||||
# <report_output_path>: 报告文件保存路径(例如:/path/to/report.html)
|
||||
```
|
||||
|
||||
**注意**:工具生成的报告目前仅包含隐私访问部分(`NSPrivacyAccessedAPITypes`),如果想看数据收集部分(`NSPrivacyCollectedDataTypes`)请使用 Xcode 生成`PrivacyReport`。
|
||||
|
||||
### 报告示例截图
|
||||
|
||||
| 原始 App 报告(report-original.html) | 修复后 App 报告(report.html) |
|
||||
|------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|
|
||||
|  |  |
|
||||
|
||||
## 💡 重要考量
|
||||
|
||||
- 如果最新版本的 SDK 支持隐私清单,请尽可能升级,以避免不必要的风险。
|
||||
- 此工具仅为临时解决方案,不应替代正确的 SDK 管理实践。
|
||||
- 在提交 App 审核之前,请检查隐私清单修复后是否符合最新的 App Store 要求。
|
||||
|
||||
## 🙌 贡献
|
||||
|
||||
欢迎任何形式的贡献,包括代码优化、Bug 修复、文档改进等。请确保遵循项目规范,并保持代码风格一致。感谢你的支持!
|
||||
124
ios/App/app_privacy_manifest_fixer/Report/report-template.html
Normal file
124
ios/App/app_privacy_manifest_fixer/Report/report-template.html
Normal file
@@ -0,0 +1,124 @@
|
||||
<!--
|
||||
Copyright (c) 2024, crasowas.
|
||||
|
||||
Use of this source code is governed by a MIT-style license
|
||||
that can be found in the LICENSE file or at
|
||||
https://opensource.org/licenses/MIT.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<title>Privacy Access Report</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
color: #333;
|
||||
background-color: #f9f9f9;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
min-width: 735px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.2em;
|
||||
margin: 0 0 15px;
|
||||
padding: 12px 20px;
|
||||
color: #fff;
|
||||
background-color: #5a9e6d;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h2 .version {
|
||||
font-size: 0.7em;
|
||||
color: #5a9e6d;
|
||||
background: #f1f1f1;
|
||||
padding: 2px 6px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #5a9e6d;
|
||||
background-color: #fcfcfc;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #5a9e6d;
|
||||
border-radius: 5px;
|
||||
font-size: 0.9em;
|
||||
margin-right: 16px;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #fff;
|
||||
background-color: #5a9e6d;
|
||||
}
|
||||
|
||||
a.warning {
|
||||
color: #e0b73c;
|
||||
background-color: #fcfcfc;
|
||||
border: 1px solid #e0b73c;
|
||||
}
|
||||
|
||||
a.warning:hover {
|
||||
color: #fff;
|
||||
background-color: #e0b73c;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 12px 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
color: #fff;
|
||||
background-color: #b0b8b1;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(odd) {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>
|
||||
This report was generated using version <strong>{{TOOL_VERSION}}</strong>.
|
||||
</span>
|
||||
<a href="https://github.com/crasowas/app_privacy_manifest_fixer" target="_blank">Like this
|
||||
project? 🌟Star it on GitHub!</a>
|
||||
</div>
|
||||
{{REPORT_CONTENT}}
|
||||
</body>
|
||||
</html>
|
||||
285
ios/App/app_privacy_manifest_fixer/Report/report.sh
Executable file
285
ios/App/app_privacy_manifest_fixer/Report/report.sh
Executable file
@@ -0,0 +1,285 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright (c) 2024, crasowas.
|
||||
#
|
||||
# Use of this source code is governed by a MIT-style license
|
||||
# that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
set -e
|
||||
|
||||
# Absolute path of the script and the tool's root directory
|
||||
script_path="$(realpath "$0")"
|
||||
tool_root_path="$(dirname "$(dirname "$script_path")")"
|
||||
|
||||
# Load common constants and utils
|
||||
source "$tool_root_path/Common/constants.sh"
|
||||
source "$tool_root_path/Common/utils.sh"
|
||||
|
||||
# Path to the app
|
||||
app_path="$1"
|
||||
|
||||
# Check if the app exists
|
||||
if [ ! -d "$app_path" ] || [[ "$app_path" != *.app ]]; then
|
||||
echo "Unable to find the app: $app_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if the app is iOS or macOS
|
||||
is_ios_app=true
|
||||
frameworks_dir="$app_path/Frameworks"
|
||||
if [ -d "$app_path/Contents/MacOS" ]; then
|
||||
is_ios_app=false
|
||||
frameworks_dir="$app_path/Contents/Frameworks"
|
||||
fi
|
||||
|
||||
report_output_file="$2"
|
||||
# Additional arguments as template usage records
|
||||
template_usage_records=("${@:2}")
|
||||
|
||||
# Copy report template to output file
|
||||
report_template_file="$tool_root_path/Report/report-template.html"
|
||||
if ! rsync -a "$report_template_file" "$report_output_file"; then
|
||||
echo "Failed to copy the report template to $report_output_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Read the current tool's version from the VERSION file
|
||||
tool_version_file="$tool_root_path/VERSION"
|
||||
tool_version="N/A"
|
||||
if [ -f "$tool_version_file" ]; then
|
||||
tool_version="$(cat "$tool_version_file")"
|
||||
fi
|
||||
|
||||
# Initialize report content
|
||||
report_content=""
|
||||
|
||||
# Get the template file used for fixing based on the app or framework name
|
||||
function get_used_template_file() {
|
||||
local name="$1"
|
||||
|
||||
for template_usage_record in "${template_usage_records[@]}"; do
|
||||
if [[ "$template_usage_record" == "$name$DELIMITER"* ]]; then
|
||||
echo "${template_usage_record#*$DELIMITER}"
|
||||
return
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Analyze accessed API types and their corresponding reasons
|
||||
function analyze_privacy_accessed_api() {
|
||||
local privacy_manifest_file="$1"
|
||||
local -a results=()
|
||||
|
||||
if [ -f "$privacy_manifest_file" ]; then
|
||||
local api_count=$(xmllint --xpath 'count(//dict/key[text()="NSPrivacyAccessedAPIType"])' "$privacy_manifest_file")
|
||||
|
||||
for ((i=1; i<=api_count; i++)); do
|
||||
local api_type=$(xmllint --xpath "(//dict/key[text()='NSPrivacyAccessedAPIType']/following-sibling::string[1])[$i]/text()" "$privacy_manifest_file" 2>/dev/null)
|
||||
local api_reasons=$(xmllint --xpath "(//dict/key[text()='NSPrivacyAccessedAPITypeReasons']/following-sibling::array[1])[position()=$i]/string/text()" "$privacy_manifest_file" 2>/dev/null | paste -sd "/" -)
|
||||
|
||||
if [ -z "$api_type" ]; then
|
||||
api_type="N/A"
|
||||
fi
|
||||
|
||||
if [ -z "$api_reasons" ]; then
|
||||
api_reasons="N/A"
|
||||
fi
|
||||
|
||||
results+=("$api_type$DELIMITER$api_reasons")
|
||||
done
|
||||
fi
|
||||
|
||||
echo "${results[@]}"
|
||||
}
|
||||
|
||||
# Get the path to the `Info.plist` file for the specified app or framework
|
||||
function get_plist_file() {
|
||||
local path="$1"
|
||||
local version_path="$2"
|
||||
local plist_file=""
|
||||
|
||||
if [[ "$path" == *.app ]]; then
|
||||
if [ "$is_ios_app" == true ]; then
|
||||
plist_file="$path/Info.plist"
|
||||
else
|
||||
plist_file="$path/Contents/Info.plist"
|
||||
fi
|
||||
elif [[ "$path" == *.framework ]]; then
|
||||
local framework_path="$(get_framework_path "$path" "$version_path")"
|
||||
|
||||
if [ "$is_ios_app" == true ]; then
|
||||
plist_file="$framework_path/Info.plist"
|
||||
else
|
||||
plist_file="$framework_path/Resources/Info.plist"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$plist_file"
|
||||
}
|
||||
|
||||
# Add an HTML <div> element with the `card` class
|
||||
function add_html_card_container() {
|
||||
local card="$1"
|
||||
|
||||
report_content="$report_content<div class=\"card\">$card</div>"
|
||||
}
|
||||
|
||||
# Generate an HTML <h2> element
|
||||
function generate_html_header() {
|
||||
local title="$1"
|
||||
local version="$2"
|
||||
|
||||
echo "<h2>$title<span class=\"version\">Version $version</span></h2>"
|
||||
}
|
||||
|
||||
# Generate an HTML <a> element with optional `warning` class
|
||||
function generate_html_anchor() {
|
||||
local text="$1"
|
||||
local href="$2"
|
||||
local warning="$3"
|
||||
|
||||
if [ "$warning" == true ]; then
|
||||
echo "<a class=\"warning\" href=\"$href\">$text</a>"
|
||||
else
|
||||
echo "<a href=\"$href\">$text</a>"
|
||||
fi
|
||||
}
|
||||
|
||||
# Generate an HTML <table> element
|
||||
function generate_html_table() {
|
||||
local thead="$1"
|
||||
local tbody="$2"
|
||||
|
||||
echo "<table>$thead$tbody</table>"
|
||||
}
|
||||
|
||||
# Generate an HTML <thead> element
|
||||
function generate_html_thead() {
|
||||
local ths=("$@")
|
||||
local tr=""
|
||||
|
||||
for th in "${ths[@]}"; do
|
||||
tr="$tr<th>$th</th>"
|
||||
done
|
||||
|
||||
echo "<thead><tr>$tr</tr></thead>"
|
||||
}
|
||||
|
||||
# Generate an HTML <tbody> element
|
||||
function generate_html_tbody() {
|
||||
local trs=("$@")
|
||||
local tbody=""
|
||||
|
||||
for tr in "${trs[@]}"; do
|
||||
tbody="$tbody<tr>"
|
||||
local tds=($(split_string_by_delimiter "$tr"))
|
||||
|
||||
for td in "${tds[@]}"; do
|
||||
tbody="$tbody<td>$td</td>"
|
||||
done
|
||||
|
||||
tbody="$tbody</tr>"
|
||||
done
|
||||
|
||||
echo "<tbody>$tbody</tbody>"
|
||||
}
|
||||
|
||||
# Generate the report content for the specified directory
|
||||
function generate_report_content() {
|
||||
local path="$1"
|
||||
local version_path="$2"
|
||||
local privacy_manifest_file=""
|
||||
|
||||
if [[ "$path" == *.app ]]; then
|
||||
# Per the documentation, the privacy manifest should be placed at the root of the app’s bundle for iOS, while for macOS, it should be located in `Contents/Resources/` within the app’s bundle
|
||||
# Reference: https://developer.apple.com/documentation/bundleresources/adding-a-privacy-manifest-to-your-app-or-third-party-sdk#Add-a-privacy-manifest-to-your-app
|
||||
if [ "$is_ios_app" == true ]; then
|
||||
privacy_manifest_file="$path/$PRIVACY_MANIFEST_FILE_NAME"
|
||||
else
|
||||
privacy_manifest_file="$path/Contents/Resources/$PRIVACY_MANIFEST_FILE_NAME"
|
||||
fi
|
||||
else
|
||||
# Per the documentation, the privacy manifest should be placed at the root of the iOS framework, while for a macOS framework with multiple versions, it should be located in the `Resources` directory within the corresponding version
|
||||
# Some SDKs don’t follow the guideline, so we use a search-based approach for now
|
||||
# Reference: https://developer.apple.com/documentation/bundleresources/adding-a-privacy-manifest-to-your-app-or-third-party-sdk#Add-a-privacy-manifest-to-your-framework
|
||||
local framework_path="$(get_framework_path "$path" "$version_path")"
|
||||
local privacy_manifest_files=($(search_privacy_manifest_files "$framework_path"))
|
||||
privacy_manifest_file="$(get_privacy_manifest_file "${privacy_manifest_files[@]}")"
|
||||
fi
|
||||
|
||||
local name="$(basename "$path")"
|
||||
local title="$name"
|
||||
if [ -n "$version_path" ]; then
|
||||
title="$name ($version_path)"
|
||||
fi
|
||||
|
||||
local plist_file="$(get_plist_file "$path" "$version_path")"
|
||||
local version="$(get_plist_version "$plist_file")"
|
||||
local card="$(generate_html_header "$title" "$version")"
|
||||
|
||||
if [ -f "$privacy_manifest_file" ]; then
|
||||
card="$card$(generate_html_anchor "$PRIVACY_MANIFEST_FILE_NAME" "$privacy_manifest_file" false)"
|
||||
|
||||
local used_template_file="$(get_used_template_file "$name$version_path")"
|
||||
|
||||
if [ -f "$used_template_file" ]; then
|
||||
card="$card$(generate_html_anchor "Template Used: $(basename "$used_template_file")" "$used_template_file" false)"
|
||||
fi
|
||||
|
||||
local trs=($(analyze_privacy_accessed_api "$privacy_manifest_file"))
|
||||
|
||||
# Generate table only if the accessed privacy API types array is not empty
|
||||
if [[ ${#trs[@]} -gt 0 ]]; then
|
||||
local thead="$(generate_html_thead "NSPrivacyAccessedAPIType" "NSPrivacyAccessedAPITypeReasons")"
|
||||
local tbody="$(generate_html_tbody "${trs[@]}")"
|
||||
|
||||
card="$card$(generate_html_table "$thead" "$tbody")"
|
||||
fi
|
||||
else
|
||||
card="$card$(generate_html_anchor "Missing Privacy Manifest" "$path" true)"
|
||||
fi
|
||||
|
||||
add_html_card_container "$card"
|
||||
}
|
||||
|
||||
# Generate the report content for app
|
||||
function generate_app_report_content() {
|
||||
generate_report_content "$app_path" ""
|
||||
}
|
||||
|
||||
# Generate the report content for frameworks
|
||||
function generate_frameworks_report_content() {
|
||||
if ! [ -d "$frameworks_dir" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
for path in "$frameworks_dir"/*; do
|
||||
if [ -d "$path" ]; then
|
||||
local versions_dir="$path/Versions"
|
||||
|
||||
if [ -d "$versions_dir" ]; then
|
||||
for version in $(ls -1 "$versions_dir" | grep -vE '^Current$'); do
|
||||
local version_path="Versions/$version"
|
||||
generate_report_content "$path" "$version_path"
|
||||
done
|
||||
else
|
||||
generate_report_content "$path" ""
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Generate the final report with all content
|
||||
function generate_final_report() {
|
||||
# Replace placeholders in the template with the tool's version and report content
|
||||
sed -i "" -e "s|{{TOOL_VERSION}}|$tool_version|g" -e "s|{{REPORT_CONTENT}}|${report_content}|g" "$report_output_file"
|
||||
|
||||
echo "Privacy Access Report has been generated: $report_output_file"
|
||||
}
|
||||
|
||||
generate_app_report_content
|
||||
generate_frameworks_report_content
|
||||
generate_final_report
|
||||
@@ -0,0 +1,55 @@
|
||||
<?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>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>35F9.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>E174.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryActiveKeyboards</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>54BD.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,55 @@
|
||||
<?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>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>0A2A.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>35F9.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>E174.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryActiveKeyboards</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>54BD.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C56D.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
1
ios/App/app_privacy_manifest_fixer/VERSION
Normal file
1
ios/App/app_privacy_manifest_fixer/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
v1.4.1
|
||||
29
ios/App/app_privacy_manifest_fixer/clean.sh
Executable file
29
ios/App/app_privacy_manifest_fixer/clean.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright (c) 2025, crasowas.
|
||||
#
|
||||
# Use of this source code is governed by a MIT-style license
|
||||
# that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
set -e
|
||||
|
||||
target_paths=("Build")
|
||||
|
||||
echo "Cleaning..."
|
||||
|
||||
deleted_anything=false
|
||||
|
||||
for path in "${target_paths[@]}"; do
|
||||
if [ -e "$path" ]; then
|
||||
echo "Removing $path..."
|
||||
rm -rf "./$path"
|
||||
deleted_anything=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$deleted_anything" == true ]; then
|
||||
echo "Cleanup completed."
|
||||
else
|
||||
echo "Nothing to clean."
|
||||
fi
|
||||
490
ios/App/app_privacy_manifest_fixer/fixer.sh
Executable file
490
ios/App/app_privacy_manifest_fixer/fixer.sh
Executable file
@@ -0,0 +1,490 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright (c) 2024, crasowas.
|
||||
#
|
||||
# Use of this source code is governed by a MIT-style license
|
||||
# that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
set -e
|
||||
|
||||
# Absolute path of the script and the tool's root directory
|
||||
script_path="$(realpath "$0")"
|
||||
tool_root_path="$(dirname "$script_path")"
|
||||
|
||||
# Load common constants and utils
|
||||
source "$tool_root_path/Common/constants.sh"
|
||||
source "$tool_root_path/Common/utils.sh"
|
||||
|
||||
# Force replace the existing privacy manifest when the `-f` option is enabled
|
||||
force=false
|
||||
|
||||
# Enable silent mode to disable build output when the `-s` option is enabled
|
||||
silent=false
|
||||
|
||||
# Parse command-line options
|
||||
while getopts ":fs" opt; do
|
||||
case $opt in
|
||||
f)
|
||||
force=true
|
||||
;;
|
||||
s)
|
||||
silent=true
|
||||
;;
|
||||
\?)
|
||||
echo "Invalid option: -$OPTARG" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
# Path of the app produced by the build process
|
||||
app_path="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
|
||||
|
||||
# Check if the app exists
|
||||
if [ ! -d "$app_path" ] || [[ "$app_path" != *.app ]]; then
|
||||
echo "Unable to find the app: $app_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if the app is iOS or macOS
|
||||
is_ios_app=true
|
||||
frameworks_dir="$app_path/Frameworks"
|
||||
if [ -d "$app_path/Contents/MacOS" ]; then
|
||||
is_ios_app=false
|
||||
frameworks_dir="$app_path/Contents/Frameworks"
|
||||
fi
|
||||
|
||||
# Default template directories
|
||||
templates_dir="$tool_root_path/Templates"
|
||||
user_templates_dir="$tool_root_path/Templates/UserTemplates"
|
||||
|
||||
# Use user-defined app privacy manifest template if it exists, otherwise fallback to default
|
||||
app_template_file="$user_templates_dir/$APP_TEMPLATE_FILE_NAME"
|
||||
if [ ! -f "$app_template_file" ]; then
|
||||
app_template_file="$templates_dir/$APP_TEMPLATE_FILE_NAME"
|
||||
fi
|
||||
|
||||
# Use user-defined framework privacy manifest template if it exists, otherwise fallback to default
|
||||
framework_template_file="$user_templates_dir/$FRAMEWORK_TEMPLATE_FILE_NAME"
|
||||
if [ ! -f "$framework_template_file" ]; then
|
||||
framework_template_file="$templates_dir/$FRAMEWORK_TEMPLATE_FILE_NAME"
|
||||
fi
|
||||
|
||||
# Disable build output in silent mode
|
||||
if [ "$silent" == false ]; then
|
||||
# Script used to generate the privacy access report
|
||||
report_script="$tool_root_path/Report/report.sh"
|
||||
# An array to record template usage for generating the privacy access report
|
||||
template_usage_records=()
|
||||
|
||||
# Build output directory
|
||||
build_dir="$tool_root_path/Build/${PRODUCT_NAME}-${CONFIGURATION}_${MARKETING_VERSION}_${CURRENT_PROJECT_VERSION}_$(date +%Y%m%d%H%M%S)"
|
||||
# Ensure the build directory exists
|
||||
mkdir -p "$build_dir"
|
||||
|
||||
# Redirect both stdout and stderr to a log file while keeping console output
|
||||
exec > >(tee "$build_dir/fix.log") 2>&1
|
||||
fi
|
||||
|
||||
# Get the path to the `Info.plist` file for the specified app or framework
|
||||
function get_plist_file() {
|
||||
local path="$1"
|
||||
local version_path="$2"
|
||||
local plist_file=""
|
||||
|
||||
if [[ "$path" == *.app ]]; then
|
||||
if [ "$is_ios_app" == true ]; then
|
||||
plist_file="$path/Info.plist"
|
||||
else
|
||||
plist_file="$path/Contents/Info.plist"
|
||||
fi
|
||||
elif [[ "$path" == *.framework ]]; then
|
||||
local framework_path="$(get_framework_path "$path" "$version_path")"
|
||||
|
||||
if [ "$is_ios_app" == true ]; then
|
||||
plist_file="$framework_path/Info.plist"
|
||||
else
|
||||
plist_file="$framework_path/Resources/Info.plist"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$plist_file"
|
||||
}
|
||||
|
||||
# Get the path to the executable for the specified app or framework
|
||||
function get_executable_path() {
|
||||
local path="$1"
|
||||
local version_path="$2"
|
||||
local executable_path=""
|
||||
|
||||
local plist_file="$(get_plist_file "$path" "$version_path")"
|
||||
local executable_name="$(get_plist_executable "$plist_file")"
|
||||
|
||||
if [[ "$path" == *.app ]]; then
|
||||
if [ "$is_ios_app" == true ]; then
|
||||
executable_path="$path/$executable_name"
|
||||
else
|
||||
executable_path="$path/Contents/MacOS/$executable_name"
|
||||
fi
|
||||
elif [[ "$path" == *.framework ]]; then
|
||||
local framework_path="$(get_framework_path "$path" "$version_path")"
|
||||
executable_path="$framework_path/$executable_name"
|
||||
fi
|
||||
|
||||
echo "$executable_path"
|
||||
}
|
||||
|
||||
# Analyze the specified binary file for API symbols and their categories
|
||||
function analyze_binary_file() {
|
||||
local path="$1"
|
||||
local -a results=()
|
||||
|
||||
# Check if the API symbol exists in the binary file using `nm` and `strings`
|
||||
local nm_output=$(nm "$path" 2>/dev/null | xcrun swift-demangle)
|
||||
local strings_output=$(strings "$path")
|
||||
local combined_output="$nm_output"$'\n'"$strings_output"
|
||||
|
||||
for api_symbol in "${API_SYMBOLS[@]}"; do
|
||||
local substrings=($(split_string_by_delimiter "$api_symbol"))
|
||||
local category=${substrings[0]}
|
||||
local api=${substrings[1]}
|
||||
|
||||
if echo "$combined_output" | grep -E "$api\$" >/dev/null; then
|
||||
local index=-1
|
||||
for ((i=0; i < ${#results[@]}; i++)); do
|
||||
local result="${results[i]}"
|
||||
local result_substrings=($(split_string_by_delimiter "$result"))
|
||||
# If the category matches an existing result, update it
|
||||
if [ "$category" == "${result_substrings[0]}" ]; then
|
||||
index=i
|
||||
results[i]="${result_substrings[0]}$DELIMITER${result_substrings[1]},$api$DELIMITER${result_substrings[2]}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# If no matching category found, add a new result
|
||||
if [[ $index -eq -1 ]]; then
|
||||
results+=("$category$DELIMITER$api$DELIMITER$(encode_path "$path")")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "${results[@]}"
|
||||
}
|
||||
|
||||
# Analyze API usage in a binary file
|
||||
function analyze_api_usage() {
|
||||
local path="$1"
|
||||
local version_path="$2"
|
||||
local -a results=()
|
||||
|
||||
local binary_file="$(get_executable_path "$path" "$version_path")"
|
||||
|
||||
if [ -f "$binary_file" ]; then
|
||||
results+=($(analyze_binary_file "$binary_file"))
|
||||
fi
|
||||
|
||||
echo "${results[@]}"
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Get unique categories from analysis results
|
||||
function get_categories() {
|
||||
local results=("$@")
|
||||
local -a categories=()
|
||||
|
||||
for result in "${results[@]}"; do
|
||||
local substrings=($(split_string_by_delimiter "$result"))
|
||||
local category=${substrings[0]}
|
||||
if [[ ! "${categories[@]}" =~ "$category" ]]; then
|
||||
categories+=("$category")
|
||||
fi
|
||||
done
|
||||
|
||||
echo "${categories[@]}"
|
||||
}
|
||||
|
||||
# Get template file for the specified app or framework
|
||||
function get_template_file() {
|
||||
local path="$1"
|
||||
local version_path="$2"
|
||||
local template_file=""
|
||||
|
||||
if [[ "$path" == *.app ]]; then
|
||||
template_file="$app_template_file"
|
||||
else
|
||||
# Give priority to the user-defined framework privacy manifest template
|
||||
local dep_name="$(get_dependency_name "$path")"
|
||||
if [ -n "$version_path" ]; then
|
||||
dep_name="$dep_name.$(basename "$version_path")"
|
||||
fi
|
||||
|
||||
local dep_template_file="$user_templates_dir/${dep_name}.xcprivacy"
|
||||
if [ -f "$dep_template_file" ]; then
|
||||
template_file="$dep_template_file"
|
||||
else
|
||||
template_file="$framework_template_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$template_file"
|
||||
}
|
||||
|
||||
# Check if the specified template file should be modified
|
||||
#
|
||||
# The following template files will be modified based on analysis:
|
||||
# * Templates/AppTemplate.xcprivacy
|
||||
# * Templates/FrameworkTemplate.xcprivacy
|
||||
# * Templates/UserTemplates/FrameworkTemplate.xcprivacy
|
||||
function is_template_modifiable() {
|
||||
local template_file="$1"
|
||||
|
||||
local template_file_name="$(basename "$template_file")"
|
||||
|
||||
if [[ "$template_file" != "$user_templates_dir"* ]] || [ "$template_file_name" == "$FRAMEWORK_TEMPLATE_FILE_NAME" ]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if `Hardened Runtime` is enabled for the specified path
|
||||
function is_hardened_runtime_enabled() {
|
||||
local path="$1"
|
||||
|
||||
# Check environment variable first
|
||||
if [ "${ENABLE_HARDENED_RUNTIME:-}" == "YES" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check the code signature flags if environment variable is not set
|
||||
local flags=$(codesign -dvvv "$path" 2>&1 | grep "flags=")
|
||||
if echo "$flags" | grep -q "runtime"; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Re-sign the target app or framework if code signing is enabled
|
||||
function resign() {
|
||||
local path="$1"
|
||||
|
||||
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" ] && [ "${CODE_SIGNING_REQUIRED:-}" != "NO" ] && [ "${CODE_SIGNING_ALLOWED:-}" != "NO" ]; then
|
||||
echo "Re-signing $path with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME:-$EXPANDED_CODE_SIGN_IDENTITY}"
|
||||
|
||||
local codesign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements"
|
||||
|
||||
if [ "$is_ios_app" == true ]; then
|
||||
$codesign_cmd "$path"
|
||||
else
|
||||
if is_hardened_runtime_enabled "$path"; then
|
||||
codesign_cmd="$codesign_cmd -o runtime"
|
||||
fi
|
||||
|
||||
if [ -d "$path/Contents/MacOS" ]; then
|
||||
find "$path/Contents/MacOS" -type f -name "*.dylib" | while read -r dylib_file; do
|
||||
$codesign_cmd "$dylib_file"
|
||||
done
|
||||
fi
|
||||
|
||||
$codesign_cmd "$path"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Fix the privacy manifest for the app or specified framework
|
||||
# To accelerate the build, existing privacy manifests will be left unchanged unless the `-f` option is enabled
|
||||
# After fixing, the app or framework will be automatically re-signed
|
||||
function fix() {
|
||||
local path="$1"
|
||||
local version_path="$2"
|
||||
local force_resign="$3"
|
||||
local privacy_manifest_file=""
|
||||
|
||||
if [[ "$path" == *.app ]]; then
|
||||
# Per the documentation, the privacy manifest should be placed at the root of the app’s bundle for iOS, while for macOS, it should be located in `Contents/Resources/` within the app’s bundle
|
||||
# Reference: https://developer.apple.com/documentation/bundleresources/adding-a-privacy-manifest-to-your-app-or-third-party-sdk#Add-a-privacy-manifest-to-your-app
|
||||
if [ "$is_ios_app" == true ]; then
|
||||
privacy_manifest_file="$path/$PRIVACY_MANIFEST_FILE_NAME"
|
||||
else
|
||||
privacy_manifest_file="$path/Contents/Resources/$PRIVACY_MANIFEST_FILE_NAME"
|
||||
fi
|
||||
else
|
||||
# Per the documentation, the privacy manifest should be placed at the root of the iOS framework, while for a macOS framework with multiple versions, it should be located in the `Resources` directory within the corresponding version
|
||||
# Some SDKs don’t follow the guideline, so we use a search-based approach for now
|
||||
# Reference: https://developer.apple.com/documentation/bundleresources/adding-a-privacy-manifest-to-your-app-or-third-party-sdk#Add-a-privacy-manifest-to-your-framework
|
||||
local framework_path="$(get_framework_path "$path" "$version_path")"
|
||||
local privacy_manifest_files=($(search_privacy_manifest_files "$framework_path"))
|
||||
privacy_manifest_file="$(get_privacy_manifest_file "${privacy_manifest_files[@]}")"
|
||||
|
||||
if [ -z "$privacy_manifest_file" ]; then
|
||||
if [ "$is_ios_app" == true ]; then
|
||||
privacy_manifest_file="$framework_path/$PRIVACY_MANIFEST_FILE_NAME"
|
||||
else
|
||||
privacy_manifest_file="$framework_path/Resources/$PRIVACY_MANIFEST_FILE_NAME"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if the privacy manifest file exists
|
||||
if [ -f "$privacy_manifest_file" ]; then
|
||||
echo "💡 Found privacy manifest file: $privacy_manifest_file"
|
||||
|
||||
if [ "$force" == false ]; then
|
||||
if [ "$force_resign" == true ]; then
|
||||
resign "$path"
|
||||
fi
|
||||
echo "✅ Privacy manifest file already exists, skipping fix."
|
||||
return
|
||||
fi
|
||||
else
|
||||
echo "⚠️ Missing privacy manifest file!"
|
||||
fi
|
||||
|
||||
local results=($(analyze_api_usage "$path" "$version_path"))
|
||||
echo "API usage analysis result(s): ${#results[@]}"
|
||||
print_array "${results[@]}"
|
||||
|
||||
local template_file="$(get_template_file "$path" "$version_path")"
|
||||
template_usage_records+=("$(basename "$path")$version_path$DELIMITER$template_file")
|
||||
|
||||
# Copy the template file to the privacy manifest location, overwriting if it exists
|
||||
cp "$template_file" "$privacy_manifest_file"
|
||||
|
||||
if is_template_modifiable "$template_file"; then
|
||||
local categories=($(get_categories "${results[@]}"))
|
||||
local remove_categories=()
|
||||
|
||||
# Check if categories is non-empty
|
||||
if [[ ${#categories[@]} -gt 0 ]]; then
|
||||
# Convert categories to a single space-separated string for easy matching
|
||||
local categories_set=" ${categories[*]} "
|
||||
|
||||
# Iterate through each element in `API_CATEGORIES`
|
||||
for element in "${API_CATEGORIES[@]}"; do
|
||||
# If element is not found in `categories_set`, add it to `remove_categories`
|
||||
if [[ ! $categories_set =~ " $element " ]]; then
|
||||
remove_categories+=("$element")
|
||||
fi
|
||||
done
|
||||
else
|
||||
# If categories is empty, add all of `API_CATEGORIES` to `remove_categories`
|
||||
remove_categories=("${API_CATEGORIES[@]}")
|
||||
fi
|
||||
|
||||
# Remove extra spaces in the XML file to simplify node removal
|
||||
xmllint --noblanks "$privacy_manifest_file" -o "$privacy_manifest_file"
|
||||
|
||||
# Build a sed command to remove all matching nodes at once
|
||||
local sed_pattern=""
|
||||
for category in "${remove_categories[@]}"; do
|
||||
# Find the node for the current category
|
||||
local remove_node="$(xmllint --xpath "//dict[string='$category']" "$privacy_manifest_file" 2>/dev/null || true)"
|
||||
|
||||
# If the node is found, escape special characters and append it to the sed pattern
|
||||
if [ -n "$remove_node" ]; then
|
||||
local escaped_node=$(echo "$remove_node" | sed 's/[\/&]/\\&/g')
|
||||
sed_pattern+="s/$escaped_node//g;"
|
||||
fi
|
||||
done
|
||||
|
||||
# Apply the combined sed pattern to the file if it's not empty
|
||||
if [ -n "$sed_pattern" ]; then
|
||||
sed -i "" "$sed_pattern" "$privacy_manifest_file"
|
||||
fi
|
||||
|
||||
# Reformat the XML file to restore spaces for readability
|
||||
xmllint --format "$privacy_manifest_file" -o "$privacy_manifest_file"
|
||||
fi
|
||||
|
||||
resign "$path"
|
||||
|
||||
echo "✅ Privacy manifest file fixed: $privacy_manifest_file."
|
||||
}
|
||||
|
||||
# Fix privacy manifests for all frameworks
|
||||
function fix_frameworks() {
|
||||
if ! [ -d "$frameworks_dir" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo "🛠️ Fixing Frameworks..."
|
||||
for path in "$frameworks_dir"/*; do
|
||||
if [ -d "$path" ]; then
|
||||
local dep_name="$(get_dependency_name "$path")"
|
||||
local versions_dir="$path/Versions"
|
||||
|
||||
if [ -d "$versions_dir" ]; then
|
||||
for version in $(ls -1 "$versions_dir" | grep -vE '^Current$'); do
|
||||
local version_path="Versions/$version"
|
||||
echo "Analyzing $dep_name ($version_path) ..."
|
||||
fix "$path" "$version_path" false
|
||||
echo ""
|
||||
done
|
||||
else
|
||||
echo "Analyzing $dep_name ..."
|
||||
fix "$path" "" false
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Fix the privacy manifest for the app
|
||||
function fix_app() {
|
||||
echo "🛠️ Fixing $(basename "$app_path" .app) App..."
|
||||
# Since the framework may have undergone fixes, the app must be forcefully re-signed
|
||||
fix "$app_path" "" true
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Generate the privacy access report for the app
|
||||
function generate_report() {
|
||||
local original="$1"
|
||||
|
||||
if [ "$silent" == true ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
local app_name="$(basename "$app_path")"
|
||||
local name="${app_name%.*}"
|
||||
local report_name=""
|
||||
|
||||
# Adjust output names if the app is flagged as original
|
||||
if [ "$original" == true ]; then
|
||||
app_name="${name}-original.app"
|
||||
report_name="report-original.html"
|
||||
else
|
||||
app_name="$name.app"
|
||||
report_name="report.html"
|
||||
fi
|
||||
|
||||
local target_app_path="$build_dir/$app_name"
|
||||
local report_path="$build_dir/$report_name"
|
||||
|
||||
echo "Copy app to $target_app_path"
|
||||
rsync -a "$app_path/" "$target_app_path/"
|
||||
|
||||
# Generate the privacy access report using the script
|
||||
sh "$report_script" "$target_app_path" "$report_path" "${template_usage_records[@]}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
start_time=$(date +%s)
|
||||
|
||||
generate_report true
|
||||
fix_frameworks
|
||||
fix_app
|
||||
generate_report false
|
||||
|
||||
end_time=$(date +%s)
|
||||
|
||||
echo "🎉 All fixed! ⏰ $((end_time - start_time)) seconds"
|
||||
echo "🌟 If you found this script helpful, please consider giving it a star on GitHub. Your support is appreciated. Thank you!"
|
||||
echo "🔗 Homepage: https://github.com/crasowas/app_privacy_manifest_fixer"
|
||||
echo "🐛 Report issues: https://github.com/crasowas/app_privacy_manifest_fixer/issues"
|
||||
71
ios/App/app_privacy_manifest_fixer/install.sh
Executable file
71
ios/App/app_privacy_manifest_fixer/install.sh
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright (c) 2024, crasowas.
|
||||
#
|
||||
# Use of this source code is governed by a MIT-style license
|
||||
# that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
set -e
|
||||
|
||||
# Check if at least one argument (project_path) is provided
|
||||
if [[ "$#" -lt 1 ]]; then
|
||||
echo "Usage: $0 <project_path> [options...]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
project_path="$1"
|
||||
|
||||
shift
|
||||
|
||||
options=()
|
||||
install_builds_only=false
|
||||
|
||||
# Check if the `--install-builds-only` option is provided and separate it from other options
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" == "--install-builds-only" ]; then
|
||||
install_builds_only=true
|
||||
else
|
||||
options+=("$arg")
|
||||
fi
|
||||
done
|
||||
|
||||
# Verify Ruby installation
|
||||
if ! command -v ruby &>/dev/null; then
|
||||
echo "Ruby is not installed. Please install Ruby and try again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if xcodeproj gem is installed
|
||||
if ! gem list -i xcodeproj &>/dev/null; then
|
||||
echo "The 'xcodeproj' gem is not installed."
|
||||
read -p "Would you like to install it now? [Y/n] " response
|
||||
if [[ "$response" =~ ^[Nn]$ ]]; then
|
||||
echo "Please install 'xcodeproj' manually and re-run the script."
|
||||
exit 1
|
||||
fi
|
||||
gem install xcodeproj || { echo "Failed to install 'xcodeproj'."; exit 1; }
|
||||
fi
|
||||
|
||||
# Convert project path to an absolute path if it is relative
|
||||
if [[ ! "$project_path" = /* ]]; then
|
||||
project_path="$(realpath "$project_path")"
|
||||
fi
|
||||
|
||||
# Absolute path of the script and the tool's root directory
|
||||
script_path="$(realpath "$0")"
|
||||
tool_root_path="$(dirname "$script_path")"
|
||||
|
||||
tool_portable_path="$tool_root_path"
|
||||
# If the tool's root directory is inside the project path, make the path portable
|
||||
if [[ "$tool_root_path" == "$project_path"* ]]; then
|
||||
# Extract the path of the tool's root directory relative to the project path
|
||||
tool_relative_path="${tool_root_path#$project_path}"
|
||||
# Formulate a portable path using the `PROJECT_DIR` environment variable provided by Xcode
|
||||
tool_portable_path="\${PROJECT_DIR}${tool_relative_path}"
|
||||
fi
|
||||
|
||||
run_script_content="\"$tool_portable_path/fixer.sh\" ${options[@]}"
|
||||
|
||||
# Execute the Ruby helper script
|
||||
ruby "$tool_root_path/Helper/xcode_install_helper.rb" "$project_path" "$run_script_content" "$install_builds_only"
|
||||
46
ios/App/app_privacy_manifest_fixer/uninstall.sh
Executable file
46
ios/App/app_privacy_manifest_fixer/uninstall.sh
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright (c) 2024, crasowas.
|
||||
#
|
||||
# Use of this source code is governed by a MIT-style license
|
||||
# that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
set -e
|
||||
|
||||
# Check if the project path is provided
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Usage: $0 <project_path>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
project_path="$1"
|
||||
|
||||
# Verify Ruby installation
|
||||
if ! command -v ruby &>/dev/null; then
|
||||
echo "Ruby is not installed. Please install Ruby and try again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if xcodeproj gem is installed
|
||||
if ! gem list -i xcodeproj &>/dev/null; then
|
||||
echo "The 'xcodeproj' gem is not installed."
|
||||
read -p "Would you like to install it now? [Y/n] " response
|
||||
if [[ "$response" =~ ^[Nn]$ ]]; then
|
||||
echo "Please install 'xcodeproj' manually and re-run the script."
|
||||
exit 1
|
||||
fi
|
||||
gem install xcodeproj || { echo "Failed to install 'xcodeproj'."; exit 1; }
|
||||
fi
|
||||
|
||||
# Convert project path to an absolute path if it is relative
|
||||
if [[ ! "$project_path" = /* ]]; then
|
||||
project_path="$(realpath "$project_path")"
|
||||
fi
|
||||
|
||||
# Absolute path of the script and the tool's root directory
|
||||
script_path="$(realpath "$0")"
|
||||
tool_root_path="$(dirname "$script_path")"
|
||||
|
||||
# Execute the Ruby helper script
|
||||
ruby "$tool_root_path/Helper/xcode_uninstall_helper.rb" "$project_path"
|
||||
108
ios/App/app_privacy_manifest_fixer/upgrade.sh
Executable file
108
ios/App/app_privacy_manifest_fixer/upgrade.sh
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright (c) 2024, crasowas.
|
||||
#
|
||||
# Use of this source code is governed by a MIT-style license
|
||||
# that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
set -e
|
||||
|
||||
# Absolute path of the script and the tool's root directory
|
||||
script_path="$(realpath "$0")"
|
||||
tool_root_path="$(dirname "$script_path")"
|
||||
|
||||
# Repository details
|
||||
readonly REPO_OWNER="crasowas"
|
||||
readonly REPO_NAME="app_privacy_manifest_fixer"
|
||||
|
||||
# URL to fetch the latest release information
|
||||
readonly LATEST_RELEASE_URL="https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/releases/latest"
|
||||
|
||||
# Fetch the release information from GitHub API
|
||||
release_info=$(curl -s "$LATEST_RELEASE_URL")
|
||||
|
||||
# Extract the latest release version, download URL, and published time
|
||||
latest_version=$(echo "$release_info" | grep -o '"tag_name": "[^"]*' | sed 's/"tag_name": "//')
|
||||
download_url=$(echo "$release_info" | grep -o '"zipball_url": "[^"]*' | sed 's/"zipball_url": "//')
|
||||
published_time=$(echo "$release_info" | grep -o '"published_at": "[^"]*' | sed 's/"published_at": "//')
|
||||
|
||||
# Ensure the latest version, download URL, and published time are successfully retrieved
|
||||
if [ -z "$latest_version" ] || [ -z "$download_url" ] || [ -z "$published_time" ]; then
|
||||
echo "Unable to fetch the latest release information."
|
||||
echo "Request URL: $LATEST_RELEASE_URL"
|
||||
echo "Response Data: $release_info"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Convert UTC time to local time
|
||||
published_time=$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$published_time" +"%s" | xargs -I{} date -j -r {} +"%Y-%m-%d %H:%M:%S %z")
|
||||
|
||||
# Read the current tool's version from the VERSION file
|
||||
tool_version_file="$tool_root_path/VERSION"
|
||||
if [ ! -f "$tool_version_file" ]; then
|
||||
echo "VERSION file not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local_version="$(cat "$tool_version_file")"
|
||||
|
||||
# Skip upgrade if the current version is already the latest
|
||||
if [ "$local_version" == "$latest_version" ]; then
|
||||
echo "Version $latest_version • $published_time"
|
||||
echo "Already up-to-date."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create a temporary directory for downloading the release
|
||||
temp_dir=$(mktemp -d)
|
||||
trap "rm -rf $temp_dir" EXIT
|
||||
|
||||
download_file_name="latest-release.tar.gz"
|
||||
|
||||
# Download the latest release archive
|
||||
echo "Downloading version $latest_version..."
|
||||
curl -L "$download_url" -o "$temp_dir/$download_file_name"
|
||||
|
||||
# Check if the download was successful
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Download failed, please check your network connection and try again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract the downloaded release archive
|
||||
echo "Extracting files..."
|
||||
tar -xzf "$temp_dir/$download_file_name" -C "$temp_dir"
|
||||
|
||||
# Find the extracted release
|
||||
extracted_release_path=$(find "$temp_dir" -mindepth 1 -maxdepth 1 -type d -name "*$REPO_NAME*" | head -n 1)
|
||||
|
||||
# Verify that an extracted release was found
|
||||
if [ -z "$extracted_release_path" ]; then
|
||||
echo "No extracted release found for the latest version."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
user_templates_dir="$tool_root_path/Templates/UserTemplates"
|
||||
user_templates_backup_dir="$temp_dir/Templates/UserTemplates"
|
||||
|
||||
# Backup the user templates directory if it exists
|
||||
if [ -d "$user_templates_dir" ]; then
|
||||
echo "Backing up user templates..."
|
||||
mkdir -p "$user_templates_backup_dir"
|
||||
rsync -a --exclude='.*' "$user_templates_dir/" "$user_templates_backup_dir/"
|
||||
fi
|
||||
|
||||
# Replace old version files with the new version files
|
||||
echo "Replacing old version files..."
|
||||
rsync -a --delete "$extracted_release_path/" "$tool_root_path/"
|
||||
|
||||
# Restore the user templates from the backup
|
||||
if [ -d "$user_templates_backup_dir" ]; then
|
||||
echo "Restoring user templates..."
|
||||
rsync -a --exclude='.*' "$user_templates_backup_dir/" "$user_templates_dir/"
|
||||
fi
|
||||
|
||||
# Upgrade complete
|
||||
echo "Version $latest_version • $published_time"
|
||||
echo "Upgrade completed successfully!"
|
||||
Reference in New Issue
Block a user