# App Privacy Manifest Fixer

[![Latest Version](https://img.shields.io/github/v/release/crasowas/app_privacy_manifest_fixer?logo=github)](https://github.com/crasowas/app_privacy_manifest_fixer/releases/latest)
![Supported Platforms](https://img.shields.io/badge/Supported%20Platforms-iOS%20%7C%20macOS-brightgreen)
[![License](https://img.shields.io/badge/License-MIT-green.svg)](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 配置截图(未启用任何命令行选项):

![Build Phases Screenshot](https://img.crasowas.dev/app_privacy_manifest_fixer/20250225011407.png)

## 🚀 快速开始

安装后,工具将在每次构建项目时自动运行,构建完成后得到的 App 包已经是修复后的结果。

如果启用`--install-builds-only`命令行选项安装,工具将仅在安装构建时运行。

### Xcode Build Log 截图

下面是项目构建时工具输出的日志截图(默认会存储到`app_privacy_manifest_fixer/Build`目录,除非启用`-s`命令行选项):

![Xcode Build Log Screenshot](https://img.crasowas.dev/app_privacy_manifest_fixer/20250225011551.png)

## 📖 使用方法

### 命令行选项

- **强制覆盖现有隐私清单(不推荐)**:

  ```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)                                                                     |
|------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|
| ![Original App Report](https://img.crasowas.dev/app_privacy_manifest_fixer/20241218230746.png) | ![Fixed App Report](https://img.crasowas.dev/app_privacy_manifest_fixer/20241218230822.png) |

## 💡 重要考量 

- 如果最新版本的 SDK 支持隐私清单,请尽可能升级,以避免不必要的风险。
- 此工具仅为临时解决方案,不应替代正确的 SDK 管理实践。
- 在提交 App 审核之前,请检查隐私清单修复后是否符合最新的 App Store 要求。

## 🙌 贡献

欢迎任何形式的贡献,包括代码优化、Bug 修复、文档改进等。请确保遵循项目规范,并保持代码风格一致。感谢你的支持!