mirror of
https://github.com/micahflee/TM-SGNL-iOS.git
synced 2025-08-03 08:12:16 +00:00
initial commit
This commit is contained in:
commit
dde0620daf
4747 changed files with 1314116 additions and 0 deletions
17
.clang-format
Normal file
17
.clang-format
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
BasedOnStyle: WebKit
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
ColumnLimit: 120
|
||||
IndentCaseLabels: true
|
||||
MaxEmptyLinesToKeep: 2
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PointerBindsToType: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
SortIncludes: CaseSensitive
|
||||
...
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.strings diff=localizablestrings
|
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Copyright 2022 Signal Messenger, LLC
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
custom: https://signal.org/donate/
|
76
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
76
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,76 @@
|
|||
---
|
||||
name: 🛠️ Bug report
|
||||
about: Create a report about a technical issue
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- This is a bug report template. By following the instructions below and filling out the sections with your information, you will help the developers get all the necessary data to fix your issue.
|
||||
You can also preview your report before submitting it. You may remove sections that aren't relevant to your particular case.
|
||||
|
||||
Before we begin, please note that this tracker is only for issues. It is not for questions, comments, or feature requests.
|
||||
|
||||
If you would like to discuss a new feature or submit suggestions, please visit the community forum:
|
||||
https://community.signalusers.org
|
||||
|
||||
If you are looking for support, please visit our support center:
|
||||
https://support.signal.org/
|
||||
or email support@signal.org
|
||||
|
||||
Let's begin with a checklist: Replace the empty checkboxes [ ] below with checked ones [x] accordingly. -->
|
||||
|
||||
- [ ] I have searched open and closed issues for duplicates
|
||||
- [ ] I am submitting a bug report for existing functionality that does not work as intended
|
||||
- [ ] This isn't a feature request or a discussion topic
|
||||
|
||||
----------------------------------------
|
||||
|
||||
### Bug description
|
||||
Describe here the issue that you are experiencing.
|
||||
|
||||
### Steps to reproduce
|
||||
- using hyphens as bullet points
|
||||
- list the steps
|
||||
- that reproduce the bug
|
||||
|
||||
**Actual result:** Describe here what happens after you run the steps above (i.e. the buggy behaviour)
|
||||
|
||||
**Expected result:** Describe here what should happen after you run the steps above (i.e. what would be the correct behaviour)
|
||||
|
||||
### Screenshots
|
||||
<!-- you can drag and drop images below -->
|
||||
|
||||
|
||||
### Device info
|
||||
<!-- replace the examples with your info -->
|
||||
**Device**: iDevice X
|
||||
|
||||
**iOS version**: X.Y.Z
|
||||
|
||||
**Signal version:** Z.Y
|
||||
|
||||
### Link to debug log
|
||||
<!-- Ensure that "Enable Debug Log" is on in Signal's settings then make the bug happen and immediately after that tap "Submit Debug Log" from settings and paste the link below. -->
|
||||
|
||||
<!-- If this is a crashing bug, after filing this issue, email a copy of your latest crash report to support@whispersystems.org
|
||||
|
||||
To get a crash log:
|
||||
|
||||
1. Go to the iOS Settings app.
|
||||
2. Go to Privacy.
|
||||
3. Go to Analytics or Diagnostics & Usage.
|
||||
4. Select Analytics Data or Diagnostics & Usage Data.
|
||||
5. Locate the .ips crash log for Signal.
|
||||
The logs will be named in the format: Signal(DateTime).ips
|
||||
6. Select the desired Signal log.
|
||||
7.a iOS 11 users, tap the Share icon in the top right corner and jump to step 10.
|
||||
7.b iOS 9&10 users, long press to see the option to highlight text and select the entire text of the log. It will end in EOF.
|
||||
8. Once the text is selected, tap Copy.
|
||||
9. Paste the copied text into an email.
|
||||
10. Send the email to support@signal.org with a subject like:
|
||||
* "iOS Crash Log: (your github issue)"
|
||||
* Example subject: iOS Crash Log: Crash on launch #111
|
||||
* Example subject: iOS Crash Log: Crash when sending video #222
|
||||
-->
|
20
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
20
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 📃 Support Center
|
||||
url: https://support.signal.org/
|
||||
about: Find answers to many common questions.
|
||||
- name: ✨ Feature request
|
||||
url: https://community.signalusers.org/c/feature-requests/ios-feature-requests/
|
||||
about: Missing something in Signal? Let us know.
|
||||
- name: 💬 Community support
|
||||
url: https://community.signalusers.org/c/support/
|
||||
about: Feel free to ask anything.
|
||||
- name: 📖 Developer documentation
|
||||
url: https://signal.org/docs/
|
||||
about: Official Signal developer documentation.
|
||||
- name: 📚 Translation feedback.
|
||||
url: https://community.signalusers.org/c/translation-feedback/
|
||||
about: Share feedback on translations.
|
||||
- name: ❓ Other issue?
|
||||
url: https://community.signalusers.org/
|
||||
about: Search on the community forums.
|
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
<!-- You can remove this first section if you have contributed before -->
|
||||
### First time contributor checklist
|
||||
<!-- replace the empty checkboxes [ ] below with checked ones [x] accordingly -->
|
||||
- [ ] I have read the [README](https://github.com/signalapp/Signal-iOS/blob/main/README.md) and [CONTRIBUTING](https://github.com/signalapp/Signal-iOS/blob/main/CONTRIBUTING.md) documents
|
||||
- [ ] I have signed the [Contributor Licence Agreement](https://signal.org/cla/)
|
||||
|
||||
### Contributor checklist
|
||||
<!-- replace the empty checkboxes [ ] below with checked ones [x] accordingly -->
|
||||
- [ ] My commits are rebased on the latest main branch
|
||||
- [ ] My commits are in nice logical chunks
|
||||
- [ ] My contribution is fully baked and is ready to be merged as is
|
||||
- [ ] I have tested my contribution on these devices:
|
||||
* iDevice A, iOS X.Y.Z
|
||||
* iDevice B, iOS Z.Y
|
||||
|
||||
- - - - - - - - - -
|
||||
|
||||
### Description
|
||||
<!--
|
||||
Describe briefly what your pull request proposes to fix. Especially if you have more than one commit, it is helpful to give a summary of what your contribution as a whole is trying to solve. You can also use the `fixes #1234` syntax to refer to specific issues either here or in your commit message.
|
||||
Also, please describe shortly how you tested that your fix actually works.
|
||||
-->
|
55
.github/actions/clone-everything/action.yml
vendored
Normal file
55
.github/actions/clone-everything/action.yml
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
name: "Clone everything"
|
||||
description: "Checks out the repo, and initializes submodules. Must check out repo first."
|
||||
|
||||
inputs:
|
||||
access-token:
|
||||
description: "Private pods access token"
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
|
||||
steps:
|
||||
- id: checkout_repo
|
||||
name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Check out submodules through GitHub instead of from scratch later.
|
||||
# THIS STEP WILL FAIL if the current Pods commit is only available in Private.
|
||||
# However, we can continue and the build will still pass.
|
||||
submodules: recursive
|
||||
continue-on-error: ${{ inputs.access-token != '' }}
|
||||
|
||||
- id: private_pods_ref
|
||||
name: Compute Private Pods Ref
|
||||
if: ${{ steps.checkout_repo.outcome == 'failure' }}
|
||||
shell: bash
|
||||
run: echo "ref=$(git ls-tree --object-only HEAD Pods)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Checkout private pods repo iff we have an access token to read private repos
|
||||
- name: Checkout Private Pods
|
||||
uses: actions/checkout@v4
|
||||
if: ${{ steps.checkout_repo.outcome == 'failure' }}
|
||||
with:
|
||||
repository: signalapp/Signal-Pods-Private
|
||||
token: ${{ inputs.access-token }}
|
||||
path: Pods
|
||||
ref: ${{ steps.private_pods_ref.outputs.ref }}
|
||||
|
||||
- id: message_backup_tests_ref
|
||||
name: Compute Signal-Message-Backup-Tests Ref
|
||||
if: ${{ steps.checkout_repo.outcome == 'failure' }}
|
||||
shell: bash
|
||||
run: echo "ref=$(git ls-tree --object-only HEAD SignalServiceKit/tests/MessageBackup/Signal-Message-Backup-Tests)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout Signal-Message-Backup-Tests
|
||||
uses: actions/checkout@v4
|
||||
if: ${{ steps.checkout_repo.outcome == 'failure' }}
|
||||
with:
|
||||
repository: signalapp/Signal-Message-Backup-Tests
|
||||
path: SignalServiceKit/tests/MessageBackup/Signal-Message-Backup-Tests
|
||||
ref: ${{ steps.message_backup_tests_ref.outputs.ref }}
|
||||
|
||||
- name: Fetch RingRTC
|
||||
shell: bash
|
||||
run: make fetch-ringrtc
|
72
.github/stale.yml
vendored
Normal file
72
.github/stale.yml
vendored
Normal file
|
@ -0,0 +1,72 @@
|
|||
# Copyright 2021-2022 Signal Messenger, LLC
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 90
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
daysUntilClose: 7
|
||||
|
||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||
onlyLabels: []
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- Accessibility
|
||||
- Acknowledged
|
||||
- Bug
|
||||
- Regression
|
||||
- "Don't mark stale"
|
||||
- "Feature Request"
|
||||
- "Good starter task"
|
||||
- "Upstream Change Needed"
|
||||
- "PR: Needs Review"
|
||||
- "PR: Ready to Merge"
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: false
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: false
|
||||
|
||||
# Set to true to ignore issues with an assignee (defaults to false)
|
||||
exemptAssignees: true
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
|
||||
# Comment to post when removing the stale label.
|
||||
# unmarkComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Comment to post when closing a stale Issue or Pull Request.
|
||||
closeComment: >
|
||||
This issue has been closed due to inactivity.
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 5
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
|
||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
||||
# pulls:
|
||||
# daysUntilStale: 30
|
||||
# markComment: >
|
||||
# This pull request has been automatically marked as stale because it has not had
|
||||
# recent activity. It will be closed if no further activity occurs. Thank you
|
||||
# for your contributions.
|
||||
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - confirmed
|
||||
|
120
.github/workflows/main.yml
vendored
Normal file
120
.github/workflows/main.yml
vendored
Normal file
|
@ -0,0 +1,120 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release/*
|
||||
|
||||
# On PRs, "head_ref" is defined and is consistent across updates. On
|
||||
# pushes, it's not defined, so we use "run_id", which is unique across
|
||||
# every run; as a result, all actions on pushes will run to completion.
|
||||
#
|
||||
# Reference: https://docs.github.com/en/actions/using-jobs/using-concurrency
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# Path format pulled from https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md#xcode
|
||||
DEVELOPER_DIR: /Applications/Xcode_16.1.app
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Build and Test
|
||||
timeout-minutes: 20
|
||||
runs-on: macos-15-xlarge
|
||||
env:
|
||||
FASTLANE_XCODEBUILD_SETTINGS_RETRIES: 7
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
# Add additional Xcode versions here if necessary.
|
||||
xcode: ["Xcode_16.1"]
|
||||
|
||||
steps:
|
||||
- name: Set Xcode version
|
||||
run: |
|
||||
echo DEVELOPER_DIR=/Applications/${{matrix.xcode}}.app >> "$GITHUB_ENV"
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check Xcode version
|
||||
run: |
|
||||
Scripts/check_xcode_version.py
|
||||
|
||||
- uses: ./.github/actions/clone-everything
|
||||
with:
|
||||
access-token: ${{ secrets.ACCESS_TOKEN }}
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1 # Reads .ruby-version file by default
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
- name: Build and Test
|
||||
run: |
|
||||
function formatFailures() {
|
||||
grep '<failure message' fastlane/test_output/report.junit | sed -E "s/^.*<failure message='(.*)'>(.*):([0-9]+)<\/failure>/::error file=\2,line=\3::\1/" | sed -E 's/"/"/g'
|
||||
exit 1
|
||||
}
|
||||
|
||||
bundle exec fastlane scan \
|
||||
--scheme Signal \
|
||||
--output_types junit \
|
||||
--skip_package_dependencies_resolution \
|
||||
--disable_package_automatic_updates \
|
||||
--xcargs '-test-timeouts-enabled YES -maximum-test-execution-time-allowance 300 -default-test-execution-time-allowance 60' \
|
||||
|| formatFailures
|
||||
|
||||
- name: Upload build logs
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: Logs
|
||||
path: ~/Library/Logs/scan
|
||||
|
||||
check_autogenstrings:
|
||||
name: Check if strings file is outdated
|
||||
|
||||
timeout-minutes: 10
|
||||
|
||||
runs-on: macos-15
|
||||
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run genstrings
|
||||
run: Scripts/translation/auto-genstrings
|
||||
|
||||
- name: Check for any changes
|
||||
run: git diff --exit-code
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
|
||||
timeout-minutes: 5
|
||||
|
||||
runs-on: macos-15
|
||||
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Fetch base commit
|
||||
run: git fetch origin --depth 1 ${{ github.base_ref }}
|
||||
|
||||
- name: Install Dependencies
|
||||
run: brew install clang-format swiftlint
|
||||
|
||||
- name: Lint files changed in the PR
|
||||
run: |
|
||||
Scripts/precommit.py --ref origin/${{ github.base_ref }}
|
||||
|
||||
# https://help.github.com/en/actions/reference/development-tools-for-github-actions#logging-commands
|
||||
git diff --name-only | sed -E 's|(.*)|::error file=\1::Incorrectly formatted (Scripts/precommit.py)|'
|
||||
git diff --exit-code || exit 1
|
42
.gitignore
vendored
Normal file
42
.gitignore
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
# Exclude user build config files
|
||||
Config/User*.xcconfig
|
||||
|
||||
# Exclude the build directory
|
||||
build/*
|
||||
# Exclude temp nibs and swap files
|
||||
*~.nib
|
||||
*.swp
|
||||
|
||||
# Exclude OS X folder attributes
|
||||
.DS_Store
|
||||
|
||||
# Exclude user-specific XCode 3 and 4 files
|
||||
xcuserdata
|
||||
*.xccheckout
|
||||
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
Index/
|
||||
|
||||
# Python
|
||||
*.pyc
|
||||
*.sdsjson
|
||||
Scripts/sds_codegen/sds-includes/*
|
||||
|
||||
/.idea
|
||||
/.vscode
|
||||
|
||||
fastlane/Preview.html
|
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
[submodule "Pods"]
|
||||
path = Pods
|
||||
url = https://github.com/signalapp/Signal-Pods.git
|
||||
[submodule "SignalServiceKit/tests/MessageBackup/Signal-Message-Backup-Tests"]
|
||||
path = SignalServiceKit/tests/MessageBackup/Signal-Message-Backup-Tests
|
||||
url = https://github.com/signalapp/Signal-Message-Backup-Tests
|
1
.ruby-version
Normal file
1
.ruby-version
Normal file
|
@ -0,0 +1 @@
|
|||
3.2.2
|
55
.swiftlint.yml
Normal file
55
.swiftlint.yml
Normal file
|
@ -0,0 +1,55 @@
|
|||
excluded:
|
||||
- Pods
|
||||
- ThirdParty
|
||||
- SignalServiceKit/Protos/Generated
|
||||
- SignalServiceKit/protobuf/Backups
|
||||
# SwiftLint has trouble with these files. See <https://github.com/realm/SwiftLint/issues/3988>.
|
||||
- Signal/Emoji/Emoji+SkinTones.swift
|
||||
- Signal/Emoji/EmojiWithSkinTones+String.swift
|
||||
disabled_rules:
|
||||
- block_based_kvo
|
||||
- compiler_protocol_init
|
||||
- control_statement
|
||||
- cyclomatic_complexity
|
||||
- empty_enum_arguments
|
||||
- file_length
|
||||
- for_where
|
||||
- force_cast
|
||||
- force_try
|
||||
- function_body_length
|
||||
- function_parameter_count
|
||||
- identifier_name
|
||||
- line_length
|
||||
- multiple_closures_with_trailing_closure
|
||||
- nesting
|
||||
- no_fallthrough_only
|
||||
- non_optional_string_data_conversion
|
||||
- notification_center_detachment
|
||||
- opening_brace
|
||||
- redundant_void_return
|
||||
- static_over_final_class
|
||||
- todo
|
||||
- trailing_comma
|
||||
- type_body_length
|
||||
- unneeded_synthesized_initializer
|
||||
- unused_closure_parameter
|
||||
- unused_optional_binding
|
||||
opt_in_rules:
|
||||
- comma_inheritance
|
||||
- empty_string
|
||||
- sorted_first_last
|
||||
inclusive_language:
|
||||
override_allowed_terms: ["master", "whitelist"]
|
||||
large_tuple:
|
||||
warning: 4
|
||||
error: 4
|
||||
type_name:
|
||||
min_length:
|
||||
warning: 2
|
||||
error: 2
|
||||
max_length:
|
||||
warning: 500
|
||||
error: 500
|
||||
excluded:
|
||||
- Id
|
||||
allowed_symbols: ["_"]
|
1
.xcode-version
Normal file
1
.xcode-version
Normal file
|
@ -0,0 +1 @@
|
|||
Xcode 16.1
|
69
BUILDING.md
Normal file
69
BUILDING.md
Normal file
|
@ -0,0 +1,69 @@
|
|||
# Building
|
||||
|
||||
We typically develop against the latest stable version of Xcode.
|
||||
|
||||
## 1. Clone
|
||||
|
||||
Clone the repo to a working directory:
|
||||
|
||||
```
|
||||
git clone --recurse-submodules https://github.com/signalapp/Signal-iOS
|
||||
```
|
||||
|
||||
Since we make use of sub-modules, you must use `git clone`, rather than
|
||||
downloading a prepared zip file from Github.
|
||||
|
||||
We recommend you fork the repo on GitHub, then clone your fork:
|
||||
|
||||
```
|
||||
git clone --recurse-submodules https://github.com/<USERNAME>/Signal-iOS.git
|
||||
```
|
||||
|
||||
You can then add the Signal repo to sync with upstream changes:
|
||||
|
||||
```
|
||||
git remote add upstream https://github.com/signalapp/Signal-iOS
|
||||
```
|
||||
|
||||
## 2. Dependencies
|
||||
|
||||
To build and configure the libraries Signal uses, just run:
|
||||
|
||||
```
|
||||
make dependencies
|
||||
```
|
||||
|
||||
## 3. Xcode
|
||||
|
||||
Open the `Signal.xcworkspace` in Xcode.
|
||||
|
||||
```
|
||||
open Signal.xcworkspace
|
||||
```
|
||||
|
||||
In the TARGETS area of the General tab, change the Team drop down to
|
||||
your own. You will need to do that for all the listed targets, for ex.
|
||||
Signal, SignalShareExtension, and SignalNSE. You will need an Apple
|
||||
Developer account for this.
|
||||
|
||||
On the Capabilities tab, turn off Push Notifications, Apple Pay,
|
||||
Communication Notifications, and Data Protection, while keeping Background Modes
|
||||
on. The App Groups capability will need to remain on in order to access the
|
||||
shared data storage. The best way to change the bundle ID for the app groups is
|
||||
setting `SIGNAL_BUNDLEID_PREFIX` in the project's settings.
|
||||
|
||||
If you wish to test the Documents API, the iCloud capability will need to
|
||||
be on with the iCloud Documents option selected.
|
||||
|
||||
Build and Run and you are ready to go!
|
||||
|
||||
## Known issues
|
||||
|
||||
Features related to push notifications are known to be not working for
|
||||
third-party contributors since Apple's Push Notification service pushes
|
||||
will only work with Open Whisper Systems production code signing
|
||||
certificate.
|
||||
|
||||
Turn on Push Notifications on the Capabilities tab if you want to register a new Signal account using the application installed via XCode.
|
||||
|
||||
If you have any other issues, please ask on the [community forum](https://community.signalusers.org/).
|
74
CONTRIBUTING.md
Normal file
74
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,74 @@
|
|||
# Contributing to Signal iOS
|
||||
|
||||
Thank you for supporting Signal and looking for ways to help. Please note that some conventions here might be a bit different than what you are used to, even if you have contributed to other open source projects before. Reading this document will help you save time and work effectively with the developers and other contributors.
|
||||
|
||||
|
||||
## Development Ideology
|
||||
|
||||
Truths which we believe to be self-evident:
|
||||
|
||||
1. **The answer is not more options.** If you feel compelled to add a preference that's exposed to the user, it's very possible you've made a wrong turn somewhere.
|
||||
1. **The user doesn't know what a key is.** We need to minimize the points at which a user is exposed to this sort of terminology as extremely as possible.
|
||||
1. **There are no power users.** The idea that some users "understand" concepts better than others has proven to be, for the most part, false. If anything, "power users" are more dangerous than the rest, and we should avoid exposing dangerous functionality to them.
|
||||
1. **If it's "like PGP," it's wrong.** PGP is our guide for what not to do.
|
||||
1. **It's an asynchronous world.** Be wary of anything that is anti-asynchronous: ACKs, protocol confirmations, or any protocol-level "advisory" message.
|
||||
1. **There is no such thing as time.** Protocol ideas that require synchronized clocks are doomed to failure.
|
||||
|
||||
|
||||
## Issues
|
||||
|
||||
Please search both open and closed issues to make sure your bug report is not a duplicate.
|
||||
|
||||
### The issue tracker is for bugs, not feature requests
|
||||
The GitHub issue tracker is not used for feature requests, but new ideas can be submitted and discussed on the [community forum](https://community.signalusers.org/c/feature-requests). The purpose of this issue tracker is to track bugs in the iOS client. Bug reports should only be submitted for existing functionality that does not work as intended. Comments that are relevant and concise will help the developers solve issues more quickly. The ["Beta Feedback" category on the community forum](https://community.signalusers.org/c/beta-feedback/) is the best place to share beta feedback.
|
||||
|
||||
### Send support questions to support
|
||||
You can reach support by sending an email to support@signal.org or by visiting the [Signal Support Center](https://support.signal.org/) where you can also search for existing troubleshooting articles and find answers to frequently asked questions. Please do not post support questions on the GitHub issue tracker.
|
||||
|
||||
### GitHub is not a generic discussion forum
|
||||
Conversations about open bug reports belong here. However, all other discussions should take place on the [community forum](https://community.signalusers.org). You can use the community forum to discuss anything that is related to Signal or to hang out with your fellow users in the "Off Topic" category.
|
||||
|
||||
### Don't bump issues
|
||||
Every time someone comments on an issue, GitHub sends an email to [hundreds of people](https://github.com/signalapp/Signal-iOS/watchers). Bumping issues with a "+1" (or asking for updates) generates a lot of unnecessary email notifications and does not help anyone solve the issue any faster. Please be respectful of everyone's time and only comment when you have new information to add.
|
||||
|
||||
### Open issues
|
||||
|
||||
#### If it's open, it's tracked
|
||||
The developers read every issue, but high-priority bugs or features can take precedence over others. Signal is an open source project, and everyone is encouraged to play an active role in diagnosing and fixing open issues.
|
||||
|
||||
### Closed issues
|
||||
|
||||
#### "My issue was closed without giving a reason!"
|
||||
Although we do our best, writing detailed explanations for every issue can be time consuming, and the topic also might have been covered previously in other related issues.
|
||||
|
||||
|
||||
## Pull requests
|
||||
|
||||
### Smaller is better
|
||||
Big changes are significantly less likely to be accepted. Large features often require protocol modifications and necessitate a staged rollout process that is coordinated across millions of users on multiple platforms (Android, iOS, and Desktop).
|
||||
|
||||
Try not to take on too much at once. As a first-time contributor, we recommend starting with small and simple PRs in order to become familiar with the codebase. Most of the work should go into discovering which three lines need to change rather than writing the code.
|
||||
|
||||
### Sign the Contributor License Agreement (CLA)
|
||||
You will need to [sign our CLA](https://signal.org/cla/) before your pull request can be merged.
|
||||
|
||||
### Submit finished and well-tested pull requests
|
||||
Please do not submit pull requests that are still a work in progress. Pull requests should be thoroughly tested and ready to merge before they are submitted.
|
||||
|
||||
### Merging can sometimes take a while
|
||||
If your pull request follows all of the advice above but still has not been merged, this usually means that the developers haven't had time to review it yet. We understand that this might feel frustrating, and we apologize. The Signal team is still small, but [we are hiring](https://signal.org/workworkwork/).
|
||||
|
||||
|
||||
## How can I contribute?
|
||||
There are several other ways to get involved:
|
||||
* Help new users learn about Signal.
|
||||
* Redirect support questions to support@signal.org and the [Signal Support Center](https://support.signal.org/).
|
||||
* Redirect non-bug discussions to the [community forum](https://community.signalusers.org).
|
||||
* Find and mark duplicate issues.
|
||||
* Try to reproduce issues and help with troubleshooting.
|
||||
* Discover solutions to open issues and post any relevant findings.
|
||||
* Test other people's pull requests.
|
||||
* [Donate to Signal.](https://signal.org/donate/)
|
||||
* Share Signal with your friends and family.
|
||||
|
||||
Signal is made for you. Thank you for your feedback and support.
|
9
Config/Project-Debug.xcconfig
Normal file
9
Config/Project-Debug.xcconfig
Normal file
|
@ -0,0 +1,9 @@
|
|||
//
|
||||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
#include "Project.xcconfig"
|
||||
|
||||
// User-specific overrides
|
||||
#include? "User-Debug.xcconfig"
|
9
Config/Project-Release.xcconfig
Normal file
9
Config/Project-Release.xcconfig
Normal file
|
@ -0,0 +1,9 @@
|
|||
//
|
||||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
#include "Project.xcconfig"
|
||||
|
||||
// User-specific overrides
|
||||
#include? "User-Release.xcconfig"
|
12
Config/Project.xcconfig
Normal file
12
Config/Project.xcconfig
Normal file
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES
|
||||
GCC_TREAT_WARNINGS_AS_ERRORS = YES
|
||||
MTL_TREAT_WARNINGS_AS_ERRORS = YES
|
||||
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES
|
||||
|
||||
// User-specific overrides
|
||||
#include? "User.xcconfig"
|
6
Config/User.xcconfig.sample
Normal file
6
Config/User.xcconfig.sample
Normal file
|
@ -0,0 +1,6 @@
|
|||
//
|
||||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
SWIFT_TREAT_WARNINGS_AS_ERRORS = NO
|
6
Gemfile
Normal file
6
Gemfile
Normal file
|
@ -0,0 +1,6 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'cocoapods'
|
||||
gem 'fastlane'
|
||||
gem 'anbt-sql-formatter'
|
||||
gem 'xcode-install'
|
301
Gemfile.lock
Normal file
301
Gemfile.lock
Normal file
|
@ -0,0 +1,301 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.7)
|
||||
base64
|
||||
nkf
|
||||
rexml
|
||||
activesupport (7.1.3.2)
|
||||
base64
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
mutex_m
|
||||
tzinfo (~> 2.0)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
algoliasearch (1.27.5)
|
||||
httpclient (~> 2.8, >= 2.8.3)
|
||||
json (>= 1.5.1)
|
||||
anbt-sql-formatter (0.1.0)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.1001.0)
|
||||
aws-sdk-core (3.211.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.95.0)
|
||||
aws-sdk-core (~> 3, >= 3.210.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.169.0)
|
||||
aws-sdk-core (~> 3, >= 3.210.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.10.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.2.0)
|
||||
bigdecimal (3.1.6)
|
||||
claide (1.1.0)
|
||||
cocoapods (1.15.2)
|
||||
addressable (~> 2.8)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.15.2)
|
||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||
cocoapods-downloader (>= 2.1, < 3.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
cocoapods-search (>= 1.0.0, < 2.0)
|
||||
cocoapods-trunk (>= 1.6.0, < 2.0)
|
||||
cocoapods-try (>= 1.1.0, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
escape (~> 0.0.4)
|
||||
fourflusher (>= 2.3.0, < 3.0)
|
||||
gh_inspector (~> 1.0)
|
||||
molinillo (~> 0.8.0)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (>= 2.3.0, < 3.0)
|
||||
xcodeproj (>= 1.23.0, < 2.0)
|
||||
cocoapods-core (1.15.2)
|
||||
activesupport (>= 5.0, < 8)
|
||||
addressable (~> 2.8)
|
||||
algoliasearch (~> 1.0)
|
||||
concurrent-ruby (~> 1.1)
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
netrc (~> 0.11)
|
||||
public_suffix (~> 4.0)
|
||||
typhoeus (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.5)
|
||||
cocoapods-downloader (2.1)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.1)
|
||||
cocoapods-trunk (1.6.0)
|
||||
nap (>= 0.8, < 2.0)
|
||||
netrc (~> 0.11)
|
||||
cocoapods-try (1.2.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
concurrent-ruby (1.2.3)
|
||||
connection_pool (2.4.1)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.5)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.6.20240107)
|
||||
dotenv (2.8.1)
|
||||
drb (2.2.0)
|
||||
ruby2_keywords
|
||||
emoji_regex (3.2.3)
|
||||
escape (0.0.4)
|
||||
ethon (0.16.0)
|
||||
ffi (>= 1.15.0)
|
||||
excon (0.112.0)
|
||||
faraday (1.10.4)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.3.1)
|
||||
fastlane (2.225.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored (~> 1.2)
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
fastlane-sirp (>= 1.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-env (>= 1.6.0, < 2.0.0)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
http-cookie (~> 1.0.5)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (>= 0.1.1, < 1.0.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.5)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (~> 3)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
fastlane-sirp (1.0.0)
|
||||
sysrandom (~> 1.0)
|
||||
ffi (1.16.3)
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.54.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.3)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
google-apis-iamcredentials_v1 (0.17.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.13.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.31.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-cloud-core (1.7.1)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.4.0)
|
||||
google-cloud-storage (1.47.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.31.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.8.1)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.7)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.14.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.8.1)
|
||||
jwt (2.9.3)
|
||||
base64
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.22.2)
|
||||
molinillo (0.8.0)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.2.0)
|
||||
nanaimo (0.4.0)
|
||||
nap (1.1.0)
|
||||
naturally (2.2.1)
|
||||
netrc (0.11.0)
|
||||
nkf (0.2.0)
|
||||
optparse (0.5.0)
|
||||
os (1.1.4)
|
||||
plist (3.7.1)
|
||||
public_suffix (4.0.7)
|
||||
rake (13.2.1)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.3.9)
|
||||
rouge (2.0.7)
|
||||
ruby-macho (2.5.1)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.5)
|
||||
signet (0.19.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
sysrandom (1.0.5)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.2)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
typhoeus (1.4.1)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uber (0.1.0)
|
||||
unicode-display_width (2.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcode-install (2.8.1)
|
||||
claide (>= 0.9.1)
|
||||
fastlane (>= 2.1.0, < 3.0.0)
|
||||
xcodeproj (1.27.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.4.0)
|
||||
rexml (>= 3.3.6, < 4.0)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
anbt-sql-formatter
|
||||
cocoapods
|
||||
fastlane
|
||||
xcode-install
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.6
|
661
LICENSE
Normal file
661
LICENSE
Normal file
|
@ -0,0 +1,661 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
18
MAINTAINING.md
Normal file
18
MAINTAINING.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
Apart from the general `BUILDING.md` there are certain things that have
|
||||
to be done by Signal-iOS maintainers.
|
||||
|
||||
For transparency and bus factor, they are outlined here.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Keeping CocoaPods based dependencies is easy enough.
|
||||
|
||||
- To just update one dependency: `bundle exec pod update DependencyKit`
|
||||
- To update all dependencies to the latest according to the Podfile range: `bundle exec pod update`
|
||||
|
||||
Some Signal Pods have prebuilt artifacts that need a checksum to be downloaded as part of the build.
|
||||
These are provided by the CallingCore team.
|
||||
|
||||
## Translations
|
||||
|
||||
Read more about translations in [TRANSLATIONS.md](Signal/translations/TRANSLATIONS.md)
|
25
Makefile
Normal file
25
Makefile
Normal file
|
@ -0,0 +1,25 @@
|
|||
SCHEME=Signal
|
||||
PODS=Pods
|
||||
BACKUP_TESTS=SignalServiceKit/tests/MessageBackup/Signal-Message-Backup-Tests
|
||||
|
||||
.PHONY: dependencies
|
||||
dependencies: pod-setup backup-tests-setup fetch-ringrtc
|
||||
|
||||
.PHONY: pod-setup
|
||||
pod-setup:
|
||||
git -C ${PODS} clean -xfd
|
||||
git -C ${PODS} reset --hard
|
||||
./Scripts/setup_private_pods
|
||||
git submodule update --init --progress ${PODS}
|
||||
|
||||
.PHONY: backup-tests-setup
|
||||
backup-tests-setup:
|
||||
git submodule update --init --progress ${BACKUP_TESTS}
|
||||
|
||||
.PHONY: fetch-ringrtc
|
||||
fetch-ringrtc:
|
||||
$(CURDIR)/Pods/SignalRingRTC/bin/set-up-for-cocoapods
|
||||
|
||||
.PHONY: test
|
||||
test: dependencies
|
||||
bundle exec fastlane scan --scheme ${SCHEME}
|
383
Podfile
Normal file
383
Podfile
Normal file
|
@ -0,0 +1,383 @@
|
|||
platform :ios, '15.0'
|
||||
|
||||
use_frameworks!
|
||||
|
||||
###
|
||||
# OWS Pods
|
||||
###
|
||||
|
||||
source 'https://cdn.cocoapods.org/'
|
||||
|
||||
pod 'blurhash', podspec: './ThirdParty/blurhash.podspec'
|
||||
pod 'SwiftProtobuf', "1.28.2"
|
||||
|
||||
ENV['LIBSIGNAL_FFI_PREBUILD_CHECKSUM'] = '6df5cfbebe2d73fb9e457b67b313023beda0bb9d1fcd3382b6058e92958add29'
|
||||
pod 'LibSignalClient', git: 'https://github.com/signalapp/libsignal.git', tag: 'v0.65.0', testspecs: ["Tests"]
|
||||
# pod 'LibSignalClient', path: '../libsignal', testspecs: ["Tests"]
|
||||
|
||||
ENV['RINGRTC_PREBUILD_CHECKSUM'] = '82e27ec7c6af7b53a95b76ce79313b229a246200ccd11d2ad1d07708c9222d9f'
|
||||
pod 'SignalRingRTC', git: 'https://github.com/signalapp/ringrtc', tag: 'v2.49.2', inhibit_warnings: true
|
||||
# pod 'SignalRingRTC', path: '../ringrtc', testspecs: ["Tests"]
|
||||
|
||||
pod 'GRDB.swift/SQLCipher'
|
||||
# pod 'GRDB.swift/SQLCipher', path: '../GRDB.swift'
|
||||
|
||||
pod 'SQLCipher', git: 'https://github.com/signalapp/sqlcipher.git', tag: 'v4.6.1-f_barrierfsync'
|
||||
# pod 'SQLCipher', path: '../sqlcipher'
|
||||
|
||||
###
|
||||
# forked third party pods
|
||||
###
|
||||
|
||||
# Forked for performance optimizations that are not likely to be upstreamed as they are specific
|
||||
# to our limited use of Mantle
|
||||
pod 'Mantle', git: 'https://github.com/signalapp/Mantle', branch: 'signal-master'
|
||||
# pod 'Mantle', path: '../Mantle'
|
||||
|
||||
pod 'libPhoneNumber-iOS', git: 'https://github.com/signalapp/libPhoneNumber-iOS', branch: 'signal-master'
|
||||
# pod 'libPhoneNumber-iOS', path: '../libPhoneNumber-iOS'
|
||||
|
||||
pod 'YYImage', git: 'https://github.com/signalapp/YYImage', :inhibit_warnings => true
|
||||
pod 'YYImage/libwebp', git: 'https://github.com/signalapp/YYImage', :inhibit_warnings => true
|
||||
pod 'libwebp', podspec: './ThirdParty/libwebp.podspec.json'
|
||||
# pod 'YYImage', path: '../YYImage'
|
||||
# pod 'YYImage/libwebp', path:'../YYImage'
|
||||
|
||||
###
|
||||
# third party pods
|
||||
####
|
||||
|
||||
pod 'Reachability', :inhibit_warnings => true
|
||||
|
||||
def ui_pods
|
||||
pod 'BonMot', inhibit_warnings: true
|
||||
pod 'PureLayout', :inhibit_warnings => true
|
||||
pod 'lottie-ios', :inhibit_warnings => true
|
||||
|
||||
pod 'LibMobileCoin/CoreHTTP', git: 'https://github.com/signalapp/libmobilecoin-ios-artifacts', tag: 'signal/6.0.2', submodules: true
|
||||
pod 'MobileCoin/CoreHTTP', git: 'https://github.com/mobilecoinofficial/MobileCoin-Swift', tag: 'v6.0.3'
|
||||
end
|
||||
|
||||
target 'Signal' do
|
||||
project 'Signal.xcodeproj', 'Debug' => :debug, 'Release' => :release
|
||||
|
||||
# Pods only available inside the main Signal app
|
||||
ui_pods
|
||||
|
||||
target 'SignalTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
# These extensions inherit all of the common pods
|
||||
|
||||
target 'SignalShareExtension' do
|
||||
ui_pods
|
||||
end
|
||||
|
||||
target 'SignalUI' do
|
||||
ui_pods
|
||||
|
||||
target 'SignalUITests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
target 'SignalServiceKit' do
|
||||
pod 'CocoaLumberjack'
|
||||
|
||||
target 'SignalServiceKitTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
target 'SignalNSE' do
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
enable_strip(installer)
|
||||
enable_extension_support_for_purelayout(installer)
|
||||
configure_warning_flags(installer)
|
||||
configure_testable_build(installer)
|
||||
promote_minimum_supported_version(installer)
|
||||
disable_bitcode(installer)
|
||||
disable_armv7(installer)
|
||||
strip_valid_archs(installer)
|
||||
update_frameworks_script(installer)
|
||||
disable_non_development_pod_warnings(installer)
|
||||
fix_ringrtc_project_symlink(installer)
|
||||
fetch_ringrtc
|
||||
copy_acknowledgements
|
||||
end
|
||||
|
||||
# Works around CocoaPods behavior designed for static libraries.
|
||||
# See https://github.com/CocoaPods/CocoaPods/issues/10277
|
||||
def enable_strip(installer)
|
||||
installer.pods_project.build_configurations.each do |build_configuration|
|
||||
build_configuration.build_settings['STRIP_INSTALLED_PRODUCT'] = 'YES'
|
||||
end
|
||||
end
|
||||
|
||||
# PureLayout by default makes use of UIApplication, and must be configured to be built for an extension.
|
||||
def enable_extension_support_for_purelayout(installer)
|
||||
installer.pods_project.targets.each do |target|
|
||||
if target.name.end_with? "PureLayout"
|
||||
target.build_configurations.each do |build_configuration|
|
||||
build_configuration.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= '$(inherited)'
|
||||
build_configuration.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << ' PURELAYOUT_APP_EXTENSIONS=1'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# We want some warning to be treated as errors.
|
||||
#
|
||||
# NOTE: We have to manually keep this list in sync with what's in our
|
||||
# Signal.xcodeproj config in Xcode go to:
|
||||
# Signal Project > Build Settings > Other Warning Flags
|
||||
def configure_warning_flags(installer)
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |build_configuration|
|
||||
build_configuration.build_settings['WARNING_CFLAGS'] = ['$(inherited)',
|
||||
'-Werror=incompatible-pointer-types',
|
||||
'-Werror=protocol',
|
||||
'-Werror=incomplete-implementation',
|
||||
'-Werror=objc-literal-conversion',
|
||||
'-Werror=objc-property-synthesis',
|
||||
'-Werror=objc-protocol-property-synthesis']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def configure_testable_build(installer)
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |build_configuration|
|
||||
next unless ["Testable Release", "Debug", "Profiling"].include?(build_configuration.name)
|
||||
build_configuration.build_settings['ONLY_ACTIVE_ARCH'] = 'YES'
|
||||
build_configuration.build_settings['ENABLE_TESTABILITY'] = 'YES'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Xcode 13 dropped support for some older iOS versions. We only need them
|
||||
# to support our project's minimum version, so let's bump each Pod's min
|
||||
# version to our min to suppress these warnings.
|
||||
def promote_minimum_supported_version(installer)
|
||||
project_min_version = current_target_definition.platform.deployment_target
|
||||
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |build_configuration|
|
||||
target_version_string = build_configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET']
|
||||
target_version = Version.create(target_version_string)
|
||||
|
||||
if target_version < project_min_version
|
||||
build_configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = project_min_version.version
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def disable_bitcode(installer)
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['ENABLE_BITCODE'] = 'NO'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def disable_armv7(installer)
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['EXCLUDED_ARCHS'] = 'armv7'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def strip_valid_archs(installer)
|
||||
Dir.glob('Pods/Target Support Files/**/*.xcconfig') do |xcconfig_path|
|
||||
xcconfig = File.read(xcconfig_path)
|
||||
xcconfig_mod = xcconfig.gsub('VALID_ARCHS[sdk=iphoneos*] = arm64', '')
|
||||
xcconfig_mod = xcconfig_mod.gsub('VALID_ARCHS[sdk=iphonesimulator*] = x86_64 arm64', '')
|
||||
xcconfig_mod = xcconfig_mod.gsub('VALID_ARCHS[sdk=iphonesimulator*] = x86_64', '')
|
||||
File.open(xcconfig_path, "w") { |file| file << xcconfig_mod }
|
||||
end
|
||||
end
|
||||
|
||||
#update_framework_scripts updates Pod-Signal-frameworks.sh to fix a bug in the .XCFramework->.framework
|
||||
#conversation process, by ensuring symlinks are properly respected in the XCFramework.
|
||||
#See https://github.com/CocoaPods/CocoaPods/issues/7587
|
||||
def update_frameworks_script(installer)
|
||||
fw_script = File.read('Pods/Target Support Files/Pods-Signal/Pods-Signal-frameworks.sh')
|
||||
fw_script_mod = fw_script.gsub(' lipo -remove "$arch" -output "$binary" "$binary"
|
||||
', ' realBinary="${binary}"
|
||||
if [ -L "${realBinary}" ]; then
|
||||
echo "Symlinked..."
|
||||
dirname="$(dirname "${realBinary}")"
|
||||
realBinary="${dirname}/$(readlink "${realBinary}")"
|
||||
fi
|
||||
lipo -remove "${arch}" -output "${realBinary}" "${realBinary}" || exit 1')
|
||||
File.open('Pods/Target Support Files/Pods-Signal/Pods-Signal-frameworks.sh', "w") { |file| file << fw_script_mod }
|
||||
end
|
||||
|
||||
# Disable warnings on any Pod not currently being modified
|
||||
def disable_non_development_pod_warnings(installer)
|
||||
non_development_targets = installer.pod_targets.select do |target|
|
||||
!installer.development_pod_targets.include?(target)
|
||||
end
|
||||
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |build_configuration|
|
||||
# Only suppress warnings for the debug configuration
|
||||
# If we're building for release, continue to display warnings for all projects
|
||||
next if build_configuration.name != "Debug"
|
||||
|
||||
next unless non_development_targets.any? do |non_dev_target|
|
||||
target.name.include?(non_dev_target.name)
|
||||
end
|
||||
|
||||
build_configuration.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'] = 'YES'
|
||||
build_configuration.build_settings['OTHER_SWIFT_FLAGS'] ||= '$(inherited)'
|
||||
build_configuration.build_settings['OTHER_SWIFT_FLAGS'] << ' -suppress-warnings'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Workaround for RingRTC's weird cached artifacts, hopefully temporary
|
||||
def fix_ringrtc_project_symlink(installer)
|
||||
ringrtc_header_ref = installer.pods_project.reference_for_path(installer.sandbox.pod_dir('SignalRingRTC') + 'out/release/libringrtc/ringrtc.h')
|
||||
if ringrtc_header_ref.path.start_with?('../') || ringrtc_header_ref.path.start_with?('/') then
|
||||
ringrtc_header_ref.path = 'out/release/libringrtc/ringrtc.h'
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_ringrtc
|
||||
`make fetch-ringrtc`
|
||||
end
|
||||
|
||||
def copy_acknowledgements
|
||||
targets = [
|
||||
'Signal',
|
||||
'SignalNSE',
|
||||
'SignalServiceKit',
|
||||
'SignalServiceKitTests',
|
||||
'SignalShareExtension',
|
||||
'SignalTests',
|
||||
'SignalUI',
|
||||
'SignalUITests'
|
||||
]
|
||||
acknowledgements_files = targets.map do |target|
|
||||
"Pods/Target Support Files/Pods-#{target}/Pods-#{target}-acknowledgements.plist"
|
||||
end
|
||||
acknowledgements_files << "Pods/LibSignalClient/acknowledgments/acknowledgments.plist"
|
||||
acknowledgements_files << "Pods/SignalRingRTC/acknowledgments/acknowledgments.plist"
|
||||
acknowledgements_files << "Pods/SignalRingRTC/out/release/acknowledgments-webrtc-ios.plist"
|
||||
|
||||
def get_specifier_groups(acknowledgements_files)
|
||||
acknowledgements_files.map do |file|
|
||||
extract_cmd = ['plutil', '-extract', 'PreferenceSpecifiers', 'json', '-o', '-', file]
|
||||
|
||||
io = IO.popen(extract_cmd, unsetenv_others: true, exception: true)
|
||||
result = JSON.parse(io.read)
|
||||
io.close
|
||||
status = $?
|
||||
raise status unless status.exitstatus == 0
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def get_acknowledgements_specifiers(group)
|
||||
group[1...-1]
|
||||
end
|
||||
|
||||
def write_output_file(specifiers)
|
||||
output_file = 'Signal/Settings.bundle/Acknowledgements.plist'
|
||||
output_json = JSON.dump(specifiers)
|
||||
system('plutil', '-create', 'xml1', output_file, exception: true)
|
||||
system('plutil', '-insert', 'PreferenceSpecifiers', '-json', output_json, '-append', output_file, exception: true)
|
||||
end
|
||||
|
||||
def add_in_repo_third_party_code_licenses(specifiers)
|
||||
# specifiers << {
|
||||
# "Type" => "PSGroupSpecifier",
|
||||
# "Title" => "",
|
||||
# "FooterText" => "",
|
||||
# "License" => "",
|
||||
# }
|
||||
specifiers << {
|
||||
"Type" => "PSGroupSpecifier",
|
||||
"Title" => "UIImage-Resize",
|
||||
"FooterText" => <<~'LICENSE',
|
||||
Without any further information, all the sources provided here are under the MIT License
|
||||
quoted below.
|
||||
|
||||
Anyway, please contact me by email (olivier.halligon+ae@gmail.com) if you plan to use my work and the provided classes
|
||||
in your own software. Thanks.
|
||||
|
||||
|
||||
/***********************************************************************************
|
||||
*
|
||||
* Copyright (c) 2010 Olivier Halligon
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
***********************************************************************************
|
||||
*
|
||||
* Any comment or suggestion welcome. Referencing this project in your AboutBox is appreciated.
|
||||
* Please tell me if you use this class so we can cross-reference our projects.
|
||||
*
|
||||
***********************************************************************************/
|
||||
LICENSE
|
||||
"License" => "MIT",
|
||||
}
|
||||
end
|
||||
|
||||
specifier_groups = get_specifier_groups(acknowledgements_files)
|
||||
|
||||
header_specifier = specifier_groups.first.first
|
||||
footer_specifier = specifier_groups.first.last
|
||||
all_acknowledgements_specifiers = specifier_groups.flat_map {|g| get_acknowledgements_specifiers(g)}
|
||||
|
||||
add_in_repo_third_party_code_licenses(all_acknowledgements_specifiers)
|
||||
|
||||
libraries_by_license = Hash.new { |h, k| h[k] = [] }
|
||||
all_acknowledgements_specifiers.each do |v|
|
||||
v["Title"].split(", ").each do |title|
|
||||
libraries_by_license[[v["FooterText"], v["License"]]] << title
|
||||
end
|
||||
end
|
||||
|
||||
grouped_acknowledgements_specifiers = []
|
||||
libraries_by_license.each do |key, value|
|
||||
titles = value.uniq.sort_by { |s| s.downcase }.join(", ")
|
||||
acknowledgement = {
|
||||
"Type" => "PSGroupSpecifier",
|
||||
"Title" => titles,
|
||||
"FooterText" => key[0],
|
||||
}
|
||||
acknowledgement["License"] = key[1] if key[1]
|
||||
grouped_acknowledgements_specifiers << acknowledgement
|
||||
end
|
||||
|
||||
cleaned_acknowledgements_specifiers = grouped_acknowledgements_specifiers.sort_by {|s| s["Title"].downcase}
|
||||
final_specifiers = [header_specifier] + cleaned_acknowledgements_specifiers + [footer_specifier]
|
||||
|
||||
write_output_file(final_specifiers)
|
||||
end
|
163
Podfile.lock
Normal file
163
Podfile.lock
Normal file
|
@ -0,0 +1,163 @@
|
|||
PODS:
|
||||
- blurhash (0.0.1)
|
||||
- BonMot (6.0.0)
|
||||
- CocoaLumberjack (3.7.4):
|
||||
- CocoaLumberjack/Core (= 3.7.4)
|
||||
- CocoaLumberjack/Core (3.7.4)
|
||||
- GRDB.swift/SQLCipher (5.26.0):
|
||||
- SQLCipher (>= 3.4.0)
|
||||
- LibMobileCoin/CoreHTTP (6.0.2):
|
||||
- SwiftProtobuf (~> 1.5)
|
||||
- libPhoneNumber-iOS (1.2.0)
|
||||
- LibSignalClient (0.65.0)
|
||||
- LibSignalClient/Tests (0.65.0)
|
||||
- libwebp (1.3.2):
|
||||
- libwebp/demux (= 1.3.2)
|
||||
- libwebp/mux (= 1.3.2)
|
||||
- libwebp/webp (= 1.3.2)
|
||||
- libwebp/demux (1.3.2):
|
||||
- libwebp/webp
|
||||
- libwebp/mux (1.3.2):
|
||||
- libwebp/demux
|
||||
- libwebp/webp (1.3.2)
|
||||
- Logging (1.4.0)
|
||||
- lottie-ios (4.4.3)
|
||||
- Mantle (2.1.0):
|
||||
- Mantle/extobjc (= 2.1.0)
|
||||
- Mantle/extobjc (2.1.0)
|
||||
- MobileCoin/CoreHTTP (6.0.3):
|
||||
- LibMobileCoin/CoreHTTP (~> 6.0.0-pre1)
|
||||
- Logging (~> 1.4)
|
||||
- PureLayout (3.1.4)
|
||||
- Reachability (3.7.6)
|
||||
- SignalRingRTC (2.49.2):
|
||||
- SignalRingRTC/WebRTC (= 2.49.2)
|
||||
- SignalRingRTC/WebRTC (2.49.2)
|
||||
- SQLCipher (4.6.1):
|
||||
- SQLCipher/standard (= 4.6.1)
|
||||
- SQLCipher/common (4.6.1)
|
||||
- SQLCipher/standard (4.6.1):
|
||||
- SQLCipher/common
|
||||
- SwiftProtobuf (1.28.2)
|
||||
- YYImage (1.0.4):
|
||||
- YYImage/Core (= 1.0.4)
|
||||
- YYImage/Core (1.0.4)
|
||||
- YYImage/libwebp (1.0.4):
|
||||
- libwebp
|
||||
- YYImage/Core
|
||||
|
||||
DEPENDENCIES:
|
||||
- blurhash (from `./ThirdParty/blurhash.podspec`)
|
||||
- BonMot
|
||||
- CocoaLumberjack
|
||||
- GRDB.swift/SQLCipher
|
||||
- LibMobileCoin/CoreHTTP (from `https://github.com/signalapp/libmobilecoin-ios-artifacts`, tag `signal/6.0.2`)
|
||||
- libPhoneNumber-iOS (from `https://github.com/signalapp/libPhoneNumber-iOS`, branch `signal-master`)
|
||||
- LibSignalClient (from `https://github.com/signalapp/libsignal.git`, tag `v0.65.0`)
|
||||
- LibSignalClient/Tests (from `https://github.com/signalapp/libsignal.git`, tag `v0.65.0`)
|
||||
- libwebp (from `./ThirdParty/libwebp.podspec.json`)
|
||||
- lottie-ios
|
||||
- Mantle (from `https://github.com/signalapp/Mantle`, branch `signal-master`)
|
||||
- MobileCoin/CoreHTTP (from `https://github.com/mobilecoinofficial/MobileCoin-Swift`, tag `v6.0.3`)
|
||||
- PureLayout
|
||||
- Reachability
|
||||
- SignalRingRTC (from `https://github.com/signalapp/ringrtc`, tag `v2.49.2`)
|
||||
- SQLCipher (from `https://github.com/signalapp/sqlcipher.git`, tag `v4.6.1-f_barrierfsync`)
|
||||
- SwiftProtobuf (= 1.28.2)
|
||||
- YYImage (from `https://github.com/signalapp/YYImage`)
|
||||
- YYImage/libwebp (from `https://github.com/signalapp/YYImage`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- BonMot
|
||||
- CocoaLumberjack
|
||||
- GRDB.swift
|
||||
- Logging
|
||||
- lottie-ios
|
||||
- PureLayout
|
||||
- Reachability
|
||||
- SwiftProtobuf
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
blurhash:
|
||||
:podspec: "./ThirdParty/blurhash.podspec"
|
||||
LibMobileCoin:
|
||||
:git: https://github.com/signalapp/libmobilecoin-ios-artifacts
|
||||
:submodules: true
|
||||
:tag: signal/6.0.2
|
||||
libPhoneNumber-iOS:
|
||||
:branch: signal-master
|
||||
:git: https://github.com/signalapp/libPhoneNumber-iOS
|
||||
LibSignalClient:
|
||||
:git: https://github.com/signalapp/libsignal.git
|
||||
:tag: v0.65.0
|
||||
libwebp:
|
||||
:podspec: "./ThirdParty/libwebp.podspec.json"
|
||||
Mantle:
|
||||
:branch: signal-master
|
||||
:git: https://github.com/signalapp/Mantle
|
||||
MobileCoin:
|
||||
:git: https://github.com/mobilecoinofficial/MobileCoin-Swift
|
||||
:tag: v6.0.3
|
||||
SignalRingRTC:
|
||||
:git: https://github.com/signalapp/ringrtc
|
||||
:tag: v2.49.2
|
||||
SQLCipher:
|
||||
:git: https://github.com/signalapp/sqlcipher.git
|
||||
:tag: v4.6.1-f_barrierfsync
|
||||
YYImage:
|
||||
:git: https://github.com/signalapp/YYImage
|
||||
|
||||
CHECKOUT OPTIONS:
|
||||
blurhash:
|
||||
:commit: 890ffdab14207154819415da7e6c969e9dfff0e9
|
||||
:git: https://github.com/signalapp/blurhash
|
||||
LibMobileCoin:
|
||||
:git: https://github.com/signalapp/libmobilecoin-ios-artifacts
|
||||
:submodules: true
|
||||
:tag: signal/6.0.2
|
||||
libPhoneNumber-iOS:
|
||||
:commit: 830f8ce343efb0f06a44e4eb44eb3bcac24c16e8
|
||||
:git: https://github.com/signalapp/libPhoneNumber-iOS
|
||||
LibSignalClient:
|
||||
:git: https://github.com/signalapp/libsignal.git
|
||||
:tag: v0.65.0
|
||||
Mantle:
|
||||
:commit: e7e46253bb01ce39525d90aa69ed9e85e758bfc4
|
||||
:git: https://github.com/signalapp/Mantle
|
||||
MobileCoin:
|
||||
:git: https://github.com/mobilecoinofficial/MobileCoin-Swift
|
||||
:tag: v6.0.3
|
||||
SignalRingRTC:
|
||||
:git: https://github.com/signalapp/ringrtc
|
||||
:tag: v2.49.2
|
||||
SQLCipher:
|
||||
:git: https://github.com/signalapp/sqlcipher.git
|
||||
:tag: v4.6.1-f_barrierfsync
|
||||
YYImage:
|
||||
:commit: 62a4cede20bcf31da73d18163408e46a92f171c6
|
||||
:git: https://github.com/signalapp/YYImage
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
blurhash: f588a9d659f4c742a325bbbbd438d1ef3c34af19
|
||||
BonMot: fb2b6a2209cb3149aca37b7131d49c051c04ae86
|
||||
CocoaLumberjack: 543c79c114dadc3b1aba95641d8738b06b05b646
|
||||
GRDB.swift: 1395cb3556df6b16ed69dfc74c3886abc75d2825
|
||||
LibMobileCoin: 8503f567fa32184a5be7bc038fbd727747dd9991
|
||||
libPhoneNumber-iOS: 1a34106b49dc6e12a7f37eb9aee7c64011509547
|
||||
LibSignalClient: d419aefee543e07952cfa4c392cc4975abfa3c33
|
||||
libwebp: 1b5562124e3ca3336fb7506e76501bed7758963a
|
||||
Logging: beeb016c9c80cf77042d62e83495816847ef108b
|
||||
lottie-ios: fcb5e73e17ba4c983140b7d21095c834b3087418
|
||||
Mantle: 2fa750afa478cd625a94230fbf1c13462f29395b
|
||||
MobileCoin: cd7f49d6eaa2d362e4c9f8324cae0cc3bc3c53b5
|
||||
PureLayout: f08c01b8dec00bb14a1fefa3de4c7d9c265df85e
|
||||
Reachability: fd0ecd23705e2599e4cceeb943222ae02296cbc6
|
||||
SignalRingRTC: 9de2cb33c5bc83aae825e576dc21696e8361fde8
|
||||
SQLCipher: ff2f045b20d675a73a70f7329395ddd4a2580063
|
||||
SwiftProtobuf: 4dbaffec76a39a8dc5da23b40af1a5dc01a4c02d
|
||||
YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331
|
||||
|
||||
PODFILE CHECKSUM: 5b9b6c357aaf3a20e6ae7e3b586eee16781c6efc
|
||||
|
||||
COCOAPODS: 1.15.2
|
40
README.md
Normal file
40
README.md
Normal file
|
@ -0,0 +1,40 @@
|
|||
# Signal iOS
|
||||
|
||||
Signal is a free, open source, messaging app for simple private communication with friends.
|
||||
|
||||
[](https://apps.apple.com/app/id874139669)
|
||||
|
||||
Also available on [Android](https://github.com/signalapp/signal-android) and [Desktop](https://github.com/signalapp/signal-desktop).
|
||||
|
||||
## Questions?
|
||||
|
||||
For troubleshooting and questions, please visit our [support center](https://support.signal.org/) or [unofficial community forum](https://community.signalusers.org/).
|
||||
|
||||
## Contributing Bug Reports
|
||||
|
||||
We use GitHub for bug tracking. Please search [existing issues](https://github.com/signalapp/signal-ios/issues) and create a new one if the issue is not yet tracked. For Android users, please use the [Signal for Android issue tracker](https://github.com/signalapp/signal-android/issues).
|
||||
|
||||
## Contributing Code
|
||||
|
||||
Instructions on how to setup your development environment and build Signal-iOS can be found in [BUILDING.md](https://github.com/signalapp/Signal-iOS/blob/main/BUILDING.md). We also recommend reading the [contribution guidelines](https://github.com/signalapp/Signal-iOS/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## Contributing Ideas
|
||||
|
||||
Have something you want to say about Signal Foundation projects or want to be part of the conversation? Get involved in the [community forum](https://community.signalusers.org).
|
||||
|
||||
## Cryptography Notice
|
||||
|
||||
This distribution includes cryptographic software. The country in which you currently reside may have restrictions on the import, possession, use, and/or re-export to another country, of encryption software.
|
||||
BEFORE using any encryption software, please check your country's laws, regulations and policies concerning the import, possession, or use, and re-export of encryption software, to see if this is permitted.
|
||||
See <http://www.wassenaar.org/> for more information.
|
||||
|
||||
The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this software as Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software using or performing cryptographic functions with asymmetric algorithms.
|
||||
The form and manner of this distribution makes it eligible for export under the License Exception ENC Technology Software Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Section 740.13) for both object code and source code.
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2013-2024 Signal Messenger, LLC
|
||||
|
||||
Licensed under the GNU AGPLv3: https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
_Apple and the Apple logo are trademarks of Apple Inc., registered in the U.S. and other countries. App Store is a service mark of Apple Inc., registered in the U.S. and other countries._
|
3
Scripts/ClearDerivedData.sh
Executable file
3
Scripts/ClearDerivedData.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
rm -rf ~/Library/Developer/Xcode/DerivedData
|
635
Scripts/EmojiGenerator.swift
Executable file
635
Scripts/EmojiGenerator.swift
Executable file
|
@ -0,0 +1,635 @@
|
|||
#!/usr/bin/env xcrun --sdk macosx swift
|
||||
//
|
||||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// OWSAssertionError but for this script
|
||||
|
||||
enum EmojiError: Error {
|
||||
case assertion(String)
|
||||
init(_ string: String) {
|
||||
self = .assertion(string)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Remote Model
|
||||
// These definitions are kept fairly lightweight since we don't control their format
|
||||
// All processing of remote data is done by converting RemoteModel items to EmojiModel items
|
||||
|
||||
enum RemoteModel {
|
||||
struct EmojiItem: Codable {
|
||||
let name: String
|
||||
let shortName: String
|
||||
let unified: String
|
||||
let sortOrder: UInt
|
||||
let category: EmojiCategory
|
||||
let skinVariations: [String: SkinVariation]?
|
||||
}
|
||||
|
||||
struct SkinVariation: Codable {
|
||||
let unified: String
|
||||
}
|
||||
|
||||
enum EmojiCategory: String, Codable, Equatable {
|
||||
case smileys = "Smileys & Emotion"
|
||||
case people = "People & Body"
|
||||
|
||||
// This category is not provided in the data set, but is actually
|
||||
// a merger of the categories of `smileys` and `people`
|
||||
case smileysAndPeople = "Smileys & People"
|
||||
|
||||
case animals = "Animals & Nature"
|
||||
case food = "Food & Drink"
|
||||
case activities = "Activities"
|
||||
case travel = "Travel & Places"
|
||||
case objects = "Objects"
|
||||
case symbols = "Symbols"
|
||||
case flags = "Flags"
|
||||
case components = "Component"
|
||||
}
|
||||
|
||||
static func fetchEmojiData() throws -> Data {
|
||||
// let remoteSourceUrl = URL(string: "https://unicodey.com/emoji-data/emoji.json")!
|
||||
// This URL has been unavailable the past couple of weeks. If you're seeing failures here, try this other one:
|
||||
let remoteSourceUrl = URL(string: "https://raw.githubusercontent.com/iamcal/emoji-data/master/emoji.json")!
|
||||
return try Data(contentsOf: remoteSourceUrl)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Local Model
|
||||
|
||||
struct EmojiModel {
|
||||
let definitions: [EmojiDefinition]
|
||||
|
||||
struct EmojiDefinition {
|
||||
let category: RemoteModel.EmojiCategory
|
||||
let rawName: String
|
||||
let enumName: String
|
||||
let variants: [Emoji]
|
||||
var baseEmoji: Character { variants[0].base }
|
||||
|
||||
struct Emoji: Comparable {
|
||||
let emojiChar: Character
|
||||
|
||||
let base: Character
|
||||
let skintoneSequence: SkinToneSequence
|
||||
|
||||
static func < (lhs: Self, rhs: Self) -> Bool {
|
||||
for (leftElement, rightElement) in zip(lhs.skintoneSequence, rhs.skintoneSequence) {
|
||||
if leftElement.sortId != rightElement.sortId {
|
||||
return leftElement.sortId < rightElement.sortId
|
||||
}
|
||||
}
|
||||
if lhs.skintoneSequence.count != rhs.skintoneSequence.count {
|
||||
return lhs.skintoneSequence.count < rhs.skintoneSequence.count
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(parsingRemoteItem remoteItem: RemoteModel.EmojiItem) throws {
|
||||
category = remoteItem.category
|
||||
rawName = remoteItem.name
|
||||
enumName = Self.parseEnumNameFromRemoteItem(remoteItem)
|
||||
|
||||
let baseEmojiChar = try Self.codePointsToCharacter(Self.parseCodePointString(remoteItem.unified))
|
||||
let baseEmoji = Emoji(emojiChar: baseEmojiChar, base: baseEmojiChar, skintoneSequence: .none)
|
||||
|
||||
let toneVariants: [Emoji]
|
||||
if let skinVariations = remoteItem.skinVariations {
|
||||
toneVariants = try skinVariations.map { key, value in
|
||||
let modifier = SkinTone.sequence(from: Self.parseCodePointString(key))
|
||||
let parsedEmoji = try Self.codePointsToCharacter(Self.parseCodePointString(value.unified))
|
||||
return Emoji(emojiChar: parsedEmoji, base: baseEmojiChar, skintoneSequence: modifier)
|
||||
}.sorted()
|
||||
} else {
|
||||
toneVariants = []
|
||||
}
|
||||
|
||||
variants = [baseEmoji] + toneVariants
|
||||
try postInitValidation()
|
||||
}
|
||||
|
||||
func postInitValidation() throws {
|
||||
guard variants.count > 0 else {
|
||||
throw EmojiError("Expecting at least one variant")
|
||||
}
|
||||
|
||||
guard variants.allSatisfy({ $0.base == baseEmoji }) else {
|
||||
// All emoji variants must have a common base emoji
|
||||
throw EmojiError("Inconsistent base emoji: \(baseEmoji)")
|
||||
}
|
||||
|
||||
let hasMultipleComponents = variants.first(where: { $0.skintoneSequence.count > 1 }) != nil
|
||||
if hasMultipleComponents, skinToneComponents == nil {
|
||||
// If you hit this, this means a new emoji was added where a skintone modifier sequence specifies multiple
|
||||
// skin tones for multiple emoji components: e.g. 👫 -> 🧍♀️+🧍♂️
|
||||
// These are defined in `skinToneComponents`. You'll need to add a new case.
|
||||
throw EmojiError("\(baseEmoji):\(enumName) definition has variants with multiple skintone modifiers but no component emojis defined")
|
||||
}
|
||||
}
|
||||
|
||||
static func parseEnumNameFromRemoteItem(_ item: RemoteModel.EmojiItem) -> String {
|
||||
// some names don't play nice with swift, so we special case them
|
||||
switch item.shortName {
|
||||
case "+1": return "plusOne"
|
||||
case "-1": return "negativeOne"
|
||||
case "8ball": return "eightBall"
|
||||
case "repeat": return "`repeat`"
|
||||
case "100": return "oneHundred"
|
||||
case "1234": return "oneTwoThreeFour"
|
||||
case "couplekiss": return "personKissPerson"
|
||||
case "couple_with_heart": return "personHeartPerson"
|
||||
default:
|
||||
let uppperCamelCase = item.shortName
|
||||
.replacingOccurrences(of: "-", with: " ")
|
||||
.replacingOccurrences(of: "_", with: " ")
|
||||
.titlecase
|
||||
.replacingOccurrences(of: " ", with: "")
|
||||
|
||||
return uppperCamelCase.first!.lowercased() + uppperCamelCase.dropFirst()
|
||||
}
|
||||
}
|
||||
|
||||
var skinToneComponents: String? {
|
||||
// There's no great way to do this except manually. Some emoji have multiple skin tones.
|
||||
// In the picker, we need to use one emoji to represent each person. For now, we manually
|
||||
// specify this. Hopefully, in the future, the data set will contain this information.
|
||||
switch enumName {
|
||||
case "peopleHoldingHands": return "[.standingPerson, .standingPerson]"
|
||||
case "twoWomenHoldingHands": return "[.womanStanding, .womanStanding]"
|
||||
case "manAndWomanHoldingHands": return "[.womanStanding, .manStanding]"
|
||||
case "twoMenHoldingHands": return "[.manStanding, .manStanding]"
|
||||
case "personKissPerson": return "[.adult, .adult]"
|
||||
case "womanKissMan": return "[.woman, .man]"
|
||||
case "manKissMan": return "[.man, .man]"
|
||||
case "womanKissWoman": return "[.woman, .woman]"
|
||||
case "personHeartPerson": return "[.adult, .adult]"
|
||||
case "womanHeartMan": return "[.woman, .man]"
|
||||
case "manHeartMan": return "[.man, .man]"
|
||||
case "womanHeartWoman": return "[.woman, .woman]"
|
||||
case "handshake": return "[.rightwardsHand, .leftwardsHand]"
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var isNormalized: Bool { enumName == normalizedEnumName }
|
||||
var normalizedEnumName: String {
|
||||
switch enumName {
|
||||
// flagUm (US Minor Outlying Islands) looks identical to the
|
||||
// US flag. We don't present it as a sendable reaction option
|
||||
// This matches the iOS keyboard behavior.
|
||||
case "flagUm": return "us"
|
||||
default: return enumName
|
||||
}
|
||||
}
|
||||
|
||||
static func parseCodePointString(_ pointString: String) -> [UnicodeScalar] {
|
||||
return pointString
|
||||
.components(separatedBy: "-")
|
||||
.map { Int($0, radix: 16)! }
|
||||
.map { UnicodeScalar($0)! }
|
||||
}
|
||||
|
||||
static func codePointsToCharacter(_ codepoints: [UnicodeScalar]) throws -> Character {
|
||||
let result = codepoints.map { String($0) }.joined()
|
||||
if result.count != 1 {
|
||||
throw EmojiError("Invalid number of chars for codepoint sequence: \(codepoints)")
|
||||
}
|
||||
return result.first!
|
||||
}
|
||||
}
|
||||
|
||||
init(rawJSONData jsonData: Data) throws {
|
||||
let jsonDecoder = JSONDecoder()
|
||||
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
|
||||
definitions = try jsonDecoder
|
||||
.decode([RemoteModel.EmojiItem].self, from: jsonData)
|
||||
.sorted { $0.sortOrder < $1.sortOrder }
|
||||
.map { try EmojiDefinition(parsingRemoteItem: $0) }
|
||||
|
||||
}
|
||||
|
||||
typealias SkinToneSequence = [EmojiModel.SkinTone]
|
||||
enum SkinTone: UnicodeScalar, CaseIterable, Equatable {
|
||||
case light = "🏻"
|
||||
case mediumLight = "🏼"
|
||||
case medium = "🏽"
|
||||
case mediumDark = "🏾"
|
||||
case dark = "🏿"
|
||||
|
||||
var sortId: Int { return SkinTone.allCases.firstIndex(of: self)! }
|
||||
|
||||
static func sequence(from codepoints: [UnicodeScalar]) -> SkinToneSequence {
|
||||
codepoints
|
||||
.map { SkinTone(rawValue: $0)! }
|
||||
.reduce(into: [SkinTone]()) { result, skinTone in
|
||||
guard !result.contains(skinTone) else { return }
|
||||
result.append(skinTone)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension EmojiModel.SkinToneSequence {
|
||||
static var none: EmojiModel.SkinToneSequence = []
|
||||
}
|
||||
|
||||
// MARK: - File Writers
|
||||
|
||||
extension EmojiGenerator {
|
||||
static func writePrimaryFile(from emojiModel: EmojiModel) {
|
||||
// Main enum: Create a string enum defining our enumNames equal to the baseEmoji string
|
||||
// e.g. case grinning = "😀"
|
||||
writeBlock(fileName: "Emoji.swift") { fileHandle in
|
||||
fileHandle.writeLine("// swiftlint:disable all")
|
||||
fileHandle.writeLine("")
|
||||
fileHandle.writeLine("/// A sorted representation of all available emoji")
|
||||
fileHandle.writeLine("enum Emoji: String, CaseIterable, Equatable {")
|
||||
fileHandle.indent {
|
||||
emojiModel.definitions.forEach {
|
||||
fileHandle.writeLine("case \($0.enumName) = \"\($0.baseEmoji)\"")
|
||||
}
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
fileHandle.writeLine("// swiftlint:disable all")
|
||||
}
|
||||
}
|
||||
|
||||
static func writeStringConversionsFile(from emojiModel: EmojiModel) {
|
||||
// Conversion from String: Creates an mapping from a single character emoji string to the
|
||||
// Emoji + SkinTone components. These components can then be uses to instantiate an
|
||||
// EmojiWithSkinTones
|
||||
// e.g.
|
||||
// emoji = "🦻🏻" => (.earWithHearingAid, skinTones: [.light])
|
||||
writeBlock(fileName: "EmojiWithSkinTones+String.swift") { fileHandle in
|
||||
fileHandle.writeLine("extension EmojiWithSkinTones {")
|
||||
fileHandle.indent {
|
||||
// Start skinToneToEmoji
|
||||
fileHandle.writeLine("static func emojiToSkinToneComponents(emoji: String) -> (Emoji, [Emoji.SkinTone])? {")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("switch emoji {")
|
||||
emojiModel.definitions.forEach { emojiDef in
|
||||
let skintoneVariants = emojiDef.variants.filter({ $0.skintoneSequence != .none})
|
||||
if skintoneVariants.isEmpty {
|
||||
// None of our variants have a skintone, nothing to do
|
||||
return
|
||||
}
|
||||
|
||||
skintoneVariants.forEach { variant in
|
||||
let skintoneSequenceKey = variant.skintoneSequence.map({ ".\($0)" }).joined(separator: ", ")
|
||||
fileHandle.writeLine("case \"\(variant.emojiChar)\": return (.\(emojiDef.enumName), [\(skintoneSequenceKey)])")
|
||||
}
|
||||
}
|
||||
fileHandle.writeLine("default: return nil")
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
}
|
||||
|
||||
static func writeSkinToneLookupFile(from emojiModel: EmojiModel) {
|
||||
writeBlock(fileName: "Emoji+SkinTones.swift") { fileHandle in
|
||||
fileHandle.writeLine("extension Emoji {")
|
||||
fileHandle.indent {
|
||||
// SkinTone enum
|
||||
fileHandle.writeLine("enum SkinTone: String, CaseIterable, Equatable {")
|
||||
fileHandle.indent {
|
||||
for skinTone in EmojiModel.SkinTone.allCases {
|
||||
fileHandle.writeLine("case \(skinTone) = \"\(skinTone.rawValue)\"")
|
||||
}
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
fileHandle.writeLine("")
|
||||
|
||||
// skin tone helpers
|
||||
fileHandle.writeLine("var hasSkinTones: Bool { return emojiPerSkinTonePermutation != nil }")
|
||||
fileHandle.writeLine("var allowsMultipleSkinTones: Bool { return hasSkinTones && skinToneComponentEmoji != nil }")
|
||||
fileHandle.writeLine("")
|
||||
|
||||
// Start skinToneComponentEmoji
|
||||
fileHandle.writeLine("var skinToneComponentEmoji: [Emoji]? {")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("switch self {")
|
||||
emojiModel.definitions.forEach { emojiDef in
|
||||
if let components = emojiDef.skinToneComponents {
|
||||
fileHandle.writeLine("case .\(emojiDef.enumName): return \(components)")
|
||||
}
|
||||
}
|
||||
|
||||
fileHandle.writeLine("default: return nil")
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
fileHandle.writeLine("")
|
||||
|
||||
// Start emojiPerSkinTonePermutation
|
||||
fileHandle.writeLine("var emojiPerSkinTonePermutation: [[SkinTone]: String]? {")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("switch self {")
|
||||
emojiModel.definitions.forEach { emojiDef in
|
||||
let skintoneVariants = emojiDef.variants.filter({ $0.skintoneSequence != .none})
|
||||
if skintoneVariants.isEmpty {
|
||||
// None of our variants have a skintone, nothing to do
|
||||
return
|
||||
}
|
||||
|
||||
fileHandle.writeLine("case .\(emojiDef.enumName):")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("return [")
|
||||
fileHandle.indent {
|
||||
skintoneVariants.forEach {
|
||||
let skintoneSequenceKey = $0.skintoneSequence.map({ ".\($0)" }).joined(separator: ", ")
|
||||
let suffix = $0 == skintoneVariants.last ? "" : ","
|
||||
fileHandle.writeLine("[\(skintoneSequenceKey)]: \"\($0.emojiChar)\"\(suffix)")
|
||||
}
|
||||
}
|
||||
fileHandle.writeLine("]")
|
||||
}
|
||||
}
|
||||
fileHandle.writeLine("default: return nil")
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
}
|
||||
|
||||
static func writeCategoryLookupFile(from emojiModel: EmojiModel) {
|
||||
let outputCategories: [RemoteModel.EmojiCategory] = [
|
||||
.smileysAndPeople,
|
||||
.animals,
|
||||
.food,
|
||||
.activities,
|
||||
.travel,
|
||||
.objects,
|
||||
.symbols,
|
||||
.flags
|
||||
]
|
||||
|
||||
writeBlock(fileName: "Emoji+Category.swift") { fileHandle in
|
||||
fileHandle.writeLine("")
|
||||
|
||||
fileHandle.writeLine("extension Emoji {")
|
||||
fileHandle.indent {
|
||||
|
||||
// Category enum
|
||||
fileHandle.writeLine("enum Category: String, CaseIterable, Equatable {")
|
||||
fileHandle.indent {
|
||||
// Declare cases
|
||||
for category in outputCategories {
|
||||
fileHandle.writeLine("case \(category) = \"\(category.rawValue)\"")
|
||||
}
|
||||
fileHandle.writeLine("")
|
||||
|
||||
// Localized name for category
|
||||
fileHandle.writeLine("var localizedName: String {")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("switch self {")
|
||||
for category in outputCategories {
|
||||
fileHandle.writeLine("case .\(category):")
|
||||
fileHandle.indent {
|
||||
let stringKey = "EMOJI_CATEGORY_\("\(category)".uppercased())_NAME"
|
||||
let stringComment = "The name for the emoji category '\(category.rawValue)'"
|
||||
|
||||
fileHandle.writeLine("return OWSLocalizedString(\"\(stringKey)\", comment: \"\(stringComment)\")")
|
||||
}
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
fileHandle.writeLine("")
|
||||
|
||||
// Emoji lookup per category
|
||||
fileHandle.writeLine("var normalizedEmoji: [Emoji] {")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("switch self {")
|
||||
|
||||
let normalizedEmojiPerCategory: [RemoteModel.EmojiCategory: [EmojiModel.EmojiDefinition]]
|
||||
normalizedEmojiPerCategory = emojiModel.definitions.reduce(into: [:]) { result, emojiDef in
|
||||
if emojiDef.isNormalized {
|
||||
var categoryList = result[emojiDef.category] ?? []
|
||||
categoryList.append(emojiDef)
|
||||
result[emojiDef.category] = categoryList
|
||||
}
|
||||
}
|
||||
|
||||
for category in outputCategories {
|
||||
let emoji: [EmojiModel.EmojiDefinition] = {
|
||||
switch category {
|
||||
case .smileysAndPeople:
|
||||
// Merge smileys & people. It's important we initially bucket these separately,
|
||||
// because we want the emojis to be sorted smileys followed by people
|
||||
return normalizedEmojiPerCategory[.smileys]! + normalizedEmojiPerCategory[.people]!
|
||||
default:
|
||||
return normalizedEmojiPerCategory[category]!
|
||||
}
|
||||
}()
|
||||
|
||||
fileHandle.writeLine("case .\(category):")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("return [")
|
||||
fileHandle.indent {
|
||||
emoji.compactMap { $0.enumName }.forEach { name in
|
||||
let suffix = name == emoji.last?.enumName ? "" : ","
|
||||
fileHandle.writeLine(".\(name)\(suffix)")
|
||||
}
|
||||
}
|
||||
fileHandle.writeLine("]")
|
||||
}
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
fileHandle.writeLine("")
|
||||
|
||||
// Category lookup per emoji
|
||||
fileHandle.writeLine("var category: Category {")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("switch self {")
|
||||
for emojiDef in emojiModel.definitions {
|
||||
let category = [.smileys, .people].contains(emojiDef.category) ? .smileysAndPeople : emojiDef.category
|
||||
if category != .components {
|
||||
fileHandle.writeLine("case .\(emojiDef.enumName): return .\(category)")
|
||||
}
|
||||
}
|
||||
// Write a default case, because this enum is too long for the compiler to validate it's exhaustive
|
||||
fileHandle.writeLine("default: fatalError(\"Unexpected case \\(self)\")")
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
fileHandle.writeLine("")
|
||||
|
||||
// Normalized variant mapping
|
||||
fileHandle.writeLine("var isNormalized: Bool { normalized == self }")
|
||||
fileHandle.writeLine("var normalized: Emoji {")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("switch self {")
|
||||
emojiModel.definitions.filter { !$0.isNormalized }.forEach {
|
||||
fileHandle.writeLine("case .\($0.enumName): return .\($0.normalizedEnumName)")
|
||||
}
|
||||
fileHandle.writeLine("default: return self")
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
}
|
||||
|
||||
static func writeNameLookupFile(from emojiModel: EmojiModel) {
|
||||
// Name lookup: Create a computed property mapping an Emoji enum element to the raw Emoji name string
|
||||
// e.g. case .grinning: return "GRINNING FACE"
|
||||
writeBlock(fileName: "Emoji+Name.swift") { fileHandle in
|
||||
fileHandle.writeLine("extension Emoji {")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("var name: String {")
|
||||
fileHandle.indent {
|
||||
fileHandle.writeLine("switch self {")
|
||||
emojiModel.definitions.forEach {
|
||||
fileHandle.writeLine("case .\($0.enumName): return \"\($0.rawName)\"")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
fileHandle.writeLine("}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - File I/O Helpers
|
||||
|
||||
class WriteHandle {
|
||||
static let emojiDirectory = URL(
|
||||
fileURLWithPath: "../Signal/src/util/Emoji",
|
||||
isDirectory: true,
|
||||
relativeTo: EmojiGenerator.pathToFolderContainingThisScript!)
|
||||
|
||||
let handle: FileHandle
|
||||
|
||||
var indentDepth: Int = 0
|
||||
var hasBeenClosed = false
|
||||
|
||||
func indent(_ block: () -> Void) {
|
||||
indentDepth += 1
|
||||
block()
|
||||
indentDepth -= 1
|
||||
}
|
||||
|
||||
func writeLine(_ body: String) {
|
||||
let spaces = indentDepth * 4
|
||||
let prefix = body.isEmpty ? "" : String(repeating: " ", count: spaces)
|
||||
let suffix = "\n"
|
||||
|
||||
let line = prefix + body + suffix
|
||||
handle.write(line.data(using: .utf8)!)
|
||||
}
|
||||
|
||||
init(fileName: String) {
|
||||
// Create directory if necessary
|
||||
if !FileManager.default.fileExists(atPath: Self.emojiDirectory.path) {
|
||||
try! FileManager.default.createDirectory(at: Self.emojiDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
|
||||
// Delete old file and create anew
|
||||
let url = URL(fileURLWithPath: fileName, relativeTo: Self.emojiDirectory)
|
||||
if FileManager.default.fileExists(atPath: url.path) {
|
||||
try! FileManager.default.removeItem(at: url)
|
||||
}
|
||||
FileManager.default.createFile(atPath: url.path, contents: nil, attributes: nil)
|
||||
handle = try! FileHandle(forWritingTo: url)
|
||||
}
|
||||
|
||||
deinit {
|
||||
precondition(hasBeenClosed, "File handle still open at de-init")
|
||||
}
|
||||
|
||||
func close() {
|
||||
handle.closeFile()
|
||||
hasBeenClosed = true
|
||||
}
|
||||
}
|
||||
|
||||
extension EmojiGenerator {
|
||||
static func writeBlock(fileName: String, block: (WriteHandle) -> Void) {
|
||||
let fileHandle = WriteHandle(fileName: fileName)
|
||||
defer { fileHandle.close() }
|
||||
|
||||
fileHandle.writeLine("//")
|
||||
fileHandle.writeLine("// Copyright 2020 Signal Messenger, LLC")
|
||||
fileHandle.writeLine("// SPDX-License-Identifier: AGPL-3.0-only")
|
||||
fileHandle.writeLine("//")
|
||||
fileHandle.writeLine("")
|
||||
|
||||
fileHandle.writeLine("// This file is generated by EmojiGenerator.swift, do not manually edit it.")
|
||||
fileHandle.writeLine("")
|
||||
|
||||
block(fileHandle)
|
||||
}
|
||||
|
||||
// from http://stackoverflow.com/a/31480534/255489
|
||||
static var pathToFolderContainingThisScript: URL? = {
|
||||
let cwd = FileManager.default.currentDirectoryPath
|
||||
|
||||
let script = CommandLine.arguments[0]
|
||||
|
||||
if script.hasPrefix("/") { // absolute
|
||||
let path = (script as NSString).deletingLastPathComponent
|
||||
return URL(fileURLWithPath: path)
|
||||
} else { // relative
|
||||
let urlCwd = URL(fileURLWithPath: cwd)
|
||||
|
||||
if let urlPath = URL(string: script, relativeTo: urlCwd) {
|
||||
let path = (urlPath.path as NSString).deletingLastPathComponent
|
||||
return URL(fileURLWithPath: path)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
}
|
||||
|
||||
// MARK: - Misc
|
||||
|
||||
extension String {
|
||||
var titlecase: String {
|
||||
components(separatedBy: " ")
|
||||
.map { $0.first!.uppercased() + $0.dropFirst().lowercased() }
|
||||
.joined(separator: " ")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
class EmojiGenerator {
|
||||
static func run() throws {
|
||||
let remoteData = try RemoteModel.fetchEmojiData()
|
||||
let model = try EmojiModel(rawJSONData: remoteData)
|
||||
|
||||
writePrimaryFile(from: model)
|
||||
writeStringConversionsFile(from: model)
|
||||
writeSkinToneLookupFile(from: model)
|
||||
writeCategoryLookupFile(from: model)
|
||||
writeNameLookupFile(from: model)
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
try EmojiGenerator.run()
|
||||
} catch {
|
||||
print("Failed to generate emoji data: \(error)")
|
||||
let errorCode = (error as? CustomNSError)?.errorCode ?? -1
|
||||
exit(Int32(errorCode))
|
||||
}
|
4
Scripts/HardResetGit.sh
Executable file
4
Scripts/HardResetGit.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
git reset --hard HEAD
|
||||
git clean -xdff
|
83
Scripts/bump_build_tag.py
Executable file
83
Scripts/bump_build_tag.py
Executable file
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import plistlib
|
||||
import subprocess
|
||||
|
||||
INFO_PLIST_PATHS = [
|
||||
"Signal/Signal-Info.plist",
|
||||
"SignalShareExtension/Info.plist",
|
||||
"SignalNSE/Info.plist",
|
||||
]
|
||||
|
||||
|
||||
def run(args):
|
||||
subprocess.run(args, check=True)
|
||||
|
||||
|
||||
def capture(args):
|
||||
return subprocess.run(args, check=True, capture_output=True, encoding="utf8").stdout
|
||||
|
||||
|
||||
class Version:
|
||||
def __init__(self, major, minor, patch):
|
||||
self.major = major
|
||||
self.minor = minor
|
||||
self.patch = patch
|
||||
|
||||
def pretty(self):
|
||||
return self.pretty2() if self.patch == 0 else self.pretty3()
|
||||
|
||||
def pretty3(self):
|
||||
return f"{self.major}.{self.minor}.{self.patch}"
|
||||
|
||||
def pretty2(self):
|
||||
assert self.patch == 0
|
||||
return f"{self.major}.{self.minor}"
|
||||
|
||||
|
||||
def parse_version(value):
|
||||
components = list(map(int, value.split(".")))
|
||||
assert len(components) in (2, 3)
|
||||
while len(components) < 3:
|
||||
components.append(0)
|
||||
return Version(components[0], components[1], components[2])
|
||||
|
||||
|
||||
def set_version(path, version):
|
||||
with open(path, "rb") as file:
|
||||
contents = plistlib.load(file)
|
||||
contents["CFBundleShortVersionString"] = version.pretty()
|
||||
with open(path, "wb") as file:
|
||||
plistlib.dump(contents, file)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="bumps the marketing version")
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
metavar="x.y.z",
|
||||
required=True,
|
||||
help="specify the new marketing version number",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--nightly", action="store_true", help="specify that this is a nightly build"
|
||||
)
|
||||
|
||||
ns = parser.parse_args()
|
||||
|
||||
output = capture(["git", "status", "--porcelain"]).rstrip()
|
||||
if len(output) > 0:
|
||||
print(output)
|
||||
print("Repository has uncommitted changes.")
|
||||
exit(1)
|
||||
|
||||
version = parse_version(ns.version)
|
||||
|
||||
for path in INFO_PLIST_PATHS:
|
||||
set_version(path, version)
|
||||
|
||||
run(["git", "add", *INFO_PLIST_PATHS])
|
||||
run(["git", "commit", "-m", f"Bump version to {version.pretty()}"])
|
||||
if version.patch == 0:
|
||||
run(["git", "tag", f"version-{version.pretty2()}"])
|
28
Scripts/check_xcode_version.py
Executable file
28
Scripts/check_xcode_version.py
Executable file
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
|
||||
|
||||
def get_actual_version():
|
||||
return subprocess.run(
|
||||
["xcodebuild", "-version"], check=True, capture_output=True, encoding="utf8"
|
||||
).stdout.split("\n")[0]
|
||||
|
||||
|
||||
def get_expected_version():
|
||||
with open(".xcode-version", "r") as file:
|
||||
return file.read().rstrip()
|
||||
|
||||
|
||||
def main():
|
||||
actual_version = get_actual_version()
|
||||
expected_version = get_expected_version()
|
||||
if actual_version != expected_version:
|
||||
print(
|
||||
f"You’re using {actual_version} but you should be using {expected_version}."
|
||||
)
|
||||
exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
679
Scripts/emoji-data.txt
Normal file
679
Scripts/emoji-data.txt
Normal file
|
@ -0,0 +1,679 @@
|
|||
# emoji-data.txt
|
||||
# Date: 2017-08-10, 11:51:32 GMT
|
||||
# © 2017 Unicode®, Inc.
|
||||
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
|
||||
# For terms of use, see http://www.unicode.org/terms_of_use.html
|
||||
#
|
||||
# Emoji Data for UTR #51
|
||||
# Version: 6.0
|
||||
#
|
||||
# For documentation and usage, see http://www.unicode.org/reports/tr51
|
||||
#
|
||||
# Format:
|
||||
# <codepoint(s)> ; <property> # <comments>
|
||||
# Note: there is no guarantee as to the structure of whitespace or comments
|
||||
#
|
||||
# Characters and sequences are listed in code point order. Users should be shown a more natural order.
|
||||
# See the CLDR collation order for Emoji.
|
||||
|
||||
|
||||
# ================================================
|
||||
|
||||
# All omitted code points have Emoji=No
|
||||
# @missing: 0000..10FFFF ; Emoji ; No
|
||||
|
||||
0023 ; Emoji # 1.1 [1] (#️) number sign
|
||||
002A ; Emoji # 1.1 [1] (*️) asterisk
|
||||
0030..0039 ; Emoji # 1.1 [10] (0️..9️) digit zero..digit nine
|
||||
00A9 ; Emoji # 1.1 [1] (©️) copyright
|
||||
00AE ; Emoji # 1.1 [1] (®️) registered
|
||||
203C ; Emoji # 1.1 [1] (‼️) double exclamation mark
|
||||
2049 ; Emoji # 3.0 [1] (⁉️) exclamation question mark
|
||||
2122 ; Emoji # 1.1 [1] (™️) trade mark
|
||||
2139 ; Emoji # 3.0 [1] (ℹ️) information
|
||||
2194..2199 ; Emoji # 1.1 [6] (↔️..↙️) left-right arrow..down-left arrow
|
||||
21A9..21AA ; Emoji # 1.1 [2] (↩️..↪️) right arrow curving left..left arrow curving right
|
||||
231A..231B ; Emoji # 1.1 [2] (⌚..⌛) watch..hourglass done
|
||||
2328 ; Emoji # 1.1 [1] (⌨️) keyboard
|
||||
23CF ; Emoji # 4.0 [1] (⏏️) eject button
|
||||
23E9..23F3 ; Emoji # 6.0 [11] (⏩..⏳) fast-forward button..hourglass not done
|
||||
23F8..23FA ; Emoji # 7.0 [3] (⏸️..⏺️) pause button..record button
|
||||
24C2 ; Emoji # 1.1 [1] (Ⓜ️) circled M
|
||||
25AA..25AB ; Emoji # 1.1 [2] (▪️..▫️) black small square..white small square
|
||||
25B6 ; Emoji # 1.1 [1] (▶️) play button
|
||||
25C0 ; Emoji # 1.1 [1] (◀️) reverse button
|
||||
25FB..25FE ; Emoji # 3.2 [4] (◻️..◾) white medium square..black medium-small square
|
||||
2600..2604 ; Emoji # 1.1 [5] (☀️..☄️) sun..comet
|
||||
260E ; Emoji # 1.1 [1] (☎️) telephone
|
||||
2611 ; Emoji # 1.1 [1] (☑️) ballot box with check
|
||||
2614..2615 ; Emoji # 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
|
||||
2618 ; Emoji # 4.1 [1] (☘️) shamrock
|
||||
261D ; Emoji # 1.1 [1] (☝️) index pointing up
|
||||
2620 ; Emoji # 1.1 [1] (☠️) skull and crossbones
|
||||
2622..2623 ; Emoji # 1.1 [2] (☢️..☣️) radioactive..biohazard
|
||||
2626 ; Emoji # 1.1 [1] (☦️) orthodox cross
|
||||
262A ; Emoji # 1.1 [1] (☪️) star and crescent
|
||||
262E..262F ; Emoji # 1.1 [2] (☮️..☯️) peace symbol..yin yang
|
||||
2638..263A ; Emoji # 1.1 [3] (☸️..☺️) wheel of dharma..smiling face
|
||||
2640 ; Emoji # 1.1 [1] (♀️) female sign
|
||||
2642 ; Emoji # 1.1 [1] (♂️) male sign
|
||||
2648..2653 ; Emoji # 1.1 [12] (♈..♓) Aries..Pisces
|
||||
2660 ; Emoji # 1.1 [1] (♠️) spade suit
|
||||
2663 ; Emoji # 1.1 [1] (♣️) club suit
|
||||
2665..2666 ; Emoji # 1.1 [2] (♥️..♦️) heart suit..diamond suit
|
||||
2668 ; Emoji # 1.1 [1] (♨️) hot springs
|
||||
267B ; Emoji # 3.2 [1] (♻️) recycling symbol
|
||||
267F ; Emoji # 4.1 [1] (♿) wheelchair symbol
|
||||
2692..2697 ; Emoji # 4.1 [6] (⚒️..⚗️) hammer and pick..alembic
|
||||
2699 ; Emoji # 4.1 [1] (⚙️) gear
|
||||
269B..269C ; Emoji # 4.1 [2] (⚛️..⚜️) atom symbol..fleur-de-lis
|
||||
26A0..26A1 ; Emoji # 4.0 [2] (⚠️..⚡) warning..high voltage
|
||||
26AA..26AB ; Emoji # 4.1 [2] (⚪..⚫) white circle..black circle
|
||||
26B0..26B1 ; Emoji # 4.1 [2] (⚰️..⚱️) coffin..funeral urn
|
||||
26BD..26BE ; Emoji # 5.2 [2] (⚽..⚾) soccer ball..baseball
|
||||
26C4..26C5 ; Emoji # 5.2 [2] (⛄..⛅) snowman without snow..sun behind cloud
|
||||
26C8 ; Emoji # 5.2 [1] (⛈️) cloud with lightning and rain
|
||||
26CE ; Emoji # 6.0 [1] (⛎) Ophiuchus
|
||||
26CF ; Emoji # 5.2 [1] (⛏️) pick
|
||||
26D1 ; Emoji # 5.2 [1] (⛑️) rescue worker’s helmet
|
||||
26D3..26D4 ; Emoji # 5.2 [2] (⛓️..⛔) chains..no entry
|
||||
26E9..26EA ; Emoji # 5.2 [2] (⛩️..⛪) shinto shrine..church
|
||||
26F0..26F5 ; Emoji # 5.2 [6] (⛰️..⛵) mountain..sailboat
|
||||
26F7..26FA ; Emoji # 5.2 [4] (⛷️..⛺) skier..tent
|
||||
26FD ; Emoji # 5.2 [1] (⛽) fuel pump
|
||||
2702 ; Emoji # 1.1 [1] (✂️) scissors
|
||||
2705 ; Emoji # 6.0 [1] (✅) white heavy check mark
|
||||
2708..2709 ; Emoji # 1.1 [2] (✈️..✉️) airplane..envelope
|
||||
270A..270B ; Emoji # 6.0 [2] (✊..✋) raised fist..raised hand
|
||||
270C..270D ; Emoji # 1.1 [2] (✌️..✍️) victory hand..writing hand
|
||||
270F ; Emoji # 1.1 [1] (✏️) pencil
|
||||
2712 ; Emoji # 1.1 [1] (✒️) black nib
|
||||
2714 ; Emoji # 1.1 [1] (✔️) heavy check mark
|
||||
2716 ; Emoji # 1.1 [1] (✖️) heavy multiplication x
|
||||
271D ; Emoji # 1.1 [1] (✝️) latin cross
|
||||
2721 ; Emoji # 1.1 [1] (✡️) star of David
|
||||
2728 ; Emoji # 6.0 [1] (✨) sparkles
|
||||
2733..2734 ; Emoji # 1.1 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star
|
||||
2744 ; Emoji # 1.1 [1] (❄️) snowflake
|
||||
2747 ; Emoji # 1.1 [1] (❇️) sparkle
|
||||
274C ; Emoji # 6.0 [1] (❌) cross mark
|
||||
274E ; Emoji # 6.0 [1] (❎) cross mark button
|
||||
2753..2755 ; Emoji # 6.0 [3] (❓..❕) question mark..white exclamation mark
|
||||
2757 ; Emoji # 5.2 [1] (❗) exclamation mark
|
||||
2763..2764 ; Emoji # 1.1 [2] (❣️..❤️) heavy heart exclamation..red heart
|
||||
2795..2797 ; Emoji # 6.0 [3] (➕..➗) heavy plus sign..heavy division sign
|
||||
27A1 ; Emoji # 1.1 [1] (➡️) right arrow
|
||||
27B0 ; Emoji # 6.0 [1] (➰) curly loop
|
||||
27BF ; Emoji # 6.0 [1] (➿) double curly loop
|
||||
2934..2935 ; Emoji # 3.2 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down
|
||||
2B05..2B07 ; Emoji # 4.0 [3] (⬅️..⬇️) left arrow..down arrow
|
||||
2B1B..2B1C ; Emoji # 5.1 [2] (⬛..⬜) black large square..white large square
|
||||
2B50 ; Emoji # 5.1 [1] (⭐) white medium star
|
||||
2B55 ; Emoji # 5.2 [1] (⭕) heavy large circle
|
||||
3030 ; Emoji # 1.1 [1] (〰️) wavy dash
|
||||
303D ; Emoji # 3.2 [1] (〽️) part alternation mark
|
||||
3297 ; Emoji # 1.1 [1] (㊗️) Japanese “congratulations” button
|
||||
3299 ; Emoji # 1.1 [1] (㊙️) Japanese “secret” button
|
||||
1F004 ; Emoji # 5.1 [1] (🀄) mahjong red dragon
|
||||
1F0CF ; Emoji # 6.0 [1] (🃏) joker
|
||||
1F170..1F171 ; Emoji # 6.0 [2] (🅰️..🅱️) A button (blood type)..B button (blood type)
|
||||
1F17E ; Emoji # 6.0 [1] (🅾️) O button (blood type)
|
||||
1F17F ; Emoji # 5.2 [1] (🅿️) P button
|
||||
1F18E ; Emoji # 6.0 [1] (🆎) AB button (blood type)
|
||||
1F191..1F19A ; Emoji # 6.0 [10] (🆑..🆚) CL button..VS button
|
||||
1F1E6..1F1FF ; Emoji # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
|
||||
1F201..1F202 ; Emoji # 6.0 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button
|
||||
1F21A ; Emoji # 5.2 [1] (🈚) Japanese “free of charge” button
|
||||
1F22F ; Emoji # 5.2 [1] (🈯) Japanese “reserved” button
|
||||
1F232..1F23A ; Emoji # 6.0 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button
|
||||
1F250..1F251 ; Emoji # 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
|
||||
1F300..1F320 ; Emoji # 6.0 [33] (🌀..🌠) cyclone..shooting star
|
||||
1F321 ; Emoji # 7.0 [1] (🌡️) thermometer
|
||||
1F324..1F32C ; Emoji # 7.0 [9] (🌤️..🌬️) sun behind small cloud..wind face
|
||||
1F32D..1F32F ; Emoji # 8.0 [3] (🌭..🌯) hot dog..burrito
|
||||
1F330..1F335 ; Emoji # 6.0 [6] (🌰..🌵) chestnut..cactus
|
||||
1F336 ; Emoji # 7.0 [1] (🌶️) hot pepper
|
||||
1F337..1F37C ; Emoji # 6.0 [70] (🌷..🍼) tulip..baby bottle
|
||||
1F37D ; Emoji # 7.0 [1] (🍽️) fork and knife with plate
|
||||
1F37E..1F37F ; Emoji # 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn
|
||||
1F380..1F393 ; Emoji # 6.0 [20] (🎀..🎓) ribbon..graduation cap
|
||||
1F396..1F397 ; Emoji # 7.0 [2] (🎖️..🎗️) military medal..reminder ribbon
|
||||
1F399..1F39B ; Emoji # 7.0 [3] (🎙️..🎛️) studio microphone..control knobs
|
||||
1F39E..1F39F ; Emoji # 7.0 [2] (🎞️..🎟️) film frames..admission tickets
|
||||
1F3A0..1F3C4 ; Emoji # 6.0 [37] (🎠..🏄) carousel horse..person surfing
|
||||
1F3C5 ; Emoji # 7.0 [1] (🏅) sports medal
|
||||
1F3C6..1F3CA ; Emoji # 6.0 [5] (🏆..🏊) trophy..person swimming
|
||||
1F3CB..1F3CE ; Emoji # 7.0 [4] (🏋️..🏎️) person lifting weights..racing car
|
||||
1F3CF..1F3D3 ; Emoji # 8.0 [5] (🏏..🏓) cricket game..ping pong
|
||||
1F3D4..1F3DF ; Emoji # 7.0 [12] (🏔️..🏟️) snow-capped mountain..stadium
|
||||
1F3E0..1F3F0 ; Emoji # 6.0 [17] (🏠..🏰) house..castle
|
||||
1F3F3..1F3F5 ; Emoji # 7.0 [3] (🏳️..🏵️) white flag..rosette
|
||||
1F3F7 ; Emoji # 7.0 [1] (🏷️) label
|
||||
1F3F8..1F3FF ; Emoji # 8.0 [8] (🏸..🏿) badminton..dark skin tone
|
||||
1F400..1F43E ; Emoji # 6.0 [63] (🐀..🐾) rat..paw prints
|
||||
1F43F ; Emoji # 7.0 [1] (🐿️) chipmunk
|
||||
1F440 ; Emoji # 6.0 [1] (👀) eyes
|
||||
1F441 ; Emoji # 7.0 [1] (👁️) eye
|
||||
1F442..1F4F7 ; Emoji # 6.0[182] (👂..📷) ear..camera
|
||||
1F4F8 ; Emoji # 7.0 [1] (📸) camera with flash
|
||||
1F4F9..1F4FC ; Emoji # 6.0 [4] (📹..📼) video camera..videocassette
|
||||
1F4FD ; Emoji # 7.0 [1] (📽️) film projector
|
||||
1F4FF ; Emoji # 8.0 [1] (📿) prayer beads
|
||||
1F500..1F53D ; Emoji # 6.0 [62] (🔀..🔽) shuffle tracks button..down button
|
||||
1F549..1F54A ; Emoji # 7.0 [2] (🕉️..🕊️) om..dove
|
||||
1F54B..1F54E ; Emoji # 8.0 [4] (🕋..🕎) kaaba..menorah
|
||||
1F550..1F567 ; Emoji # 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty
|
||||
1F56F..1F570 ; Emoji # 7.0 [2] (🕯️..🕰️) candle..mantelpiece clock
|
||||
1F573..1F579 ; Emoji # 7.0 [7] (🕳️..🕹️) hole..joystick
|
||||
1F57A ; Emoji # 9.0 [1] (🕺) man dancing
|
||||
1F587 ; Emoji # 7.0 [1] (🖇️) linked paperclips
|
||||
1F58A..1F58D ; Emoji # 7.0 [4] (🖊️..🖍️) pen..crayon
|
||||
1F590 ; Emoji # 7.0 [1] (🖐️) hand with fingers splayed
|
||||
1F595..1F596 ; Emoji # 7.0 [2] (🖕..🖖) middle finger..vulcan salute
|
||||
1F5A4 ; Emoji # 9.0 [1] (🖤) black heart
|
||||
1F5A5 ; Emoji # 7.0 [1] (🖥️) desktop computer
|
||||
1F5A8 ; Emoji # 7.0 [1] (🖨️) printer
|
||||
1F5B1..1F5B2 ; Emoji # 7.0 [2] (🖱️..🖲️) computer mouse..trackball
|
||||
1F5BC ; Emoji # 7.0 [1] (🖼️) framed picture
|
||||
1F5C2..1F5C4 ; Emoji # 7.0 [3] (🗂️..🗄️) card index dividers..file cabinet
|
||||
1F5D1..1F5D3 ; Emoji # 7.0 [3] (🗑️..🗓️) wastebasket..spiral calendar
|
||||
1F5DC..1F5DE ; Emoji # 7.0 [3] (🗜️..🗞️) clamp..rolled-up newspaper
|
||||
1F5E1 ; Emoji # 7.0 [1] (🗡️) dagger
|
||||
1F5E3 ; Emoji # 7.0 [1] (🗣️) speaking head
|
||||
1F5E8 ; Emoji # 7.0 [1] (🗨️) left speech bubble
|
||||
1F5EF ; Emoji # 7.0 [1] (🗯️) right anger bubble
|
||||
1F5F3 ; Emoji # 7.0 [1] (🗳️) ballot box with ballot
|
||||
1F5FA ; Emoji # 7.0 [1] (🗺️) world map
|
||||
1F5FB..1F5FF ; Emoji # 6.0 [5] (🗻..🗿) mount fuji..moai
|
||||
1F600 ; Emoji # 6.1 [1] (😀) grinning face
|
||||
1F601..1F610 ; Emoji # 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face
|
||||
1F611 ; Emoji # 6.1 [1] (😑) expressionless face
|
||||
1F612..1F614 ; Emoji # 6.0 [3] (😒..😔) unamused face..pensive face
|
||||
1F615 ; Emoji # 6.1 [1] (😕) confused face
|
||||
1F616 ; Emoji # 6.0 [1] (😖) confounded face
|
||||
1F617 ; Emoji # 6.1 [1] (😗) kissing face
|
||||
1F618 ; Emoji # 6.0 [1] (😘) face blowing a kiss
|
||||
1F619 ; Emoji # 6.1 [1] (😙) kissing face with smiling eyes
|
||||
1F61A ; Emoji # 6.0 [1] (😚) kissing face with closed eyes
|
||||
1F61B ; Emoji # 6.1 [1] (😛) face with tongue
|
||||
1F61C..1F61E ; Emoji # 6.0 [3] (😜..😞) winking face with tongue..disappointed face
|
||||
1F61F ; Emoji # 6.1 [1] (😟) worried face
|
||||
1F620..1F625 ; Emoji # 6.0 [6] (😠..😥) angry face..sad but relieved face
|
||||
1F626..1F627 ; Emoji # 6.1 [2] (😦..😧) frowning face with open mouth..anguished face
|
||||
1F628..1F62B ; Emoji # 6.0 [4] (😨..😫) fearful face..tired face
|
||||
1F62C ; Emoji # 6.1 [1] (😬) grimacing face
|
||||
1F62D ; Emoji # 6.0 [1] (😭) loudly crying face
|
||||
1F62E..1F62F ; Emoji # 6.1 [2] (😮..😯) face with open mouth..hushed face
|
||||
1F630..1F633 ; Emoji # 6.0 [4] (😰..😳) anxious face with sweat..flushed face
|
||||
1F634 ; Emoji # 6.1 [1] (😴) sleeping face
|
||||
1F635..1F640 ; Emoji # 6.0 [12] (😵..🙀) dizzy face..weary cat face
|
||||
1F641..1F642 ; Emoji # 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
|
||||
1F643..1F644 ; Emoji # 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
|
||||
1F645..1F64F ; Emoji # 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
|
||||
1F680..1F6C5 ; Emoji # 6.0 [70] (🚀..🛅) rocket..left luggage
|
||||
1F6CB..1F6CF ; Emoji # 7.0 [5] (🛋️..🛏️) couch and lamp..bed
|
||||
1F6D0 ; Emoji # 8.0 [1] (🛐) place of worship
|
||||
1F6D1..1F6D2 ; Emoji # 9.0 [2] (🛑..🛒) stop sign..shopping cart
|
||||
1F6E0..1F6E5 ; Emoji # 7.0 [6] (🛠️..🛥️) hammer and wrench..motor boat
|
||||
1F6E9 ; Emoji # 7.0 [1] (🛩️) small airplane
|
||||
1F6EB..1F6EC ; Emoji # 7.0 [2] (🛫..🛬) airplane departure..airplane arrival
|
||||
1F6F0 ; Emoji # 7.0 [1] (🛰️) satellite
|
||||
1F6F3 ; Emoji # 7.0 [1] (🛳️) passenger ship
|
||||
1F6F4..1F6F6 ; Emoji # 9.0 [3] (🛴..🛶) kick scooter..canoe
|
||||
1F6F7..1F6F8 ; Emoji # 10.0 [2] (🛷..🛸) sled..flying saucer
|
||||
1F910..1F918 ; Emoji # 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
|
||||
1F919..1F91E ; Emoji # 9.0 [6] (🤙..🤞) call me hand..crossed fingers
|
||||
1F91F ; Emoji # 10.0 [1] (🤟) love-you gesture
|
||||
1F920..1F927 ; Emoji # 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face
|
||||
1F928..1F92F ; Emoji # 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
|
||||
1F930 ; Emoji # 9.0 [1] (🤰) pregnant woman
|
||||
1F931..1F932 ; Emoji # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
|
||||
1F933..1F93A ; Emoji # 9.0 [8] (🤳..🤺) selfie..person fencing
|
||||
1F93C..1F93E ; Emoji # 9.0 [3] (🤼..🤾) people wrestling..person playing handball
|
||||
1F940..1F945 ; Emoji # 9.0 [6] (🥀..🥅) wilted flower..goal net
|
||||
1F947..1F94B ; Emoji # 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
|
||||
1F94C ; Emoji # 10.0 [1] (🥌) curling stone
|
||||
1F950..1F95E ; Emoji # 9.0 [15] (🥐..🥞) croissant..pancakes
|
||||
1F95F..1F96B ; Emoji # 10.0 [13] (🥟..🥫) dumpling..canned food
|
||||
1F980..1F984 ; Emoji # 8.0 [5] (🦀..🦄) crab..unicorn face
|
||||
1F985..1F991 ; Emoji # 9.0 [13] (🦅..🦑) eagle..squid
|
||||
1F992..1F997 ; Emoji # 10.0 [6] (🦒..🦗) giraffe..cricket
|
||||
1F9C0 ; Emoji # 8.0 [1] (🧀) cheese wedge
|
||||
1F9D0..1F9E6 ; Emoji # 10.0 [23] (🧐..🧦) face with monocle..socks
|
||||
|
||||
# Total elements: 1182
|
||||
|
||||
# ================================================
|
||||
|
||||
# All omitted code points have Emoji_Presentation=No
|
||||
# @missing: 0000..10FFFF ; Emoji_Presentation ; No
|
||||
|
||||
231A..231B ; Emoji_Presentation # 1.1 [2] (⌚..⌛) watch..hourglass done
|
||||
23E9..23EC ; Emoji_Presentation # 6.0 [4] (⏩..⏬) fast-forward button..fast down button
|
||||
23F0 ; Emoji_Presentation # 6.0 [1] (⏰) alarm clock
|
||||
23F3 ; Emoji_Presentation # 6.0 [1] (⏳) hourglass not done
|
||||
25FD..25FE ; Emoji_Presentation # 3.2 [2] (◽..◾) white medium-small square..black medium-small square
|
||||
2614..2615 ; Emoji_Presentation # 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
|
||||
2648..2653 ; Emoji_Presentation # 1.1 [12] (♈..♓) Aries..Pisces
|
||||
267F ; Emoji_Presentation # 4.1 [1] (♿) wheelchair symbol
|
||||
2693 ; Emoji_Presentation # 4.1 [1] (⚓) anchor
|
||||
26A1 ; Emoji_Presentation # 4.0 [1] (⚡) high voltage
|
||||
26AA..26AB ; Emoji_Presentation # 4.1 [2] (⚪..⚫) white circle..black circle
|
||||
26BD..26BE ; Emoji_Presentation # 5.2 [2] (⚽..⚾) soccer ball..baseball
|
||||
26C4..26C5 ; Emoji_Presentation # 5.2 [2] (⛄..⛅) snowman without snow..sun behind cloud
|
||||
26CE ; Emoji_Presentation # 6.0 [1] (⛎) Ophiuchus
|
||||
26D4 ; Emoji_Presentation # 5.2 [1] (⛔) no entry
|
||||
26EA ; Emoji_Presentation # 5.2 [1] (⛪) church
|
||||
26F2..26F3 ; Emoji_Presentation # 5.2 [2] (⛲..⛳) fountain..flag in hole
|
||||
26F5 ; Emoji_Presentation # 5.2 [1] (⛵) sailboat
|
||||
26FA ; Emoji_Presentation # 5.2 [1] (⛺) tent
|
||||
26FD ; Emoji_Presentation # 5.2 [1] (⛽) fuel pump
|
||||
2705 ; Emoji_Presentation # 6.0 [1] (✅) white heavy check mark
|
||||
270A..270B ; Emoji_Presentation # 6.0 [2] (✊..✋) raised fist..raised hand
|
||||
2728 ; Emoji_Presentation # 6.0 [1] (✨) sparkles
|
||||
274C ; Emoji_Presentation # 6.0 [1] (❌) cross mark
|
||||
274E ; Emoji_Presentation # 6.0 [1] (❎) cross mark button
|
||||
2753..2755 ; Emoji_Presentation # 6.0 [3] (❓..❕) question mark..white exclamation mark
|
||||
2757 ; Emoji_Presentation # 5.2 [1] (❗) exclamation mark
|
||||
2795..2797 ; Emoji_Presentation # 6.0 [3] (➕..➗) heavy plus sign..heavy division sign
|
||||
27B0 ; Emoji_Presentation # 6.0 [1] (➰) curly loop
|
||||
27BF ; Emoji_Presentation # 6.0 [1] (➿) double curly loop
|
||||
2B1B..2B1C ; Emoji_Presentation # 5.1 [2] (⬛..⬜) black large square..white large square
|
||||
2B50 ; Emoji_Presentation # 5.1 [1] (⭐) white medium star
|
||||
2B55 ; Emoji_Presentation # 5.2 [1] (⭕) heavy large circle
|
||||
1F004 ; Emoji_Presentation # 5.1 [1] (🀄) mahjong red dragon
|
||||
1F0CF ; Emoji_Presentation # 6.0 [1] (🃏) joker
|
||||
1F18E ; Emoji_Presentation # 6.0 [1] (🆎) AB button (blood type)
|
||||
1F191..1F19A ; Emoji_Presentation # 6.0 [10] (🆑..🆚) CL button..VS button
|
||||
1F1E6..1F1FF ; Emoji_Presentation # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
|
||||
1F201 ; Emoji_Presentation # 6.0 [1] (🈁) Japanese “here” button
|
||||
1F21A ; Emoji_Presentation # 5.2 [1] (🈚) Japanese “free of charge” button
|
||||
1F22F ; Emoji_Presentation # 5.2 [1] (🈯) Japanese “reserved” button
|
||||
1F232..1F236 ; Emoji_Presentation # 6.0 [5] (🈲..🈶) Japanese “prohibited” button..Japanese “not free of charge” button
|
||||
1F238..1F23A ; Emoji_Presentation # 6.0 [3] (🈸..🈺) Japanese “application” button..Japanese “open for business” button
|
||||
1F250..1F251 ; Emoji_Presentation # 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
|
||||
1F300..1F320 ; Emoji_Presentation # 6.0 [33] (🌀..🌠) cyclone..shooting star
|
||||
1F32D..1F32F ; Emoji_Presentation # 8.0 [3] (🌭..🌯) hot dog..burrito
|
||||
1F330..1F335 ; Emoji_Presentation # 6.0 [6] (🌰..🌵) chestnut..cactus
|
||||
1F337..1F37C ; Emoji_Presentation # 6.0 [70] (🌷..🍼) tulip..baby bottle
|
||||
1F37E..1F37F ; Emoji_Presentation # 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn
|
||||
1F380..1F393 ; Emoji_Presentation # 6.0 [20] (🎀..🎓) ribbon..graduation cap
|
||||
1F3A0..1F3C4 ; Emoji_Presentation # 6.0 [37] (🎠..🏄) carousel horse..person surfing
|
||||
1F3C5 ; Emoji_Presentation # 7.0 [1] (🏅) sports medal
|
||||
1F3C6..1F3CA ; Emoji_Presentation # 6.0 [5] (🏆..🏊) trophy..person swimming
|
||||
1F3CF..1F3D3 ; Emoji_Presentation # 8.0 [5] (🏏..🏓) cricket game..ping pong
|
||||
1F3E0..1F3F0 ; Emoji_Presentation # 6.0 [17] (🏠..🏰) house..castle
|
||||
1F3F4 ; Emoji_Presentation # 7.0 [1] (🏴) black flag
|
||||
1F3F8..1F3FF ; Emoji_Presentation # 8.0 [8] (🏸..🏿) badminton..dark skin tone
|
||||
1F400..1F43E ; Emoji_Presentation # 6.0 [63] (🐀..🐾) rat..paw prints
|
||||
1F440 ; Emoji_Presentation # 6.0 [1] (👀) eyes
|
||||
1F442..1F4F7 ; Emoji_Presentation # 6.0[182] (👂..📷) ear..camera
|
||||
1F4F8 ; Emoji_Presentation # 7.0 [1] (📸) camera with flash
|
||||
1F4F9..1F4FC ; Emoji_Presentation # 6.0 [4] (📹..📼) video camera..videocassette
|
||||
1F4FF ; Emoji_Presentation # 8.0 [1] (📿) prayer beads
|
||||
1F500..1F53D ; Emoji_Presentation # 6.0 [62] (🔀..🔽) shuffle tracks button..down button
|
||||
1F54B..1F54E ; Emoji_Presentation # 8.0 [4] (🕋..🕎) kaaba..menorah
|
||||
1F550..1F567 ; Emoji_Presentation # 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty
|
||||
1F57A ; Emoji_Presentation # 9.0 [1] (🕺) man dancing
|
||||
1F595..1F596 ; Emoji_Presentation # 7.0 [2] (🖕..🖖) middle finger..vulcan salute
|
||||
1F5A4 ; Emoji_Presentation # 9.0 [1] (🖤) black heart
|
||||
1F5FB..1F5FF ; Emoji_Presentation # 6.0 [5] (🗻..🗿) mount fuji..moai
|
||||
1F600 ; Emoji_Presentation # 6.1 [1] (😀) grinning face
|
||||
1F601..1F610 ; Emoji_Presentation # 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face
|
||||
1F611 ; Emoji_Presentation # 6.1 [1] (😑) expressionless face
|
||||
1F612..1F614 ; Emoji_Presentation # 6.0 [3] (😒..😔) unamused face..pensive face
|
||||
1F615 ; Emoji_Presentation # 6.1 [1] (😕) confused face
|
||||
1F616 ; Emoji_Presentation # 6.0 [1] (😖) confounded face
|
||||
1F617 ; Emoji_Presentation # 6.1 [1] (😗) kissing face
|
||||
1F618 ; Emoji_Presentation # 6.0 [1] (😘) face blowing a kiss
|
||||
1F619 ; Emoji_Presentation # 6.1 [1] (😙) kissing face with smiling eyes
|
||||
1F61A ; Emoji_Presentation # 6.0 [1] (😚) kissing face with closed eyes
|
||||
1F61B ; Emoji_Presentation # 6.1 [1] (😛) face with tongue
|
||||
1F61C..1F61E ; Emoji_Presentation # 6.0 [3] (😜..😞) winking face with tongue..disappointed face
|
||||
1F61F ; Emoji_Presentation # 6.1 [1] (😟) worried face
|
||||
1F620..1F625 ; Emoji_Presentation # 6.0 [6] (😠..😥) angry face..sad but relieved face
|
||||
1F626..1F627 ; Emoji_Presentation # 6.1 [2] (😦..😧) frowning face with open mouth..anguished face
|
||||
1F628..1F62B ; Emoji_Presentation # 6.0 [4] (😨..😫) fearful face..tired face
|
||||
1F62C ; Emoji_Presentation # 6.1 [1] (😬) grimacing face
|
||||
1F62D ; Emoji_Presentation # 6.0 [1] (😭) loudly crying face
|
||||
1F62E..1F62F ; Emoji_Presentation # 6.1 [2] (😮..😯) face with open mouth..hushed face
|
||||
1F630..1F633 ; Emoji_Presentation # 6.0 [4] (😰..😳) anxious face with sweat..flushed face
|
||||
1F634 ; Emoji_Presentation # 6.1 [1] (😴) sleeping face
|
||||
1F635..1F640 ; Emoji_Presentation # 6.0 [12] (😵..🙀) dizzy face..weary cat face
|
||||
1F641..1F642 ; Emoji_Presentation # 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
|
||||
1F643..1F644 ; Emoji_Presentation # 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
|
||||
1F645..1F64F ; Emoji_Presentation # 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
|
||||
1F680..1F6C5 ; Emoji_Presentation # 6.0 [70] (🚀..🛅) rocket..left luggage
|
||||
1F6CC ; Emoji_Presentation # 7.0 [1] (🛌) person in bed
|
||||
1F6D0 ; Emoji_Presentation # 8.0 [1] (🛐) place of worship
|
||||
1F6D1..1F6D2 ; Emoji_Presentation # 9.0 [2] (🛑..🛒) stop sign..shopping cart
|
||||
1F6EB..1F6EC ; Emoji_Presentation # 7.0 [2] (🛫..🛬) airplane departure..airplane arrival
|
||||
1F6F4..1F6F6 ; Emoji_Presentation # 9.0 [3] (🛴..🛶) kick scooter..canoe
|
||||
1F6F7..1F6F8 ; Emoji_Presentation # 10.0 [2] (🛷..🛸) sled..flying saucer
|
||||
1F910..1F918 ; Emoji_Presentation # 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
|
||||
1F919..1F91E ; Emoji_Presentation # 9.0 [6] (🤙..🤞) call me hand..crossed fingers
|
||||
1F91F ; Emoji_Presentation # 10.0 [1] (🤟) love-you gesture
|
||||
1F920..1F927 ; Emoji_Presentation # 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face
|
||||
1F928..1F92F ; Emoji_Presentation # 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
|
||||
1F930 ; Emoji_Presentation # 9.0 [1] (🤰) pregnant woman
|
||||
1F931..1F932 ; Emoji_Presentation # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
|
||||
1F933..1F93A ; Emoji_Presentation # 9.0 [8] (🤳..🤺) selfie..person fencing
|
||||
1F93C..1F93E ; Emoji_Presentation # 9.0 [3] (🤼..🤾) people wrestling..person playing handball
|
||||
1F940..1F945 ; Emoji_Presentation # 9.0 [6] (🥀..🥅) wilted flower..goal net
|
||||
1F947..1F94B ; Emoji_Presentation # 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
|
||||
1F94C ; Emoji_Presentation # 10.0 [1] (🥌) curling stone
|
||||
1F950..1F95E ; Emoji_Presentation # 9.0 [15] (🥐..🥞) croissant..pancakes
|
||||
1F95F..1F96B ; Emoji_Presentation # 10.0 [13] (🥟..🥫) dumpling..canned food
|
||||
1F980..1F984 ; Emoji_Presentation # 8.0 [5] (🦀..🦄) crab..unicorn face
|
||||
1F985..1F991 ; Emoji_Presentation # 9.0 [13] (🦅..🦑) eagle..squid
|
||||
1F992..1F997 ; Emoji_Presentation # 10.0 [6] (🦒..🦗) giraffe..cricket
|
||||
1F9C0 ; Emoji_Presentation # 8.0 [1] (🧀) cheese wedge
|
||||
1F9D0..1F9E6 ; Emoji_Presentation # 10.0 [23] (🧐..🧦) face with monocle..socks
|
||||
|
||||
# Total elements: 966
|
||||
|
||||
# ================================================
|
||||
|
||||
# All omitted code points have Emoji_Modifier=No
|
||||
# @missing: 0000..10FFFF ; Emoji_Modifier ; No
|
||||
|
||||
1F3FB..1F3FF ; Emoji_Modifier # 8.0 [5] (🏻..🏿) light skin tone..dark skin tone
|
||||
|
||||
# Total elements: 5
|
||||
|
||||
# ================================================
|
||||
|
||||
# All omitted code points have Emoji_Modifier_Base=No
|
||||
# @missing: 0000..10FFFF ; Emoji_Modifier_Base ; No
|
||||
|
||||
261D ; Emoji_Modifier_Base # 1.1 [1] (☝️) index pointing up
|
||||
26F9 ; Emoji_Modifier_Base # 5.2 [1] (⛹️) person bouncing ball
|
||||
270A..270B ; Emoji_Modifier_Base # 6.0 [2] (✊..✋) raised fist..raised hand
|
||||
270C..270D ; Emoji_Modifier_Base # 1.1 [2] (✌️..✍️) victory hand..writing hand
|
||||
1F385 ; Emoji_Modifier_Base # 6.0 [1] (🎅) Santa Claus
|
||||
1F3C2..1F3C4 ; Emoji_Modifier_Base # 6.0 [3] (🏂..🏄) snowboarder..person surfing
|
||||
1F3C7 ; Emoji_Modifier_Base # 6.0 [1] (🏇) horse racing
|
||||
1F3CA ; Emoji_Modifier_Base # 6.0 [1] (🏊) person swimming
|
||||
1F3CB..1F3CC ; Emoji_Modifier_Base # 7.0 [2] (🏋️..🏌️) person lifting weights..person golfing
|
||||
1F442..1F443 ; Emoji_Modifier_Base # 6.0 [2] (👂..👃) ear..nose
|
||||
1F446..1F450 ; Emoji_Modifier_Base # 6.0 [11] (👆..👐) backhand index pointing up..open hands
|
||||
1F466..1F469 ; Emoji_Modifier_Base # 6.0 [4] (👦..👩) boy..woman
|
||||
1F46E ; Emoji_Modifier_Base # 6.0 [1] (👮) police officer
|
||||
1F470..1F478 ; Emoji_Modifier_Base # 6.0 [9] (👰..👸) bride with veil..princess
|
||||
1F47C ; Emoji_Modifier_Base # 6.0 [1] (👼) baby angel
|
||||
1F481..1F483 ; Emoji_Modifier_Base # 6.0 [3] (💁..💃) person tipping hand..woman dancing
|
||||
1F485..1F487 ; Emoji_Modifier_Base # 6.0 [3] (💅..💇) nail polish..person getting haircut
|
||||
1F4AA ; Emoji_Modifier_Base # 6.0 [1] (💪) flexed biceps
|
||||
1F574..1F575 ; Emoji_Modifier_Base # 7.0 [2] (🕴️..🕵️) man in suit levitating..detective
|
||||
1F57A ; Emoji_Modifier_Base # 9.0 [1] (🕺) man dancing
|
||||
1F590 ; Emoji_Modifier_Base # 7.0 [1] (🖐️) hand with fingers splayed
|
||||
1F595..1F596 ; Emoji_Modifier_Base # 7.0 [2] (🖕..🖖) middle finger..vulcan salute
|
||||
1F645..1F647 ; Emoji_Modifier_Base # 6.0 [3] (🙅..🙇) person gesturing NO..person bowing
|
||||
1F64B..1F64F ; Emoji_Modifier_Base # 6.0 [5] (🙋..🙏) person raising hand..folded hands
|
||||
1F6A3 ; Emoji_Modifier_Base # 6.0 [1] (🚣) person rowing boat
|
||||
1F6B4..1F6B6 ; Emoji_Modifier_Base # 6.0 [3] (🚴..🚶) person biking..person walking
|
||||
1F6C0 ; Emoji_Modifier_Base # 6.0 [1] (🛀) person taking bath
|
||||
1F6CC ; Emoji_Modifier_Base # 7.0 [1] (🛌) person in bed
|
||||
1F918 ; Emoji_Modifier_Base # 8.0 [1] (🤘) sign of the horns
|
||||
1F919..1F91C ; Emoji_Modifier_Base # 9.0 [4] (🤙..🤜) call me hand..right-facing fist
|
||||
1F91E ; Emoji_Modifier_Base # 9.0 [1] (🤞) crossed fingers
|
||||
1F91F ; Emoji_Modifier_Base # 10.0 [1] (🤟) love-you gesture
|
||||
1F926 ; Emoji_Modifier_Base # 9.0 [1] (🤦) person facepalming
|
||||
1F930 ; Emoji_Modifier_Base # 9.0 [1] (🤰) pregnant woman
|
||||
1F931..1F932 ; Emoji_Modifier_Base # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
|
||||
1F933..1F939 ; Emoji_Modifier_Base # 9.0 [7] (🤳..🤹) selfie..person juggling
|
||||
1F93D..1F93E ; Emoji_Modifier_Base # 9.0 [2] (🤽..🤾) person playing water polo..person playing handball
|
||||
1F9D1..1F9DD ; Emoji_Modifier_Base # 10.0 [13] (🧑..🧝) adult..elf
|
||||
|
||||
# Total elements: 102
|
||||
|
||||
# ================================================
|
||||
|
||||
# All omitted code points have Emoji_Component=No
|
||||
# @missing: 0000..10FFFF ; Emoji_Component ; No
|
||||
|
||||
0023 ; Emoji_Component # 1.1 [1] (#️) number sign
|
||||
002A ; Emoji_Component # 1.1 [1] (*️) asterisk
|
||||
0030..0039 ; Emoji_Component # 1.1 [10] (0️..9️) digit zero..digit nine
|
||||
200D ; Emoji_Component # 1.1 [1] () zero width joiner
|
||||
20E3 ; Emoji_Component # 3.0 [1] (⃣) combining enclosing keycap
|
||||
FE0F ; Emoji_Component # 3.2 [1] () VARIATION SELECTOR-16
|
||||
1F1E6..1F1FF ; Emoji_Component # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
|
||||
1F3FB..1F3FF ; Emoji_Component # 8.0 [5] (🏻..🏿) light skin tone..dark skin tone
|
||||
E0020..E007F ; Emoji_Component # 3.1 [96] (..) tag space..cancel tag
|
||||
|
||||
# Total elements: 142
|
||||
|
||||
# ================================================
|
||||
|
||||
# All omitted code points have Extended_Pictographic=No
|
||||
# @missing: 0000..10FFFF ; Extended_Pictographic ; No
|
||||
|
||||
00A9 ; Extended_Pictographic# 1.1 [1] (©️) copyright
|
||||
00AE ; Extended_Pictographic# 1.1 [1] (®️) registered
|
||||
203C ; Extended_Pictographic# 1.1 [1] (‼️) double exclamation mark
|
||||
2049 ; Extended_Pictographic# 3.0 [1] (⁉️) exclamation question mark
|
||||
2122 ; Extended_Pictographic# 1.1 [1] (™️) trade mark
|
||||
2139 ; Extended_Pictographic# 3.0 [1] (ℹ️) information
|
||||
2194..2199 ; Extended_Pictographic# 1.1 [6] (↔️..↙️) left-right arrow..down-left arrow
|
||||
21A9..21AA ; Extended_Pictographic# 1.1 [2] (↩️..↪️) right arrow curving left..left arrow curving right
|
||||
231A..231B ; Extended_Pictographic# 1.1 [2] (⌚..⌛) watch..hourglass done
|
||||
2328 ; Extended_Pictographic# 1.1 [1] (⌨️) keyboard
|
||||
2388 ; Extended_Pictographic# 3.0 [1] (⎈️) HELM SYMBOL
|
||||
23CF ; Extended_Pictographic# 4.0 [1] (⏏️) eject button
|
||||
23E9..23F3 ; Extended_Pictographic# 6.0 [11] (⏩..⏳) fast-forward button..hourglass not done
|
||||
23F8..23FA ; Extended_Pictographic# 7.0 [3] (⏸️..⏺️) pause button..record button
|
||||
24C2 ; Extended_Pictographic# 1.1 [1] (Ⓜ️) circled M
|
||||
25AA..25AB ; Extended_Pictographic# 1.1 [2] (▪️..▫️) black small square..white small square
|
||||
25B6 ; Extended_Pictographic# 1.1 [1] (▶️) play button
|
||||
25C0 ; Extended_Pictographic# 1.1 [1] (◀️) reverse button
|
||||
25FB..25FE ; Extended_Pictographic# 3.2 [4] (◻️..◾) white medium square..black medium-small square
|
||||
2600..2605 ; Extended_Pictographic# 1.1 [6] (☀️..★️) sun..BLACK STAR
|
||||
2607..2612 ; Extended_Pictographic# 1.1 [12] (☇️..☒️) LIGHTNING..BALLOT BOX WITH X
|
||||
2614..2615 ; Extended_Pictographic# 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
|
||||
2616..2617 ; Extended_Pictographic# 3.2 [2] (☖️..☗️) WHITE SHOGI PIECE..BLACK SHOGI PIECE
|
||||
2618 ; Extended_Pictographic# 4.1 [1] (☘️) shamrock
|
||||
2619 ; Extended_Pictographic# 3.0 [1] (☙️) REVERSED ROTATED FLORAL HEART BULLET
|
||||
261A..266F ; Extended_Pictographic# 1.1 [86] (☚️..♯️) BLACK LEFT POINTING INDEX..MUSIC SHARP SIGN
|
||||
2670..2671 ; Extended_Pictographic# 3.0 [2] (♰️..♱️) WEST SYRIAC CROSS..EAST SYRIAC CROSS
|
||||
2672..267D ; Extended_Pictographic# 3.2 [12] (♲️..♽️) UNIVERSAL RECYCLING SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL
|
||||
267E..267F ; Extended_Pictographic# 4.1 [2] (♾️..♿) PERMANENT PAPER SIGN..wheelchair symbol
|
||||
2680..2689 ; Extended_Pictographic# 3.2 [10] (⚀️..⚉️) DIE FACE-1..BLACK CIRCLE WITH TWO WHITE DOTS
|
||||
268A..2691 ; Extended_Pictographic# 4.0 [8] (⚊️..⚑️) MONOGRAM FOR YANG..BLACK FLAG
|
||||
2692..269C ; Extended_Pictographic# 4.1 [11] (⚒️..⚜️) hammer and pick..fleur-de-lis
|
||||
269D ; Extended_Pictographic# 5.1 [1] (⚝️) OUTLINED WHITE STAR
|
||||
269E..269F ; Extended_Pictographic# 5.2 [2] (⚞️..⚟️) THREE LINES CONVERGING RIGHT..THREE LINES CONVERGING LEFT
|
||||
26A0..26A1 ; Extended_Pictographic# 4.0 [2] (⚠️..⚡) warning..high voltage
|
||||
26A2..26B1 ; Extended_Pictographic# 4.1 [16] (⚢️..⚱️) DOUBLED FEMALE SIGN..funeral urn
|
||||
26B2 ; Extended_Pictographic# 5.0 [1] (⚲️) NEUTER
|
||||
26B3..26BC ; Extended_Pictographic# 5.1 [10] (⚳️..⚼️) CERES..SESQUIQUADRATE
|
||||
26BD..26BF ; Extended_Pictographic# 5.2 [3] (⚽..⚿️) soccer ball..SQUARED KEY
|
||||
26C0..26C3 ; Extended_Pictographic# 5.1 [4] (⛀️..⛃️) WHITE DRAUGHTS MAN..BLACK DRAUGHTS KING
|
||||
26C4..26CD ; Extended_Pictographic# 5.2 [10] (⛄..⛍️) snowman without snow..DISABLED CAR
|
||||
26CE ; Extended_Pictographic# 6.0 [1] (⛎) Ophiuchus
|
||||
26CF..26E1 ; Extended_Pictographic# 5.2 [19] (⛏️..⛡️) pick..RESTRICTED LEFT ENTRY-2
|
||||
26E2 ; Extended_Pictographic# 6.0 [1] (⛢️) ASTRONOMICAL SYMBOL FOR URANUS
|
||||
26E3 ; Extended_Pictographic# 5.2 [1] (⛣️) HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE
|
||||
26E4..26E7 ; Extended_Pictographic# 6.0 [4] (⛤️..⛧️) PENTAGRAM..INVERTED PENTAGRAM
|
||||
26E8..26FF ; Extended_Pictographic# 5.2 [24] (⛨️..⛿️) BLACK CROSS ON SHIELD..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE
|
||||
2700 ; Extended_Pictographic# 7.0 [1] (✀️) BLACK SAFETY SCISSORS
|
||||
2701..2704 ; Extended_Pictographic# 1.1 [4] (✁️..✄️) UPPER BLADE SCISSORS..WHITE SCISSORS
|
||||
2705 ; Extended_Pictographic# 6.0 [1] (✅) white heavy check mark
|
||||
2708..2709 ; Extended_Pictographic# 1.1 [2] (✈️..✉️) airplane..envelope
|
||||
270A..270B ; Extended_Pictographic# 6.0 [2] (✊..✋) raised fist..raised hand
|
||||
270C..2712 ; Extended_Pictographic# 1.1 [7] (✌️..✒️) victory hand..black nib
|
||||
2714 ; Extended_Pictographic# 1.1 [1] (✔️) heavy check mark
|
||||
2716 ; Extended_Pictographic# 1.1 [1] (✖️) heavy multiplication x
|
||||
271D ; Extended_Pictographic# 1.1 [1] (✝️) latin cross
|
||||
2721 ; Extended_Pictographic# 1.1 [1] (✡️) star of David
|
||||
2728 ; Extended_Pictographic# 6.0 [1] (✨) sparkles
|
||||
2733..2734 ; Extended_Pictographic# 1.1 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star
|
||||
2744 ; Extended_Pictographic# 1.1 [1] (❄️) snowflake
|
||||
2747 ; Extended_Pictographic# 1.1 [1] (❇️) sparkle
|
||||
274C ; Extended_Pictographic# 6.0 [1] (❌) cross mark
|
||||
274E ; Extended_Pictographic# 6.0 [1] (❎) cross mark button
|
||||
2753..2755 ; Extended_Pictographic# 6.0 [3] (❓..❕) question mark..white exclamation mark
|
||||
2757 ; Extended_Pictographic# 5.2 [1] (❗) exclamation mark
|
||||
2763..2767 ; Extended_Pictographic# 1.1 [5] (❣️..❧️) heavy heart exclamation..ROTATED FLORAL HEART BULLET
|
||||
2795..2797 ; Extended_Pictographic# 6.0 [3] (➕..➗) heavy plus sign..heavy division sign
|
||||
27A1 ; Extended_Pictographic# 1.1 [1] (➡️) right arrow
|
||||
27B0 ; Extended_Pictographic# 6.0 [1] (➰) curly loop
|
||||
27BF ; Extended_Pictographic# 6.0 [1] (➿) double curly loop
|
||||
2934..2935 ; Extended_Pictographic# 3.2 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down
|
||||
2B05..2B07 ; Extended_Pictographic# 4.0 [3] (⬅️..⬇️) left arrow..down arrow
|
||||
2B1B..2B1C ; Extended_Pictographic# 5.1 [2] (⬛..⬜) black large square..white large square
|
||||
2B50 ; Extended_Pictographic# 5.1 [1] (⭐) white medium star
|
||||
2B55 ; Extended_Pictographic# 5.2 [1] (⭕) heavy large circle
|
||||
3030 ; Extended_Pictographic# 1.1 [1] (〰️) wavy dash
|
||||
303D ; Extended_Pictographic# 3.2 [1] (〽️) part alternation mark
|
||||
3297 ; Extended_Pictographic# 1.1 [1] (㊗️) Japanese “congratulations” button
|
||||
3299 ; Extended_Pictographic# 1.1 [1] (㊙️) Japanese “secret” button
|
||||
1F000..1F02B ; Extended_Pictographic# 5.1 [44] (🀀️..🀫️) MAHJONG TILE EAST WIND..MAHJONG TILE BACK
|
||||
1F02C..1F02F ; Extended_Pictographic# 10.0 [4] (️..️) <reserved-1F02C>..<reserved-1F02F>
|
||||
1F030..1F093 ; Extended_Pictographic# 5.1[100] (🀰️..🂓️) DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06
|
||||
1F094..1F09F ; Extended_Pictographic# 10.0 [12] (️..️) <reserved-1F094>..<reserved-1F09F>
|
||||
1F0A0..1F0AE ; Extended_Pictographic# 6.0 [15] (🂠️..🂮️) PLAYING CARD BACK..PLAYING CARD KING OF SPADES
|
||||
1F0AF..1F0B0 ; Extended_Pictographic# 10.0 [2] (️..️) <reserved-1F0AF>..<reserved-1F0B0>
|
||||
1F0B1..1F0BE ; Extended_Pictographic# 6.0 [14] (🂱️..🂾️) PLAYING CARD ACE OF HEARTS..PLAYING CARD KING OF HEARTS
|
||||
1F0BF ; Extended_Pictographic# 7.0 [1] (🂿️) PLAYING CARD RED JOKER
|
||||
1F0C0 ; Extended_Pictographic# 10.0 [1] (️) <reserved-1F0C0>
|
||||
1F0C1..1F0CF ; Extended_Pictographic# 6.0 [15] (🃁️..🃏) PLAYING CARD ACE OF DIAMONDS..joker
|
||||
1F0D0 ; Extended_Pictographic# 10.0 [1] (️) <reserved-1F0D0>
|
||||
1F0D1..1F0DF ; Extended_Pictographic# 6.0 [15] (🃑️..🃟️) PLAYING CARD ACE OF CLUBS..PLAYING CARD WHITE JOKER
|
||||
1F0E0..1F0F5 ; Extended_Pictographic# 7.0 [22] (🃠️..🃵️) PLAYING CARD FOOL..PLAYING CARD TRUMP-21
|
||||
1F0F6..1F0FF ; Extended_Pictographic# 10.0 [10] (️..️) <reserved-1F0F6>..<reserved-1F0FF>
|
||||
1F10D..1F10F ; Extended_Pictographic# 10.0 [3] (🄍️..🄏️) <reserved-1F10D>..<reserved-1F10F>
|
||||
1F12F ; Extended_Pictographic# 10.0 [1] (🄯️) <reserved-1F12F>
|
||||
1F16C..1F16F ; Extended_Pictographic# 10.0 [4] (🅬️..🅯️) <reserved-1F16C>..<reserved-1F16F>
|
||||
1F170..1F171 ; Extended_Pictographic# 6.0 [2] (🅰️..🅱️) A button (blood type)..B button (blood type)
|
||||
1F17E ; Extended_Pictographic# 6.0 [1] (🅾️) O button (blood type)
|
||||
1F17F ; Extended_Pictographic# 5.2 [1] (🅿️) P button
|
||||
1F18E ; Extended_Pictographic# 6.0 [1] (🆎) AB button (blood type)
|
||||
1F191..1F19A ; Extended_Pictographic# 6.0 [10] (🆑..🆚) CL button..VS button
|
||||
1F1AD..1F1E5 ; Extended_Pictographic# 10.0 [57] (🆭️..️) <reserved-1F1AD>..<reserved-1F1E5>
|
||||
1F201..1F202 ; Extended_Pictographic# 6.0 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button
|
||||
1F203..1F20F ; Extended_Pictographic# 10.0 [13] (️..️) <reserved-1F203>..<reserved-1F20F>
|
||||
1F21A ; Extended_Pictographic# 5.2 [1] (🈚) Japanese “free of charge” button
|
||||
1F22F ; Extended_Pictographic# 5.2 [1] (🈯) Japanese “reserved” button
|
||||
1F232..1F23A ; Extended_Pictographic# 6.0 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button
|
||||
1F23C..1F23F ; Extended_Pictographic# 10.0 [4] (️..️) <reserved-1F23C>..<reserved-1F23F>
|
||||
1F249..1F24F ; Extended_Pictographic# 10.0 [7] (️..️) <reserved-1F249>..<reserved-1F24F>
|
||||
1F250..1F251 ; Extended_Pictographic# 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
|
||||
1F252..1F25F ; Extended_Pictographic# 10.0 [14] (️..️) <reserved-1F252>..<reserved-1F25F>
|
||||
1F260..1F265 ; Extended_Pictographic# 10.0 [6] (🉠️..🉥️) ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI
|
||||
1F266..1F2FF ; Extended_Pictographic# 10.0[154] (️..️) <reserved-1F266>..<reserved-1F2FF>
|
||||
1F300..1F320 ; Extended_Pictographic# 6.0 [33] (🌀..🌠) cyclone..shooting star
|
||||
1F321..1F32C ; Extended_Pictographic# 7.0 [12] (🌡️..🌬️) thermometer..wind face
|
||||
1F32D..1F32F ; Extended_Pictographic# 8.0 [3] (🌭..🌯) hot dog..burrito
|
||||
1F330..1F335 ; Extended_Pictographic# 6.0 [6] (🌰..🌵) chestnut..cactus
|
||||
1F336 ; Extended_Pictographic# 7.0 [1] (🌶️) hot pepper
|
||||
1F337..1F37C ; Extended_Pictographic# 6.0 [70] (🌷..🍼) tulip..baby bottle
|
||||
1F37D ; Extended_Pictographic# 7.0 [1] (🍽️) fork and knife with plate
|
||||
1F37E..1F37F ; Extended_Pictographic# 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn
|
||||
1F380..1F393 ; Extended_Pictographic# 6.0 [20] (🎀..🎓) ribbon..graduation cap
|
||||
1F394..1F39F ; Extended_Pictographic# 7.0 [12] (🎔️..🎟️) HEART WITH TIP ON THE LEFT..admission tickets
|
||||
1F3A0..1F3C4 ; Extended_Pictographic# 6.0 [37] (🎠..🏄) carousel horse..person surfing
|
||||
1F3C5 ; Extended_Pictographic# 7.0 [1] (🏅) sports medal
|
||||
1F3C6..1F3CA ; Extended_Pictographic# 6.0 [5] (🏆..🏊) trophy..person swimming
|
||||
1F3CB..1F3CE ; Extended_Pictographic# 7.0 [4] (🏋️..🏎️) person lifting weights..racing car
|
||||
1F3CF..1F3D3 ; Extended_Pictographic# 8.0 [5] (🏏..🏓) cricket game..ping pong
|
||||
1F3D4..1F3DF ; Extended_Pictographic# 7.0 [12] (🏔️..🏟️) snow-capped mountain..stadium
|
||||
1F3E0..1F3F0 ; Extended_Pictographic# 6.0 [17] (🏠..🏰) house..castle
|
||||
1F3F1..1F3F7 ; Extended_Pictographic# 7.0 [7] (🏱️..🏷️) WHITE PENNANT..label
|
||||
1F3F8..1F3FA ; Extended_Pictographic# 8.0 [3] (🏸..🏺) badminton..amphora
|
||||
1F400..1F43E ; Extended_Pictographic# 6.0 [63] (🐀..🐾) rat..paw prints
|
||||
1F43F ; Extended_Pictographic# 7.0 [1] (🐿️) chipmunk
|
||||
1F440 ; Extended_Pictographic# 6.0 [1] (👀) eyes
|
||||
1F441 ; Extended_Pictographic# 7.0 [1] (👁️) eye
|
||||
1F442..1F4F7 ; Extended_Pictographic# 6.0[182] (👂..📷) ear..camera
|
||||
1F4F8 ; Extended_Pictographic# 7.0 [1] (📸) camera with flash
|
||||
1F4F9..1F4FC ; Extended_Pictographic# 6.0 [4] (📹..📼) video camera..videocassette
|
||||
1F4FD..1F4FE ; Extended_Pictographic# 7.0 [2] (📽️..📾️) film projector..PORTABLE STEREO
|
||||
1F4FF ; Extended_Pictographic# 8.0 [1] (📿) prayer beads
|
||||
1F500..1F53D ; Extended_Pictographic# 6.0 [62] (🔀..🔽) shuffle tracks button..down button
|
||||
1F53E..1F53F ; Extended_Pictographic# 7.0 [2] (🔾️..🔿️) LOWER RIGHT SHADOWED WHITE CIRCLE..UPPER RIGHT SHADOWED WHITE CIRCLE
|
||||
1F540..1F543 ; Extended_Pictographic# 6.1 [4] (🕀️..🕃️) CIRCLED CROSS POMMEE..NOTCHED LEFT SEMICIRCLE WITH THREE DOTS
|
||||
1F544..1F54A ; Extended_Pictographic# 7.0 [7] (🕄️..🕊️) NOTCHED RIGHT SEMICIRCLE WITH THREE DOTS..dove
|
||||
1F54B..1F54F ; Extended_Pictographic# 8.0 [5] (🕋..🕏️) kaaba..BOWL OF HYGIEIA
|
||||
1F550..1F567 ; Extended_Pictographic# 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty
|
||||
1F568..1F579 ; Extended_Pictographic# 7.0 [18] (🕨️..🕹️) RIGHT SPEAKER..joystick
|
||||
1F57A ; Extended_Pictographic# 9.0 [1] (🕺) man dancing
|
||||
1F57B..1F5A3 ; Extended_Pictographic# 7.0 [41] (🕻️..🖣️) LEFT HAND TELEPHONE RECEIVER..BLACK DOWN POINTING BACKHAND INDEX
|
||||
1F5A4 ; Extended_Pictographic# 9.0 [1] (🖤) black heart
|
||||
1F5A5..1F5FA ; Extended_Pictographic# 7.0 [86] (🖥️..🗺️) desktop computer..world map
|
||||
1F5FB..1F5FF ; Extended_Pictographic# 6.0 [5] (🗻..🗿) mount fuji..moai
|
||||
1F600 ; Extended_Pictographic# 6.1 [1] (😀) grinning face
|
||||
1F601..1F610 ; Extended_Pictographic# 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face
|
||||
1F611 ; Extended_Pictographic# 6.1 [1] (😑) expressionless face
|
||||
1F612..1F614 ; Extended_Pictographic# 6.0 [3] (😒..😔) unamused face..pensive face
|
||||
1F615 ; Extended_Pictographic# 6.1 [1] (😕) confused face
|
||||
1F616 ; Extended_Pictographic# 6.0 [1] (😖) confounded face
|
||||
1F617 ; Extended_Pictographic# 6.1 [1] (😗) kissing face
|
||||
1F618 ; Extended_Pictographic# 6.0 [1] (😘) face blowing a kiss
|
||||
1F619 ; Extended_Pictographic# 6.1 [1] (😙) kissing face with smiling eyes
|
||||
1F61A ; Extended_Pictographic# 6.0 [1] (😚) kissing face with closed eyes
|
||||
1F61B ; Extended_Pictographic# 6.1 [1] (😛) face with tongue
|
||||
1F61C..1F61E ; Extended_Pictographic# 6.0 [3] (😜..😞) winking face with tongue..disappointed face
|
||||
1F61F ; Extended_Pictographic# 6.1 [1] (😟) worried face
|
||||
1F620..1F625 ; Extended_Pictographic# 6.0 [6] (😠..😥) angry face..sad but relieved face
|
||||
1F626..1F627 ; Extended_Pictographic# 6.1 [2] (😦..😧) frowning face with open mouth..anguished face
|
||||
1F628..1F62B ; Extended_Pictographic# 6.0 [4] (😨..😫) fearful face..tired face
|
||||
1F62C ; Extended_Pictographic# 6.1 [1] (😬) grimacing face
|
||||
1F62D ; Extended_Pictographic# 6.0 [1] (😭) loudly crying face
|
||||
1F62E..1F62F ; Extended_Pictographic# 6.1 [2] (😮..😯) face with open mouth..hushed face
|
||||
1F630..1F633 ; Extended_Pictographic# 6.0 [4] (😰..😳) anxious face with sweat..flushed face
|
||||
1F634 ; Extended_Pictographic# 6.1 [1] (😴) sleeping face
|
||||
1F635..1F640 ; Extended_Pictographic# 6.0 [12] (😵..🙀) dizzy face..weary cat face
|
||||
1F641..1F642 ; Extended_Pictographic# 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
|
||||
1F643..1F644 ; Extended_Pictographic# 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
|
||||
1F645..1F64F ; Extended_Pictographic# 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
|
||||
1F680..1F6C5 ; Extended_Pictographic# 6.0 [70] (🚀..🛅) rocket..left luggage
|
||||
1F6C6..1F6CF ; Extended_Pictographic# 7.0 [10] (🛆️..🛏️) TRIANGLE WITH ROUNDED CORNERS..bed
|
||||
1F6D0 ; Extended_Pictographic# 8.0 [1] (🛐) place of worship
|
||||
1F6D1..1F6D2 ; Extended_Pictographic# 9.0 [2] (🛑..🛒) stop sign..shopping cart
|
||||
1F6D3..1F6D4 ; Extended_Pictographic# 10.0 [2] (🛓️..🛔️) STUPA..PAGODA
|
||||
1F6D5..1F6DF ; Extended_Pictographic# 10.0 [11] (🛕️..🛟️) <reserved-1F6D5>..<reserved-1F6DF>
|
||||
1F6E0..1F6EC ; Extended_Pictographic# 7.0 [13] (🛠️..🛬) hammer and wrench..airplane arrival
|
||||
1F6ED..1F6EF ; Extended_Pictographic# 10.0 [3] (️..️) <reserved-1F6ED>..<reserved-1F6EF>
|
||||
1F6F0..1F6F3 ; Extended_Pictographic# 7.0 [4] (🛰️..🛳️) satellite..passenger ship
|
||||
1F6F4..1F6F6 ; Extended_Pictographic# 9.0 [3] (🛴..🛶) kick scooter..canoe
|
||||
1F6F7..1F6F8 ; Extended_Pictographic# 10.0 [2] (🛷..🛸) sled..flying saucer
|
||||
1F6F9..1F6FF ; Extended_Pictographic# 10.0 [7] (🛹️..️) <reserved-1F6F9>..<reserved-1F6FF>
|
||||
1F774..1F77F ; Extended_Pictographic# 10.0 [12] (🝴️..🝿️) <reserved-1F774>..<reserved-1F77F>
|
||||
1F7D5..1F7FF ; Extended_Pictographic# 10.0 [43] (🟕️..️) <reserved-1F7D5>..<reserved-1F7FF>
|
||||
1F80C..1F80F ; Extended_Pictographic# 10.0 [4] (️..️) <reserved-1F80C>..<reserved-1F80F>
|
||||
1F848..1F84F ; Extended_Pictographic# 10.0 [8] (️..️) <reserved-1F848>..<reserved-1F84F>
|
||||
1F85A..1F85F ; Extended_Pictographic# 10.0 [6] (️..️) <reserved-1F85A>..<reserved-1F85F>
|
||||
1F888..1F88F ; Extended_Pictographic# 10.0 [8] (️..️) <reserved-1F888>..<reserved-1F88F>
|
||||
1F8AE..1F8FF ; Extended_Pictographic# 10.0 [82] (️..️) <reserved-1F8AE>..<reserved-1F8FF>
|
||||
1F900..1F90B ; Extended_Pictographic# 10.0 [12] (🤀️..🤋️) CIRCLED CROSS FORMEE WITH FOUR DOTS..DOWNWARD FACING NOTCHED HOOK WITH DOT
|
||||
1F90C..1F90F ; Extended_Pictographic# 10.0 [4] (🤌️..🤏️) <reserved-1F90C>..<reserved-1F90F>
|
||||
1F910..1F918 ; Extended_Pictographic# 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
|
||||
1F919..1F91E ; Extended_Pictographic# 9.0 [6] (🤙..🤞) call me hand..crossed fingers
|
||||
1F91F ; Extended_Pictographic# 10.0 [1] (🤟) love-you gesture
|
||||
1F920..1F927 ; Extended_Pictographic# 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face
|
||||
1F928..1F92F ; Extended_Pictographic# 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
|
||||
1F930 ; Extended_Pictographic# 9.0 [1] (🤰) pregnant woman
|
||||
1F931..1F932 ; Extended_Pictographic# 10.0 [2] (🤱..🤲) breast-feeding..palms up together
|
||||
1F933..1F93A ; Extended_Pictographic# 9.0 [8] (🤳..🤺) selfie..person fencing
|
||||
1F93C..1F93E ; Extended_Pictographic# 9.0 [3] (🤼..🤾) people wrestling..person playing handball
|
||||
1F93F ; Extended_Pictographic# 10.0 [1] (🤿️) <reserved-1F93F>
|
||||
1F940..1F945 ; Extended_Pictographic# 9.0 [6] (🥀..🥅) wilted flower..goal net
|
||||
1F947..1F94B ; Extended_Pictographic# 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
|
||||
1F94C ; Extended_Pictographic# 10.0 [1] (🥌) curling stone
|
||||
1F94D..1F94F ; Extended_Pictographic# 10.0 [3] (🥍️..🥏️) <reserved-1F94D>..<reserved-1F94F>
|
||||
1F950..1F95E ; Extended_Pictographic# 9.0 [15] (🥐..🥞) croissant..pancakes
|
||||
1F95F..1F96B ; Extended_Pictographic# 10.0 [13] (🥟..🥫) dumpling..canned food
|
||||
1F96C..1F97F ; Extended_Pictographic# 10.0 [20] (🥬️..🥿️) <reserved-1F96C>..<reserved-1F97F>
|
||||
1F980..1F984 ; Extended_Pictographic# 8.0 [5] (🦀..🦄) crab..unicorn face
|
||||
1F985..1F991 ; Extended_Pictographic# 9.0 [13] (🦅..🦑) eagle..squid
|
||||
1F992..1F997 ; Extended_Pictographic# 10.0 [6] (🦒..🦗) giraffe..cricket
|
||||
1F998..1F9BF ; Extended_Pictographic# 10.0 [40] (🦘️..🦿️) <reserved-1F998>..<reserved-1F9BF>
|
||||
1F9C0 ; Extended_Pictographic# 8.0 [1] (🧀) cheese wedge
|
||||
1F9C1..1F9CF ; Extended_Pictographic# 10.0 [15] (🧁️..🧏️) <reserved-1F9C1>..<reserved-1F9CF>
|
||||
1F9D0..1F9E6 ; Extended_Pictographic# 10.0 [23] (🧐..🧦) face with monocle..socks
|
||||
1F9E7..1FFFD ; Extended_Pictographic# 10.0[1559] (🧧️..️) <reserved-1F9E7>..<reserved-1FFFD>
|
||||
|
||||
# Total elements: 3823
|
||||
|
||||
#EOF
|
140
Scripts/emoji_ranges.py
Executable file
140
Scripts/emoji_ranges.py
Executable file
|
@ -0,0 +1,140 @@
|
|||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import io
|
||||
|
||||
|
||||
def fail(message):
|
||||
print(message)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# For simplicity and compactness, we pre-define the
|
||||
# emoji code planes to ensure that all of the currently-used
|
||||
# emoji ranges within them are combined.
|
||||
big_ranges = [
|
||||
(
|
||||
0x1F600,
|
||||
0x1F64F,
|
||||
),
|
||||
(
|
||||
0x1F300,
|
||||
0x1F5FF,
|
||||
),
|
||||
(
|
||||
0x1F680,
|
||||
0x1F6FF,
|
||||
),
|
||||
(
|
||||
0x2600,
|
||||
0x26FF,
|
||||
),
|
||||
(
|
||||
0x2700,
|
||||
0x27BF,
|
||||
),
|
||||
(
|
||||
0xFE00,
|
||||
0xFE0F,
|
||||
),
|
||||
(
|
||||
0x1F900,
|
||||
0x1F9FF,
|
||||
),
|
||||
(
|
||||
65024,
|
||||
65039,
|
||||
),
|
||||
(
|
||||
8400,
|
||||
8447,
|
||||
),
|
||||
]
|
||||
|
||||
if __name__ == "__main__":
|
||||
src_filename = "emoji-data.txt"
|
||||
src_dir_path = os.path.dirname(__file__)
|
||||
src_file_path = os.path.join(src_dir_path, src_filename)
|
||||
print("src_file_path", src_file_path)
|
||||
if not os.path.exists(src_file_path):
|
||||
fail("Could not find input file")
|
||||
|
||||
with io.open(src_file_path, "r", encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
|
||||
lines = text.split("\n")
|
||||
raw_ranges = []
|
||||
for line in lines:
|
||||
if "#" in line:
|
||||
line = line[: line.index("#")].strip()
|
||||
if ";" not in line:
|
||||
continue
|
||||
print("line:", line)
|
||||
range_text = line[: line.index(";")]
|
||||
print("\t:", range_text)
|
||||
if ".." in range_text:
|
||||
range_start_hex_string, range_end_hex_string = range_text.split("..")
|
||||
else:
|
||||
range_start_hex_string = range_end_hex_string = range_text.strip()
|
||||
range_start = int(range_start_hex_string.strip(), 16)
|
||||
range_end = int(range_end_hex_string.strip(), 16)
|
||||
print("\t", range_start, range_end)
|
||||
|
||||
raw_ranges.append(
|
||||
(
|
||||
range_start,
|
||||
range_end,
|
||||
)
|
||||
)
|
||||
|
||||
raw_ranges += big_ranges
|
||||
|
||||
raw_ranges.sort(key=lambda a: a[0])
|
||||
|
||||
new_ranges = []
|
||||
for range_start, range_end in raw_ranges:
|
||||
if len(new_ranges) > 0:
|
||||
last_range = new_ranges[-1]
|
||||
# print 'last_range', last_range
|
||||
last_range_start, last_range_end = last_range
|
||||
if range_start >= last_range_start and range_start <= last_range_end + 1:
|
||||
# if last_range_end + 1 == range_start:
|
||||
new_ranges = new_ranges[:-1]
|
||||
print(
|
||||
"merging",
|
||||
last_range_start,
|
||||
last_range_end,
|
||||
"and",
|
||||
range_start,
|
||||
range_end,
|
||||
)
|
||||
new_ranges.append(
|
||||
(
|
||||
last_range_start,
|
||||
max(range_end, last_range_end),
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
new_ranges.append(
|
||||
(
|
||||
range_start,
|
||||
range_end,
|
||||
)
|
||||
)
|
||||
|
||||
print()
|
||||
for range_start, range_end in new_ranges:
|
||||
# print '0x%X...0x%X, // %d Emotions' % (range_start, range_end, (1 + range_end - range_start), )
|
||||
print(
|
||||
"EmojiRange(rangeStart:0x%X, rangeEnd:0x%X),"
|
||||
% (
|
||||
range_start,
|
||||
range_end,
|
||||
)
|
||||
)
|
||||
print("new_ranges:", len(new_ranges))
|
||||
print()
|
||||
print("Copy and paste the code above into DisplayableText.swift")
|
||||
print()
|
5
Scripts/feature_flags_beta.py
Executable file
5
Scripts/feature_flags_beta.py
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
import feature_flags_common
|
||||
|
||||
if __name__ == "__main__":
|
||||
feature_flags_common.set_feature_flags("beta")
|
58
Scripts/feature_flags_common.py
Executable file
58
Scripts/feature_flags_common.py
Executable file
|
@ -0,0 +1,58 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
FILE_PATH = "SignalServiceKit/Util/FeatureFlags+Generated.swift"
|
||||
|
||||
|
||||
def run(args):
|
||||
subprocess.run(args, check=True)
|
||||
|
||||
|
||||
def capture(args):
|
||||
return subprocess.run(args, check=True, capture_output=True, encoding="utf8").stdout
|
||||
|
||||
|
||||
def generate(level):
|
||||
return """//
|
||||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension FeatureBuild {
|
||||
#if DEBUG
|
||||
static let current: FeatureBuild = .dev
|
||||
#else
|
||||
static let current: FeatureBuild = .{level}
|
||||
#endif
|
||||
}
|
||||
""".replace(
|
||||
"{level}", str(level)
|
||||
)
|
||||
|
||||
|
||||
def set_feature_flags(new_flags_level):
|
||||
output = capture(["git", "status", "--porcelain"]).rstrip()
|
||||
if len(output) > 0:
|
||||
print(output)
|
||||
print("Repository has uncommitted changes.")
|
||||
exit(1)
|
||||
|
||||
new_value = generate(new_flags_level)
|
||||
with open(FILE_PATH, "r") as file:
|
||||
old_value = file.read()
|
||||
|
||||
if new_value == old_value:
|
||||
print(f"Feature flags already set to {new_flags_level}; nothing to do")
|
||||
exit(0)
|
||||
|
||||
with open(FILE_PATH, "w") as file:
|
||||
file.write(new_value)
|
||||
|
||||
run(["git", "add", FILE_PATH])
|
||||
run(["git", "commit", "-m", f"Feature flags for .{new_flags_level}."])
|
10
Scripts/feature_flags_internal.py
Executable file
10
Scripts/feature_flags_internal.py
Executable file
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env python3
|
||||
import feature_flags_common
|
||||
|
||||
|
||||
def main():
|
||||
feature_flags_common.set_feature_flags("internal")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
5
Scripts/feature_flags_production.py
Executable file
5
Scripts/feature_flags_production.py
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
import feature_flags_common
|
||||
|
||||
if __name__ == "__main__":
|
||||
feature_flags_common.set_feature_flags("production")
|
12
Scripts/feature_flags_qa.py
Executable file
12
Scripts/feature_flags_qa.py
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env python3
|
||||
from sys import stderr
|
||||
from time import sleep
|
||||
from feature_flags_internal import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("❗️ feature_flags_qa.py is deprecated.", file=stderr)
|
||||
print("Waiting a moment to make sure you see this message...", file=stderr)
|
||||
|
||||
sleep(3)
|
||||
|
||||
main()
|
1
Scripts/git_hooks/README.md
Normal file
1
Scripts/git_hooks/README.md
Normal file
|
@ -0,0 +1 @@
|
|||
Copy these git hooks into .git/hooks
|
3
Scripts/git_hooks/pre-commit
Executable file
3
Scripts/git_hooks/pre-commit
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
Scripts/precommit.py
|
121
Scripts/lint/lint-license-headers
Executable file
121
Scripts/lint/lint-license-headers
Executable file
|
@ -0,0 +1,121 @@
|
|||
#!/usr/bin/env python3
|
||||
from functools import cache
|
||||
from pathlib import Path
|
||||
from tempfile import mkstemp
|
||||
from typing import Iterable
|
||||
import argparse
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from util import EXTENSIONS_TO_CHECK
|
||||
|
||||
|
||||
COPYRIGHT_LINE_RE = re.compile(r"^// Copyright 2\d\d\d Signal Messenger, LLC\n$")
|
||||
SPDX_LINE = "// SPDX-License-Identifier: AGPL-3.0-only\n"
|
||||
|
||||
|
||||
def read_first_lines(line_count: int, path: Path) -> list[str]:
|
||||
if line_count == 0:
|
||||
return ""
|
||||
result = []
|
||||
with open(path, "rt", encoding="utf8") as file:
|
||||
for line in file:
|
||||
result.append(line)
|
||||
if len(result) >= line_count:
|
||||
break
|
||||
return result
|
||||
|
||||
|
||||
def has_shebang(line: str) -> bool:
|
||||
return line.startswith("#!")
|
||||
|
||||
|
||||
def has_swift_tools_version(line: str) -> bool:
|
||||
return line.startswith("// swift-tools-version:")
|
||||
|
||||
|
||||
def is_first_line_important(line: str) -> bool:
|
||||
return has_shebang(line) or has_swift_tools_version(line)
|
||||
|
||||
|
||||
def has_valid_license_header(path: Path) -> bool:
|
||||
first_six_lines = read_first_lines(6, path)
|
||||
if is_first_line_important(first_six_lines[0]):
|
||||
lines_to_consider = first_six_lines[1:]
|
||||
else:
|
||||
lines_to_consider = first_six_lines[:-1]
|
||||
return (
|
||||
(lines_to_consider[0] == "//\n")
|
||||
and (COPYRIGHT_LINE_RE.match(lines_to_consider[1]) is not None)
|
||||
and (lines_to_consider[2] == SPDX_LINE)
|
||||
and (lines_to_consider[3] == "//\n")
|
||||
and (lines_to_consider[4] == "\n")
|
||||
)
|
||||
|
||||
|
||||
@cache
|
||||
def get_staging_file_path() -> Path:
|
||||
path_str = mkstemp(prefix="signal-ios-license-staging-file-")[1]
|
||||
return Path(path_str)
|
||||
|
||||
|
||||
def get_creation_year_for(path: Path) -> str:
|
||||
dates_changed = subprocess.check_output(
|
||||
["git", "log", "--follow", "--format=%as", "--date=short", path.resolve()],
|
||||
text=True,
|
||||
).splitlines()
|
||||
return dates_changed[-1][0:4]
|
||||
|
||||
|
||||
def lines_with_license_header_added(path: Path) -> Iterable[str]:
|
||||
with open(path, "rt", encoding="utf8") as source_file:
|
||||
for index, line in enumerate(source_file):
|
||||
if index == 0:
|
||||
if is_first_line_important(line):
|
||||
yield line
|
||||
yield "//\n"
|
||||
yield f"// Copyright {get_creation_year_for(path)} Signal Messenger, LLC\n"
|
||||
yield SPDX_LINE
|
||||
yield "//\n"
|
||||
yield "\n"
|
||||
if not is_first_line_important(line):
|
||||
yield line
|
||||
else:
|
||||
yield line
|
||||
|
||||
|
||||
def add_license_header_to(path: Path) -> None:
|
||||
staging_file_path = get_staging_file_path()
|
||||
with open(staging_file_path, "wt", encoding="utf8") as staging_file:
|
||||
for line in lines_with_license_header_added(path):
|
||||
staging_file.write(line)
|
||||
staging_file_path.replace(path)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Check license headers across the project"
|
||||
)
|
||||
parser.add_argument("path", nargs="*", help="A path to process")
|
||||
parser.add_argument(
|
||||
"--fix", action="store_true", help="Attempt to auto-add license headers"
|
||||
)
|
||||
ns = parser.parse_args()
|
||||
should_fix = ns.fix
|
||||
|
||||
all_good = True
|
||||
for path in ns.path:
|
||||
path = Path(path)
|
||||
if not has_valid_license_header(path):
|
||||
if should_fix:
|
||||
add_license_header_to(path)
|
||||
else:
|
||||
print(f"{path} has an invalid license header", file=sys.stderr)
|
||||
all_good = False
|
||||
|
||||
if not all_good:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
3
Scripts/lint/util.py
Normal file
3
Scripts/lint/util.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
EXTENSIONS_TO_CHECK = set(
|
||||
(".h", ".hpp", ".cpp", ".m", ".mm", ".pch", ".swift", ".proto")
|
||||
)
|
256
Scripts/precommit.py
Executable file
256
Scripts/precommit.py
Executable file
|
@ -0,0 +1,256 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import argparse
|
||||
from typing import Iterable
|
||||
from pathlib import Path
|
||||
from lint.util import EXTENSIONS_TO_CHECK
|
||||
|
||||
CLANG_FORMAT_EXTS = set([".m", ".mm", ".h"])
|
||||
|
||||
|
||||
def sort_forward_decl_statement_block(text):
|
||||
lines = text.split("\n")
|
||||
lines = [line.strip() for line in lines if line.strip()]
|
||||
lines = list(set(lines))
|
||||
lines.sort()
|
||||
return "\n" + "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def find_matching_section(text, match_test):
|
||||
lines = text.split("\n")
|
||||
first_matching_line_index = None
|
||||
for index, line in enumerate(lines):
|
||||
if match_test(line):
|
||||
first_matching_line_index = index
|
||||
break
|
||||
|
||||
if first_matching_line_index is None:
|
||||
return None
|
||||
|
||||
# Absorb any leading empty lines.
|
||||
while first_matching_line_index > 0:
|
||||
prev_line = lines[first_matching_line_index - 1]
|
||||
if prev_line.strip() != "":
|
||||
break
|
||||
first_matching_line_index = first_matching_line_index - 1
|
||||
|
||||
first_non_matching_line_index = None
|
||||
for index, line in enumerate(lines[first_matching_line_index:]):
|
||||
if line.strip() == "":
|
||||
# Absorb any trailing empty lines.
|
||||
continue
|
||||
if not match_test(line):
|
||||
first_non_matching_line_index = index + first_matching_line_index
|
||||
break
|
||||
|
||||
text0 = "\n".join(lines[:first_matching_line_index])
|
||||
if first_non_matching_line_index is None:
|
||||
text1 = "\n".join(lines[first_matching_line_index:])
|
||||
text2 = None
|
||||
else:
|
||||
text1 = "\n".join(
|
||||
lines[first_matching_line_index:first_non_matching_line_index]
|
||||
)
|
||||
text2 = "\n".join(lines[first_non_matching_line_index:])
|
||||
|
||||
return text0, text1, text2
|
||||
|
||||
|
||||
def sort_matching_blocks(sort_name, filepath, text, match_func, sort_func):
|
||||
unprocessed = text
|
||||
processed = None
|
||||
while True:
|
||||
section = find_matching_section(unprocessed, match_func)
|
||||
if not section:
|
||||
if processed:
|
||||
processed = "\n".join((processed, unprocessed))
|
||||
else:
|
||||
processed = unprocessed
|
||||
break
|
||||
|
||||
text0, text1, text2 = section
|
||||
|
||||
if processed:
|
||||
processed = "\n".join((processed, text0))
|
||||
else:
|
||||
processed = text0
|
||||
|
||||
text1 = sort_func(text1)
|
||||
processed = "\n".join((processed, text1))
|
||||
if text2:
|
||||
unprocessed = text2
|
||||
else:
|
||||
break
|
||||
|
||||
if text != processed:
|
||||
print(sort_name, filepath)
|
||||
return processed
|
||||
|
||||
|
||||
def find_forward_class_statement_section(text):
|
||||
def is_forward_class_statement(line):
|
||||
return line.strip().startswith("@class ")
|
||||
|
||||
return find_matching_section(text, is_forward_class_statement)
|
||||
|
||||
|
||||
def find_forward_protocol_statement_section(text):
|
||||
def is_forward_protocol_statement(line):
|
||||
return line.strip().startswith("@protocol ") and line.strip().endswith(";")
|
||||
|
||||
return find_matching_section(text, is_forward_protocol_statement)
|
||||
|
||||
|
||||
def sort_forward_class_statements(filepath, file_extension, text):
|
||||
if file_extension not in (".h", ".m", ".mm"):
|
||||
return text
|
||||
return sort_matching_blocks(
|
||||
"sort_class_statements",
|
||||
filepath,
|
||||
text,
|
||||
find_forward_class_statement_section,
|
||||
sort_forward_decl_statement_block,
|
||||
)
|
||||
|
||||
|
||||
def sort_forward_protocol_statements(filepath, file_extension, text):
|
||||
if file_extension not in (".h", ".m", ".mm"):
|
||||
return text
|
||||
return sort_matching_blocks(
|
||||
"sort_forward_protocol_statements",
|
||||
filepath,
|
||||
text,
|
||||
find_forward_protocol_statement_section,
|
||||
sort_forward_decl_statement_block,
|
||||
)
|
||||
|
||||
|
||||
def get_ext(file: str) -> str:
|
||||
return os.path.splitext(file)[1]
|
||||
|
||||
|
||||
def process(filepath):
|
||||
file_ext = get_ext(filepath)
|
||||
|
||||
with open(filepath, "rt") as f:
|
||||
text = f.read()
|
||||
|
||||
original_text = text
|
||||
|
||||
text = sort_forward_class_statements(filepath, file_ext, text)
|
||||
text = sort_forward_protocol_statements(filepath, file_ext, text)
|
||||
text = text.strip() + "\n"
|
||||
|
||||
if original_text == text:
|
||||
return
|
||||
|
||||
with open(filepath, "wt") as f:
|
||||
f.write(text)
|
||||
|
||||
|
||||
def get_file_paths_for_commit(commit):
|
||||
return (
|
||||
subprocess.run(
|
||||
["git", "diff", "--name-only", "--diff-filter=ACMR", commit],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
encoding="utf8",
|
||||
)
|
||||
.stdout.rstrip()
|
||||
.split("\n")
|
||||
)
|
||||
|
||||
|
||||
def should_process_file(file_path: str) -> bool:
|
||||
if get_ext(file_path) not in EXTENSIONS_TO_CHECK:
|
||||
return False
|
||||
|
||||
for component in Path(file_path).parts:
|
||||
if component.startswith("."):
|
||||
return False
|
||||
if component in ("Pods", "ThirdParty"):
|
||||
return False
|
||||
if component.startswith("MobileCoinExternal."):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def swiftlint(file_paths):
|
||||
file_paths = list(filter(lambda f: get_ext(f) == ".swift", file_paths))
|
||||
if len(file_paths) == 0:
|
||||
return True
|
||||
|
||||
subprocess.run(["swiftlint", "lint", "--quiet", "--fix", *file_paths])
|
||||
proc = subprocess.run(["swiftlint", "lint", "--quiet", "--strict", *file_paths])
|
||||
|
||||
return proc.returncode == 0
|
||||
|
||||
|
||||
def clang_format(file_paths):
|
||||
file_paths = list(filter(lambda f: get_ext(f) in CLANG_FORMAT_EXTS, file_paths))
|
||||
if len(file_paths) == 0:
|
||||
return True
|
||||
proc = subprocess.run(["clang-format", "-i", *file_paths])
|
||||
return proc.returncode == 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="lint & format files")
|
||||
parser.add_argument("path", nargs="*", help="a path to process")
|
||||
parser.add_argument(
|
||||
"--ref",
|
||||
metavar="commit-sha",
|
||||
help="process paths that have changed since this commit",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-xcode-sort",
|
||||
action='store_true',
|
||||
help="skip sorting the Xcode project",
|
||||
)
|
||||
ns = parser.parse_args()
|
||||
|
||||
if len(ns.path) > 0:
|
||||
file_paths = ns.path
|
||||
else:
|
||||
file_paths = get_file_paths_for_commit(ns.ref or "HEAD")
|
||||
file_paths = sorted(set(filter(should_process_file, file_paths)))
|
||||
|
||||
result = True
|
||||
|
||||
print("Checking license headers...", flush=True)
|
||||
proc = subprocess.run(["Scripts/lint/lint-license-headers", "--fix", *file_paths])
|
||||
if proc.returncode != 0:
|
||||
result = False
|
||||
print("")
|
||||
|
||||
print("Running swiftlint...", flush=True)
|
||||
if not swiftlint(file_paths):
|
||||
result = False
|
||||
print("")
|
||||
|
||||
print("Sorting forward declarations...", flush=True)
|
||||
for file_path in file_paths:
|
||||
process(file_path)
|
||||
print("")
|
||||
|
||||
if ns.skip_xcode_sort:
|
||||
print("Skipping Xcode project sort!", flush=True)
|
||||
else:
|
||||
print("Sorting Xcode project...", flush=True)
|
||||
proc = subprocess.run(["Scripts/sort-Xcode-project-file", "Signal.xcodeproj"])
|
||||
if proc.returncode != 0:
|
||||
result = False
|
||||
print("")
|
||||
|
||||
print("Running clang-format...", flush=True)
|
||||
if not clang_format(file_paths):
|
||||
result = False
|
||||
print("")
|
||||
|
||||
if not result:
|
||||
print("Some errors couldn't be fixed automatically.")
|
||||
sys.exit(1)
|
1
Scripts/protos/.gitignore
vendored
Normal file
1
Scripts/protos/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.jar
|
2350
Scripts/protos/ProtoWrappers.py
Executable file
2350
Scripts/protos/ProtoWrappers.py
Executable file
File diff suppressed because it is too large
Load diff
100
Scripts/schema_dump
Executable file
100
Scripts/schema_dump
Executable file
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
SCHEMA_PATH = "SignalServiceKit/Resources/schema.sql"
|
||||
|
||||
TABLES_TO_IGNORE = [
|
||||
"grdb_migrations",
|
||||
"sqlite_sequence",
|
||||
"indexable_text_fts_data",
|
||||
"indexable_text_fts_idx",
|
||||
"indexable_text_fts_docsize",
|
||||
"indexable_text_fts_config",
|
||||
"SearchableNameFTS_data",
|
||||
"SearchableNameFTS_idx",
|
||||
"SearchableNameFTS_docsize",
|
||||
"SearchableNameFTS_config",
|
||||
]
|
||||
|
||||
|
||||
def main(ns):
|
||||
repo_root = os.path.abspath(os.path.join(__file__, "../.."))
|
||||
args = ["Scripts/sqlclient", "--quiet"]
|
||||
if ns.staging:
|
||||
args.extend(["--staging"])
|
||||
if ns.path is not None:
|
||||
args.extend(["--path", ns.path])
|
||||
if ns.passphrase is not None:
|
||||
args.extend(["--passphrase", ns.passphrase])
|
||||
args.extend(["--", ".schema"])
|
||||
schema = subprocess.run(
|
||||
args,
|
||||
check=True,
|
||||
encoding="utf8",
|
||||
capture_output=True,
|
||||
cwd=repo_root,
|
||||
).stdout
|
||||
|
||||
# Drop the "ok" from setting the passphrase.
|
||||
assert schema.startswith("ok\n")
|
||||
schema = schema[3:]
|
||||
|
||||
# Normalize the formatting.
|
||||
schema = subprocess.run(
|
||||
["bundle", "exec", "anbt-sql-formatter"],
|
||||
check=True,
|
||||
input=schema,
|
||||
encoding="utf8",
|
||||
capture_output=True,
|
||||
).stdout
|
||||
|
||||
# Remove tables that don't need to be included. (Generally, some other
|
||||
# mechanism creates these so that we don't need to.)
|
||||
for table in TABLES_TO_IGNORE:
|
||||
schema = re.sub(
|
||||
r"CREATE\s+TABLE\s+(IF NOT EXISTS\s+)?'?" + table + r".*?;\n\n",
|
||||
"",
|
||||
schema,
|
||||
flags=re.MULTILINE | re.DOTALL,
|
||||
)
|
||||
|
||||
file_path = os.path.join(repo_root, SCHEMA_PATH)
|
||||
with open(file_path, "r") as file:
|
||||
old_schema = file.read()
|
||||
|
||||
if schema == old_schema:
|
||||
return
|
||||
|
||||
with open(file_path, "w") as file:
|
||||
file.write(schema)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
target = parser.add_mutually_exclusive_group()
|
||||
target.add_argument(
|
||||
"--staging",
|
||||
action="store_true",
|
||||
help="Target the staging database of the currently-booted simulator.",
|
||||
)
|
||||
target.add_argument(
|
||||
"--path",
|
||||
metavar="/a/b/c",
|
||||
help="Target the database at the provided path.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--passphrase",
|
||||
metavar="abcdef0123456789",
|
||||
help="Use the provided passphrase to decrypt the database. "
|
||||
"(Or you can use “Settings” -> “Internal” -> “Misc” -> “Save plaintext database key”.)",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ns = parse_args()
|
||||
main(ns)
|
15
Scripts/sds_codegen/Makefile
Normal file
15
Scripts/sds_codegen/Makefile
Normal file
|
@ -0,0 +1,15 @@
|
|||
REPO_ROOT=../../
|
||||
|
||||
default: parse_and_regenerate
|
||||
|
||||
parse_and_regenerate:
|
||||
cd $(REPO_ROOT) && \
|
||||
Scripts/sds_codegen/sds_codegen.sh
|
||||
|
||||
regen: regenerate
|
||||
regenerate:
|
||||
cd $(REPO_ROOT) && \
|
||||
Scripts/sds_codegen/sds_regenerate.sh
|
||||
|
||||
clean:
|
||||
find $(REPO_ROOT) -name \*.sdsjson -exec rm {} \;
|
21
Scripts/sds_codegen/sds_codegen.sh
Executable file
21
Scripts/sds_codegen/sds_codegen.sh
Executable file
|
@ -0,0 +1,21 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -eux
|
||||
|
||||
# When parsing Obj-c source files, we need to be able to import type
|
||||
# definitions for all types we use, otherwise clang will treat them
|
||||
# as `long *`.
|
||||
#
|
||||
# This script enumerates all swift files in our codebase (including our Pods)
|
||||
# and generates fake Obj-c headers (.h) that @interface and @protocol
|
||||
# stubs for each swift class. This is analogous to a very simplified
|
||||
# version of the "-Swift.h" files used by Swift for bridging.
|
||||
Scripts/sds_codegen/sds_parse_swift_bridging.py --src-path . --swift-bridging-path Scripts/sds_codegen/sds-includes
|
||||
|
||||
# We parse Obj-C source files (.m only, not .mm yet) to extract simple class descriptions (class name, base class, property names and types, etc.)
|
||||
Scripts/sds_codegen/sds_parse_objc.py --src-path SignalServiceKit/ --swift-bridging-path Scripts/sds_codegen/sds-includes
|
||||
|
||||
Scripts/sds_codegen/sds_regenerate.sh
|
||||
|
||||
# lint & reformat generated sources
|
||||
find SignalServiceKit -type f -exec grep --quiet --fixed-strings '// --- CODE GENERATION MARKER' {} \; -print0 | xargs -0 Scripts/precommit.py
|
11
Scripts/sds_codegen/sds_codegen_ssk_objc.sh
Executable file
11
Scripts/sds_codegen/sds_codegen_ssk_objc.sh
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# The root directory of the repo.
|
||||
REPO_ROOT=`git rev-parse --show-toplevel`
|
||||
|
||||
# We parse Obj-C source files (.m only, not .mm yet) to extract simple class descriptions (class name, base class, property names and types, etc.)
|
||||
$REPO_ROOT/Scripts/sds_codegen/sds_parse_objc.py --src-path SignalServiceKit/ --swift-bridging-path $REPO_ROOT/Scripts/sds_codegen/sds-includes
|
||||
|
||||
$REPO_ROOT/Scripts/sds_codegen/sds_regenerate.sh
|
68
Scripts/sds_codegen/sds_common.py
Executable file
68
Scripts/sds_codegen/sds_common.py
Executable file
|
@ -0,0 +1,68 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
SDS_JSON_FILE_EXTENSION = ".sdsjson"
|
||||
|
||||
|
||||
def fail(*args):
|
||||
error = " ".join(str(arg) for arg in args)
|
||||
raise Exception(error)
|
||||
|
||||
|
||||
git_repo_path = os.path.abspath(
|
||||
subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip()
|
||||
)
|
||||
|
||||
|
||||
def sds_to_relative_path(path):
|
||||
path = os.path.abspath(path)
|
||||
if not path.startswith(git_repo_path):
|
||||
fail("Unexpected path:", path)
|
||||
path = path[len(git_repo_path) :]
|
||||
if path.startswith(os.sep):
|
||||
path = path[len(os.sep) :]
|
||||
return path
|
||||
|
||||
|
||||
def sds_from_relative_path(path):
|
||||
return os.path.join(git_repo_path, path)
|
||||
|
||||
|
||||
def clean_up_generated_code(text):
|
||||
# Remove trailing whitespace.
|
||||
lines = text.split("\n")
|
||||
lines = [line.rstrip() for line in lines]
|
||||
text = "\n".join(lines)
|
||||
# Compact newlines.
|
||||
while "\n\n\n" in text:
|
||||
text = text.replace("\n\n\n", "\n\n")
|
||||
# Ensure there's a trailing newline.
|
||||
return text.strip() + "\n"
|
||||
|
||||
|
||||
def clean_up_generated_swift(text):
|
||||
return clean_up_generated_code(text)
|
||||
|
||||
|
||||
def clean_up_generated_objc(text):
|
||||
return clean_up_generated_code(text)
|
||||
|
||||
|
||||
def pretty_module_path(path):
|
||||
path = os.path.abspath(path)
|
||||
if path.startswith(git_repo_path):
|
||||
path = path[len(git_repo_path) :]
|
||||
return path
|
||||
|
||||
|
||||
def write_text_file_if_changed(file_path, text):
|
||||
if os.path.exists(file_path):
|
||||
with open(file_path, "r") as f:
|
||||
oldText = f.read()
|
||||
if oldText == text:
|
||||
return
|
||||
|
||||
with open(file_path, "w") as f:
|
||||
f.write(text)
|
141
Scripts/sds_codegen/sds_config/sds-config.json
Normal file
141
Scripts/sds_codegen/sds_config/sds-config.json
Normal file
|
@ -0,0 +1,141 @@
|
|||
{
|
||||
"nsnumber_types": {
|
||||
"TSThread.archivedAsOfMessageSortId": "UInt64",
|
||||
"TSIncomingMessage.serverTimestamp": "UInt64",
|
||||
"TSIncomingMessage.deprecated_sourceDeviceId": "UInt32",
|
||||
"TSOutgoingMessageRecipientState.deliveryTimestamp": "UInt64",
|
||||
"TSOutgoingMessageRecipientState.readTimestamp": "UInt64",
|
||||
"OWSBackupFragment.uncompressedDataLength": "UInt64",
|
||||
"SSKJobRecord.exclusiveProcessIdentifier": "Int32",
|
||||
"TSMessage.storyTimestamp": "UInt64",
|
||||
"TSThread.editTargetTimestamp": "UInt64",
|
||||
"TSThread.lastSentStoryTimestamp": "UInt64",
|
||||
"TSThread.lastViewedStoryTimestamp": "UInt64",
|
||||
"TSThread.lastReceivedStoryTimestamp": "UInt64",
|
||||
"TSMessage.expireTimerVersion": "UInt32"
|
||||
},
|
||||
"properties_to_ignore": [
|
||||
"TSYapDatabaseObject.grdbId",
|
||||
"OWSDynamicOutgoingMessage.block",
|
||||
"OWSDisappearingMessagesConfiguration.originalDictionaryValue",
|
||||
"OWSDisappearingMessagesConfiguration.newRecord",
|
||||
"TSThread.isArchivedByLegacyTimestampForSorting",
|
||||
"TSContactThread.contactThreadSchemaVersion",
|
||||
"TSCall.callSchemaVersion",
|
||||
"TSErrorMessage.errorMessageSchemaVersion",
|
||||
"TSMessage.schemaVersion",
|
||||
"TSIncomingMessage.incomingMessageSchemaVersion",
|
||||
"TSInfoMessage.infoMessageSchemaVersion",
|
||||
"TSOutgoingMessage.outgoingMessageSchemaVersion",
|
||||
"OWSUnknownProtocolVersionMessage.unknownProtocolVersionMessageSchemaVersion",
|
||||
"OWSRecipientIdentity.recipientIdentitySchemaVersion",
|
||||
"TSRecipientReadReceipt.recipientReadReceiptSchemaVersion",
|
||||
"OWSLinkedDeviceReadReceipt.linkedDeviceReadReceiptSchemaVersion",
|
||||
"TSOutgoingMessage.changeActionsProtoData"
|
||||
],
|
||||
"class_cache_get_code": {
|
||||
"InstalledSticker": "SSKEnvironment.shared.modelReadCachesRef.installedStickerCache.getInstalledSticker(uniqueId: uniqueId, transaction: transaction)",
|
||||
"TSThread": "SSKEnvironment.shared.modelReadCachesRef.threadReadCache.getThread(uniqueId: uniqueId, transaction: transaction)",
|
||||
"TSInteraction": "SSKEnvironment.shared.modelReadCachesRef.interactionReadCache.getInteraction(uniqueId: uniqueId, transaction: transaction)"
|
||||
},
|
||||
"class_cache_set_code": {
|
||||
"InstalledSticker": "SSKEnvironment.shared.modelReadCachesRef.installedStickerCache.didReadInstalledSticker",
|
||||
"TSThread": "SSKEnvironment.shared.modelReadCachesRef.threadReadCache.didReadThread",
|
||||
"TSInteraction": "SSKEnvironment.shared.modelReadCachesRef.interactionReadCache.didReadInteraction"
|
||||
},
|
||||
"class_to_skip_serialization": [
|
||||
"OWSContactOffersInteraction",
|
||||
"OWSOutgoingSyncMessage",
|
||||
"OWSOutgoingReactionMessage",
|
||||
"OWSEndSessionMessage",
|
||||
"OWSStaticOutgoingMessage",
|
||||
"OWSDynamicOutgoingMessage",
|
||||
"OWSDisappearingMessagesConfigurationMessage",
|
||||
"OWSGroupInfoRequestMessage",
|
||||
"OWSProfileKeyMessage",
|
||||
"OWSOutgoingNullMessage",
|
||||
"OWSOutgoingCallMessage",
|
||||
"OWSOutgoingGroupCallMessage",
|
||||
"OWSOutgoingSenderKeyDistributionMessage",
|
||||
"OWSOutgoingResendRequest",
|
||||
"OWSReceiptsForSenderMessage",
|
||||
"OWSUnknownDBObject",
|
||||
"TSOutgoingDeleteMessage",
|
||||
"OWSOutgoingResendResponse"
|
||||
],
|
||||
"manually_typed_fields": {
|
||||
"TSThread.conversationColorName": {
|
||||
"swift_type": "ConversationColorName",
|
||||
"objc_initializer_type": "ConversationColorName",
|
||||
"is_objc_codable": true,
|
||||
"is_enum": true,
|
||||
"column_type": ".unicodeString",
|
||||
"record_swift_type": "String",
|
||||
"serialize_record_invocation": "%s.rawValue",
|
||||
"should_use_blob": false
|
||||
},
|
||||
"TSInfoMessage.infoMessageUserInfo": {
|
||||
"swift_type": "[InfoMessageUserInfoKey: AnyObject]",
|
||||
"objc_initializer_type": "NSDictionary<InfoMessageUserInfoKey, id> *",
|
||||
"is_objc_codable": false,
|
||||
"is_enum": false,
|
||||
"column_type": ".blob",
|
||||
"record_swift_type": "Data",
|
||||
"serialize_record_invocation": "optionalArchive(%s)",
|
||||
"should_use_blob": true
|
||||
},
|
||||
"TSThread.mentionNotificationMode": {
|
||||
"swift_type": "TSThreadMentionNotificationMode",
|
||||
"objc_initializer_type": "TSThreadMentionNotificationMode",
|
||||
"is_objc_codable": true,
|
||||
"is_enum": true,
|
||||
"column_type": ".int",
|
||||
"record_swift_type": "UInt",
|
||||
"serialize_record_invocation": "%s.rawValue",
|
||||
"should_use_blob": false
|
||||
},
|
||||
"TSThread.storyViewMode": {
|
||||
"swift_type": "TSThreadStoryViewMode",
|
||||
"objc_initializer_type": "TSThreadStoryViewMode",
|
||||
"is_objc_codable": true,
|
||||
"is_enum": true,
|
||||
"column_type": ".int",
|
||||
"record_swift_type": "UInt",
|
||||
"serialize_record_invocation": "%s.rawValue",
|
||||
"should_use_blob": false
|
||||
}
|
||||
},
|
||||
"custom_accessors": {
|
||||
"TSInfoMessage.read": "wasRead",
|
||||
"TSErrorMessage.read": "wasRead",
|
||||
"TSIncomingMessage.read": "wasRead",
|
||||
"TSCall.read": "wasRead",
|
||||
"OWSGroupCallMessage.read": "wasRead",
|
||||
"TSIncomingMessage.viewed": "wasViewed",
|
||||
"OWSDisappearingMessagesConfiguration.enabled": "isEnabled",
|
||||
"OWSDisappearingMessagesConfiguration.newRecord": "isNewRecord"
|
||||
},
|
||||
"custom_column_names": {
|
||||
"TSInteraction.uniqueThreadId": "threadUniqueId",
|
||||
"TSThread.lastVisibleSortIdObsolete": "lastVisibleSortId",
|
||||
"TSThread.lastVisibleSortIdOnScreenPercentageObsolete": "lastVisibleSortIdOnScreenPercentage",
|
||||
"TSThread.mutedUntilDateObsolete": "mutedUntilDate",
|
||||
"TSThread.isArchivedObsolete": "isArchived",
|
||||
"TSThread.isMarkedUnreadObsolete": "isMarkedUnread",
|
||||
"TSThread.mutedUntilTimestampObsolete": "mutedUntilTimestamp",
|
||||
"TSThread.conversationColorNameObsolete": "conversationColorName"
|
||||
},
|
||||
"aliased_column_names": {
|
||||
"TSInteraction.sortId": "id",
|
||||
"SSKJobRecord.sortId": "id"
|
||||
},
|
||||
"renamed_column_names": {
|
||||
"TSThread.lastVisibleSortIdObsolete": "lastVisibleSortId",
|
||||
"TSThread.lastVisibleSortIdOnScreenPercentageObsolete": "lastVisibleSortIdOnScreenPercentage",
|
||||
"TSThread.mutedUntilDateObsolete": "mutedUntilDate",
|
||||
"TSThread.isArchivedObsolete": "isArchived",
|
||||
"TSThread.isMarkedUnreadObsolete": "isMarkedUnread",
|
||||
"TSThread.mutedUntilTimestampObsolete": "mutedUntilTimestamp",
|
||||
"TSThread.conversationColorNameObsolete": "conversationColorName"
|
||||
}
|
||||
}
|
180
Scripts/sds_codegen/sds_config/sds-property_order.json
Normal file
180
Scripts/sds_codegen/sds_config/sds-property_order.json
Normal file
|
@ -0,0 +1,180 @@
|
|||
{
|
||||
"#comment": "NOTE: This file is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run `sds_codegen.sh`.",
|
||||
"AttachmentRecord.albumMessageId": 1,
|
||||
"AttachmentRecord.attachmentSchemaVersion": 27,
|
||||
"AttachmentRecord.attachmentType": 2,
|
||||
"AttachmentRecord.blurHash": 3,
|
||||
"AttachmentRecord.byteCount": 4,
|
||||
"AttachmentRecord.cachedAudioDurationSeconds": 10,
|
||||
"AttachmentRecord.cachedImageHeight": 11,
|
||||
"AttachmentRecord.cachedImageWidth": 12,
|
||||
"AttachmentRecord.caption": 5,
|
||||
"AttachmentRecord.cdnKey": 24,
|
||||
"AttachmentRecord.cdnNumber": 25,
|
||||
"AttachmentRecord.clientUuid": 29,
|
||||
"AttachmentRecord.contentType": 6,
|
||||
"AttachmentRecord.creationTimestamp": 13,
|
||||
"AttachmentRecord.digest": 14,
|
||||
"AttachmentRecord.encryptionKey": 7,
|
||||
"AttachmentRecord.isAnimatedCached": 26,
|
||||
"AttachmentRecord.isUploaded": 15,
|
||||
"AttachmentRecord.isValidImageCached": 16,
|
||||
"AttachmentRecord.isValidVideoCached": 17,
|
||||
"AttachmentRecord.lazyRestoreFragmentId": 18,
|
||||
"AttachmentRecord.localRelativeFilePath": 19,
|
||||
"AttachmentRecord.mediaSize": 20,
|
||||
"AttachmentRecord.pointerType": 21,
|
||||
"AttachmentRecord.serverId": 8,
|
||||
"AttachmentRecord.sourceFilename": 9,
|
||||
"AttachmentRecord.state": 22,
|
||||
"AttachmentRecord.uploadTimestamp": 23,
|
||||
"AttachmentRecord.videoDuration": 28,
|
||||
"BaseModel": 56,
|
||||
"DisappearingMessagesConfigurationRecord.durationSeconds": 1,
|
||||
"DisappearingMessagesConfigurationRecord.enabled": 2,
|
||||
"DisappearingMessagesConfigurationRecord.timerVersion": 3,
|
||||
"IncomingGroupsV2MessageJobRecord.createdAt": 1,
|
||||
"IncomingGroupsV2MessageJobRecord.envelopeData": 2,
|
||||
"IncomingGroupsV2MessageJobRecord.groupId": 5,
|
||||
"IncomingGroupsV2MessageJobRecord.plaintextData": 3,
|
||||
"IncomingGroupsV2MessageJobRecord.serverDeliveryTimestamp": 6,
|
||||
"IncomingGroupsV2MessageJobRecord.wasReceivedByUD": 4,
|
||||
"InstalledStickerRecord.contentType": 3,
|
||||
"InstalledStickerRecord.emojiString": 1,
|
||||
"InstalledStickerRecord.info": 2,
|
||||
"InteractionRecord.archivedPaymentInfo": 71,
|
||||
"InteractionRecord.authorId": 5,
|
||||
"InteractionRecord.authorPhoneNumber": 6,
|
||||
"InteractionRecord.authorUUID": 7,
|
||||
"InteractionRecord.body": 8,
|
||||
"InteractionRecord.bodyRanges": 52,
|
||||
"InteractionRecord.callType": 9,
|
||||
"InteractionRecord.configurationDurationSeconds": 10,
|
||||
"InteractionRecord.configurationIsEnabled": 11,
|
||||
"InteractionRecord.contactShare": 12,
|
||||
"InteractionRecord.createdByRemoteName": 13,
|
||||
"InteractionRecord.createdInExistingGroup": 14,
|
||||
"InteractionRecord.creatorUuid": 57,
|
||||
"InteractionRecord.customMessage": 15,
|
||||
"InteractionRecord.deprecated_attachmentIds": 4,
|
||||
"InteractionRecord.deprecated_sourceDeviceId": 44,
|
||||
"InteractionRecord.editState": 70,
|
||||
"InteractionRecord.envelopeData": 16,
|
||||
"InteractionRecord.eraId": 55,
|
||||
"InteractionRecord.errorType": 17,
|
||||
"InteractionRecord.expireStartedAt": 18,
|
||||
"InteractionRecord.expireTimerVersion": 72,
|
||||
"InteractionRecord.expiresAt": 19,
|
||||
"InteractionRecord.expiresInSeconds": 20,
|
||||
"InteractionRecord.giftBadge": 69,
|
||||
"InteractionRecord.groupMetaMessage": 21,
|
||||
"InteractionRecord.hasEnded": 56,
|
||||
"InteractionRecord.hasLegacyMessageState": 22,
|
||||
"InteractionRecord.hasSyncedTranscript": 23,
|
||||
"InteractionRecord.infoMessageUserInfo": 50,
|
||||
"InteractionRecord.isGroupStoryReply": 67,
|
||||
"InteractionRecord.isLocalChange": 25,
|
||||
"InteractionRecord.isSmsMessageRestoredFromBackup": 73,
|
||||
"InteractionRecord.isViewOnceComplete": 26,
|
||||
"InteractionRecord.isViewOnceMessage": 27,
|
||||
"InteractionRecord.isVoiceMessage": 28,
|
||||
"InteractionRecord.joinedMemberUuids": 58,
|
||||
"InteractionRecord.legacyMessageState": 29,
|
||||
"InteractionRecord.legacyWasDelivered": 30,
|
||||
"InteractionRecord.linkPreview": 31,
|
||||
"InteractionRecord.messageId": 32,
|
||||
"InteractionRecord.messageSticker": 33,
|
||||
"InteractionRecord.messageType": 34,
|
||||
"InteractionRecord.mostRecentFailureText": 35,
|
||||
"InteractionRecord.offerType": 53,
|
||||
"InteractionRecord.paymentCancellation": 60,
|
||||
"InteractionRecord.paymentNotification": 61,
|
||||
"InteractionRecord.paymentRequest": 62,
|
||||
"InteractionRecord.preKeyBundle": 36,
|
||||
"InteractionRecord.protocolVersion": 37,
|
||||
"InteractionRecord.quotedMessage": 38,
|
||||
"InteractionRecord.read": 39,
|
||||
"InteractionRecord.receivedAtTimestamp": 1,
|
||||
"InteractionRecord.recipientAddress": 40,
|
||||
"InteractionRecord.recipientAddressStates": 41,
|
||||
"InteractionRecord.sender": 42,
|
||||
"InteractionRecord.serverDeliveryTimestamp": 54,
|
||||
"InteractionRecord.serverGuid": 64,
|
||||
"InteractionRecord.serverTimestamp": 43,
|
||||
"InteractionRecord.storedMessageState": 45,
|
||||
"InteractionRecord.storedShouldStartExpireTimer": 46,
|
||||
"InteractionRecord.storyAuthorUuidString": 65,
|
||||
"InteractionRecord.storyReactionEmoji": 68,
|
||||
"InteractionRecord.storyTimestamp": 66,
|
||||
"InteractionRecord.timestamp": 2,
|
||||
"InteractionRecord.uniqueThreadId": 3,
|
||||
"InteractionRecord.unregisteredAddress": 47,
|
||||
"InteractionRecord.verificationState": 48,
|
||||
"InteractionRecord.viewed": 63,
|
||||
"InteractionRecord.wasIdentityVerified": 59,
|
||||
"InteractionRecord.wasNotCreatedLocally": 24,
|
||||
"InteractionRecord.wasReceivedByUD": 49,
|
||||
"InteractionRecord.wasRemotelyDeleted": 51,
|
||||
"MessageContentJobRecord.createdAt": 1,
|
||||
"MessageContentJobRecord.envelopeData": 2,
|
||||
"MessageContentJobRecord.plaintextData": 3,
|
||||
"MessageContentJobRecord.serverDeliveryTimestamp": 5,
|
||||
"MessageContentJobRecord.wasReceivedByUD": 4,
|
||||
"PaymentModelRecord.addressUuidString": 1,
|
||||
"PaymentModelRecord.createdTimestamp": 2,
|
||||
"PaymentModelRecord.interactionUniqueId": 14,
|
||||
"PaymentModelRecord.isUnread": 3,
|
||||
"PaymentModelRecord.mcLedgerBlockIndex": 4,
|
||||
"PaymentModelRecord.mcReceiptData": 5,
|
||||
"PaymentModelRecord.mcTransactionData": 6,
|
||||
"PaymentModelRecord.memoMessage": 7,
|
||||
"PaymentModelRecord.mobileCoin": 8,
|
||||
"PaymentModelRecord.paymentAmount": 9,
|
||||
"PaymentModelRecord.paymentFailure": 10,
|
||||
"PaymentModelRecord.paymentState": 11,
|
||||
"PaymentModelRecord.paymentType": 12,
|
||||
"PaymentModelRecord.requestUuidString": 13,
|
||||
"PaymentRequestModelRecord.addressUuidString": 1,
|
||||
"PaymentRequestModelRecord.createdTimestamp": 2,
|
||||
"PaymentRequestModelRecord.isIncomingRequest": 3,
|
||||
"PaymentRequestModelRecord.memoMessage": 4,
|
||||
"PaymentRequestModelRecord.paymentAmount": 5,
|
||||
"PaymentRequestModelRecord.requestUuidString": 6,
|
||||
"RecipientIdentityRecord.accountId": 1,
|
||||
"RecipientIdentityRecord.createdAt": 2,
|
||||
"RecipientIdentityRecord.identityKey": 3,
|
||||
"RecipientIdentityRecord.isFirstKnownKey": 4,
|
||||
"RecipientIdentityRecord.verificationState": 5,
|
||||
"StickerPackRecord.author": 1,
|
||||
"StickerPackRecord.cover": 2,
|
||||
"StickerPackRecord.dateCreated": 3,
|
||||
"StickerPackRecord.info": 4,
|
||||
"StickerPackRecord.isInstalled": 5,
|
||||
"StickerPackRecord.items": 6,
|
||||
"StickerPackRecord.title": 7,
|
||||
"ThreadRecord.addresses": 21,
|
||||
"ThreadRecord.allowsReplies": 18,
|
||||
"ThreadRecord.contactPhoneNumber": 8,
|
||||
"ThreadRecord.contactUUID": 9,
|
||||
"ThreadRecord.conversationColorNameObsolete": 1,
|
||||
"ThreadRecord.creationDate": 2,
|
||||
"ThreadRecord.editTargetTimestamp": 23,
|
||||
"ThreadRecord.groupModel": 10,
|
||||
"ThreadRecord.hasDismissedOffers": 11,
|
||||
"ThreadRecord.isArchivedObsolete": 3,
|
||||
"ThreadRecord.isMarkedUnreadObsolete": 12,
|
||||
"ThreadRecord.lastInteractionRowId": 4,
|
||||
"ThreadRecord.lastReceivedStoryTimestamp": 24,
|
||||
"ThreadRecord.lastSentStoryTimestamp": 19,
|
||||
"ThreadRecord.lastViewedStoryTimestamp": 23,
|
||||
"ThreadRecord.lastVisibleSortIdObsolete": 14,
|
||||
"ThreadRecord.lastVisibleSortIdOnScreenPercentageObsolete": 13,
|
||||
"ThreadRecord.mentionNotificationMode": 16,
|
||||
"ThreadRecord.messageDraft": 5,
|
||||
"ThreadRecord.messageDraftBodyRanges": 15,
|
||||
"ThreadRecord.mutedUntilDateObsolete": 6,
|
||||
"ThreadRecord.mutedUntilTimestampObsolete": 17,
|
||||
"ThreadRecord.name": 20,
|
||||
"ThreadRecord.shouldThreadBeVisible": 7,
|
||||
"ThreadRecord.storyViewMode": 22
|
||||
}
|
76
Scripts/sds_codegen/sds_config/sds_record_type_map.json
Normal file
76
Scripts/sds_codegen/sds_config/sds_record_type_map.json
Normal file
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"#comment": "NOTE: This file is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run `sds_codegen.sh`.",
|
||||
"BaseModel": 56,
|
||||
"ExperienceUpgrade": 55,
|
||||
"IncomingGroupsV2MessageJob": 63,
|
||||
"InstalledSticker": 24,
|
||||
"OWS100RemoveTSRecipientsMigration": 40,
|
||||
"OWS101ExistingUsersBlockOnIdentityChange": 43,
|
||||
"OWS102MoveLoggingPreferenceToUserDefaults": 47,
|
||||
"OWS103EnableVideoCalling": 42,
|
||||
"OWS104CreateRecipientIdentities": 45,
|
||||
"OWS105AttachmentFilePaths": 44,
|
||||
"OWS107LegacySounds": 50,
|
||||
"OWS108CallLoggingPreference": 48,
|
||||
"OWS109OutgoingMessageState": 51,
|
||||
"OWSAddToContactsOfferMessage": 25,
|
||||
"OWSAddToProfileWhitelistOfferMessage": 7,
|
||||
"OWSBackupFragment": 32,
|
||||
"OWSContactOffersInteraction": 22,
|
||||
"OWSContactQuery": 57,
|
||||
"OWSDatabaseMigration": 46,
|
||||
"OWSDevice": 33,
|
||||
"OWSDisappearingConfigurationUpdateInfoMessage": 28,
|
||||
"OWSDisappearingMessagesConfiguration": 39,
|
||||
"OWSGroupCallMessage": 65,
|
||||
"OWSIncomingArchivedPaymentMessage": 78,
|
||||
"OWSIncomingContactSyncJobRecord": 61,
|
||||
"OWSIncomingGroupSyncJobRecord": 60,
|
||||
"OWSIncomingPaymentMessage": 75,
|
||||
"OWSLinkedDeviceReadReceipt": 36,
|
||||
"OWSLocalUserLeaveGroupJobRecord": 74,
|
||||
"OWSMessageContentJob": 15,
|
||||
"OWSOutgoingArchivedPaymentMessage": 79,
|
||||
"OWSOutgoingPaymentMessage": 68,
|
||||
"OWSPaymentActivationRequestFinishedMessage": 77,
|
||||
"OWSPaymentActivationRequestMessage": 76,
|
||||
"OWSReaction": 62,
|
||||
"OWSReceiptCredentialRedemptionJobRecord": 71,
|
||||
"OWSRecipientIdentity": 38,
|
||||
"OWSRecoverableDecryptionPlaceholder": 70,
|
||||
"OWSResaveCollectionDBMigration": 49,
|
||||
"OWSSendGiftBadgeJobRecord": 73,
|
||||
"OWSSessionResetJobRecord": 52,
|
||||
"OWSUnknownContactBlockOfferMessage": 5,
|
||||
"OWSUnknownDBObject": 37,
|
||||
"OWSUnknownProtocolVersionMessage": 54,
|
||||
"OWSUserProfile": 41,
|
||||
"OWSVerificationStateChangeMessage": 13,
|
||||
"SSKJobRecord": 34,
|
||||
"SSKMessageDecryptJobRecord": 53,
|
||||
"SSKMessageSenderJobRecord": 35,
|
||||
"SignalAccount": 30,
|
||||
"SignalRecipient": 31,
|
||||
"StickerPack": 14,
|
||||
"TSCall": 20,
|
||||
"TSContactThread": 27,
|
||||
"TSErrorMessage": 9,
|
||||
"TSGroupMember": 69,
|
||||
"TSGroupThread": 26,
|
||||
"TSIncomingMessage": 19,
|
||||
"TSInfoMessage": 10,
|
||||
"TSInteraction": 16,
|
||||
"TSInvalidIdentityKeyErrorMessage": 17,
|
||||
"TSInvalidIdentityKeyReceivingErrorMessage": 1,
|
||||
"TSInvalidIdentityKeySendingErrorMessage": 23,
|
||||
"TSMention": 64,
|
||||
"TSMessage": 11,
|
||||
"TSOutgoingMessage": 21,
|
||||
"TSPaymentModel": 67,
|
||||
"TSPaymentRequestModel": 66,
|
||||
"TSPrivateStoryThread": 72,
|
||||
"TSRecipientReadReceipt": 12,
|
||||
"TSThread": 2,
|
||||
"TSUnreadIndicatorInteraction": 4,
|
||||
"TestModel": 59
|
||||
}
|
2956
Scripts/sds_codegen/sds_generate.py
Executable file
2956
Scripts/sds_codegen/sds_generate.py
Executable file
File diff suppressed because it is too large
Load diff
853
Scripts/sds_codegen/sds_parse_objc.py
Executable file
853
Scripts/sds_codegen/sds_parse_objc.py
Executable file
|
@ -0,0 +1,853 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import argparse
|
||||
import re
|
||||
import json
|
||||
import sds_common
|
||||
from sds_common import fail
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
git_repo_path = sds_common.git_repo_path
|
||||
|
||||
|
||||
def ows_getoutput(cmd: list[str]) -> tuple[int, str, str]:
|
||||
proc = subprocess.Popen(
|
||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
||||
)
|
||||
stdout, stderr = proc.communicate()
|
||||
|
||||
return proc.returncode, stdout, stderr
|
||||
|
||||
|
||||
class LineProcessor:
|
||||
def __init__(self, text):
|
||||
self.lines = text.split("\n")
|
||||
|
||||
def hasNext(self):
|
||||
return len(self.lines) > 0
|
||||
|
||||
def next(self, should_pop=False):
|
||||
if len(self.lines) == 0:
|
||||
return None
|
||||
line = self.lines[0]
|
||||
if should_pop:
|
||||
self.lines = self.lines[1:]
|
||||
return line
|
||||
|
||||
def popNext(self):
|
||||
return self.next(should_pop=True)
|
||||
|
||||
|
||||
counter = 0
|
||||
|
||||
|
||||
def next_counter():
|
||||
global counter
|
||||
counter = counter + 1
|
||||
return counter
|
||||
|
||||
|
||||
class ParsedClass:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.is_implemented = False
|
||||
self.property_map = {}
|
||||
self.super_class_name = None
|
||||
self.counter = next_counter()
|
||||
self.finalize_method_name = None
|
||||
self.namespace = None
|
||||
self.protocol_names = []
|
||||
|
||||
def get_property(self, name):
|
||||
result = self.property_map.get(name)
|
||||
if result is None:
|
||||
result = self.get_inherited_property(name)
|
||||
return result
|
||||
|
||||
def add_property(self, property):
|
||||
self.property_map[property.name] = property
|
||||
|
||||
def properties(self):
|
||||
result = []
|
||||
for name in sorted(self.property_map.keys()):
|
||||
result.append(self.property_map[name])
|
||||
return result
|
||||
|
||||
def property_names(self):
|
||||
return sorted(self.property_map.keys())
|
||||
|
||||
def inherit_from_protocol(self, namespace, protocol_name):
|
||||
self.namespace = namespace
|
||||
self.protocol_names.append(protocol_name)
|
||||
|
||||
def get_inherited_property(self, name):
|
||||
for protocol in self.class_protocols():
|
||||
result = protocol.get_property(name)
|
||||
if result is not None:
|
||||
return result
|
||||
return None
|
||||
|
||||
def all_properties(self):
|
||||
result = self.properties()
|
||||
# We need to include any properties synthesized by this class
|
||||
# but declared in a protocol.
|
||||
for protocol in self.class_protocols():
|
||||
result.extend(protocol.all_properties())
|
||||
return result
|
||||
|
||||
def class_protocols(self):
|
||||
result = []
|
||||
for protocol_name in self.protocol_names:
|
||||
if protocol_name == self.name:
|
||||
# There are classes that implement a protocol of the same name, e.g. MTLModel
|
||||
# Ignore them.
|
||||
continue
|
||||
|
||||
protocol = self.namespace.find_class(protocol_name)
|
||||
if protocol is None:
|
||||
if (
|
||||
protocol_name.startswith("NS")
|
||||
or protocol_name.startswith("AV")
|
||||
or protocol_name.startswith("UI")
|
||||
or protocol_name.startswith("MF")
|
||||
or protocol_name.startswith("UN")
|
||||
or protocol_name.startswith("CN")
|
||||
):
|
||||
# Ignore built in protocols.
|
||||
continue
|
||||
print("clazz:", self.name)
|
||||
print("file_path:", file_path)
|
||||
fail("Missing protocol:", protocol_name)
|
||||
|
||||
result.append(protocol)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class ParsedProperty:
|
||||
def __init__(self, name, objc_type, is_optional):
|
||||
self.name = name
|
||||
self.objc_type = objc_type
|
||||
self.is_optional = is_optional
|
||||
self.is_not_readonly = False
|
||||
self.is_synthesized = False
|
||||
|
||||
|
||||
class Namespace:
|
||||
def __init__(self):
|
||||
self.class_map = {}
|
||||
|
||||
def upsert_class(self, class_name):
|
||||
clazz = self.class_map.get(class_name)
|
||||
if clazz is None:
|
||||
clazz = ParsedClass(class_name)
|
||||
self.class_map[class_name] = clazz
|
||||
return clazz
|
||||
|
||||
def find_class(self, class_name):
|
||||
clazz = self.class_map.get(class_name)
|
||||
return clazz
|
||||
|
||||
def class_names(self):
|
||||
return sorted(self.class_map.keys())
|
||||
|
||||
|
||||
split_objc_ast_prefix_regex = re.compile(r"^([ |\-`]*)(.+)$")
|
||||
|
||||
|
||||
# The AST emitted by clang uses punctuation to indicate the AST hierarchy.
|
||||
# This function strips that out.
|
||||
def split_objc_ast_prefix(line):
|
||||
match = split_objc_ast_prefix_regex.search(line)
|
||||
if match is None:
|
||||
fail("Could not match line:", line)
|
||||
prefix = match.group(1)
|
||||
remainder = match.group(2)
|
||||
return prefix, remainder
|
||||
|
||||
|
||||
def process_objc_ast(namespace: Namespace, file_path: str, raw_ast: str) -> None:
|
||||
m_filename = os.path.basename(file_path)
|
||||
file_base, file_extension = os.path.splitext(m_filename)
|
||||
if file_extension != ".m":
|
||||
fail("Bad file extension:", file_extension)
|
||||
h_filename = file_base + ".h"
|
||||
|
||||
# TODO: Remove
|
||||
lines = raw_ast.split("\n")
|
||||
raw_ast = "\n".join(lines)
|
||||
|
||||
lines = LineProcessor(raw_ast)
|
||||
while lines.hasNext():
|
||||
line = lines.popNext()
|
||||
prefix, remainder = split_objc_ast_prefix(line)
|
||||
|
||||
if remainder.startswith("ObjCInterfaceDecl "):
|
||||
# |-ObjCInterfaceDecl 0x112510490 <SignalDataStoreCommon/ObjCMessage.h:14:1, line:25:2> line:14:12 ObjCMessage
|
||||
process_objc_interface(namespace, file_path, lines, prefix, remainder)
|
||||
elif remainder.startswith("ObjCCategoryDecl "):
|
||||
# |-ObjCCategoryDecl 0x112510d58 <SignalDataStoreCommon/ObjCMessage.m:18:1, line:22:2> line:18:12
|
||||
process_objc_category(namespace, file_path, lines, prefix, remainder)
|
||||
elif remainder.startswith("ObjCImplementationDecl "):
|
||||
# `-ObjCImplementationDecl 0x112510f20 <line:24:1, line:87:1> line:24:17 ObjCMessage
|
||||
process_objc_implementation(namespace, file_path, lines, prefix, remainder)
|
||||
elif remainder.startswith("ObjCProtocolDecl "):
|
||||
# `-ObjCImplementationDecl 0x112510f20 <line:24:1, line:87:1> line:24:17 ObjCMessage
|
||||
process_objc_protocol_decl(namespace, file_path, lines, prefix, remainder)
|
||||
# TODO: Category impl.
|
||||
elif remainder.startswith("TypedefDecl "):
|
||||
# `-ObjCImplementationDecl 0x112510f20 <line:24:1, line:87:1> line:24:17 ObjCMessage
|
||||
process_objc_type_declaration(
|
||||
namespace, file_path, lines, prefix, remainder
|
||||
)
|
||||
elif remainder.startswith("EnumDecl "):
|
||||
# `-ObjCImplementationDecl 0x112510f20 <line:24:1, line:87:1> line:24:17 ObjCMessage
|
||||
process_objc_enum_declaration(
|
||||
namespace, file_path, lines, prefix, remainder
|
||||
)
|
||||
|
||||
|
||||
# |-EnumDecl 0x7fd576047310 </Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/CoreFoundation.framework/Headers/CFAvailability.h:127:43, /Users/matthew/code/workspace/ows/Signal-iOS-2/SignalServiceKit/src/Messages/TSCall.h:12:29> col:29 RPRecentCallType 'NSUInteger':'unsigned long'
|
||||
# | `-EnumExtensibilityAttr 0x7fd5760473f0 </Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/CoreFoundation.framework/Headers/CFAvailability.h:116:45, col:68> Open
|
||||
# |-TypedefDecl 0x7fd576047488 </Users/matthew/code/workspace/ows/Signal-iOS-2/SignalServiceKit/src/Messages/TSCall.h:12:1, col:29> col:29 referenced RPRecentCallType 'enum RPRecentCallType':'enum RPRecentCallType'
|
||||
# | `-ElaboratedType 0x7fd576047430 'enum RPRecentCallType' sugar
|
||||
# | `-EnumType 0x7fd5760473d0 'enum RPRecentCallType'
|
||||
# | `-Enum 0x7fd576047518 'RPRecentCallType'
|
||||
# |-EnumDecl 0x7fd576047518 prev 0x7fd576047310 </Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/CoreFoundation.framework/Headers/CFAvailability.h:127:90,
|
||||
process_objc_enum_declaration_regex = re.compile(r"^.+? ([^ ]+) '([^']+)':'([^']+)'$")
|
||||
# process_objc_enum_declaration_regex = re.compile(r"^.+?'([^']+)'(:'([^']+)')?$")
|
||||
|
||||
enum_type_map = {}
|
||||
|
||||
|
||||
def process_objc_enum_declaration(namespace, file_path, lines, prefix, remainder):
|
||||
match = process_objc_enum_declaration_regex.search(remainder)
|
||||
if match is None:
|
||||
print("file_path:", file_path)
|
||||
print("Could not match line:", remainder)
|
||||
return
|
||||
type1 = get_match_group(match, 1)
|
||||
type2 = get_match_group(match, 2)
|
||||
type3 = get_match_group(match, 3)
|
||||
|
||||
if type1.startswith("line:"):
|
||||
return
|
||||
if type1 in enum_type_map:
|
||||
return
|
||||
enum_type_map[type1] = type2
|
||||
|
||||
|
||||
# |-TypedefDecl 0x7f8d8fb44748 <line:12:1, line:22:3> col:3 referenced RPRecentCallType 'enum RPRecentCallType':'RPRecentCallType'
|
||||
process_objc_type_declaration_regex = re.compile(r"^.+?'([^']+)'(:'([^']+)')?$")
|
||||
|
||||
|
||||
def process_objc_type_declaration(namespace, file_path, lines, prefix, remainder):
|
||||
match = process_objc_type_declaration_regex.search(remainder)
|
||||
if match is None:
|
||||
print("file_path:", file_path)
|
||||
fail("Could not match line:", remainder)
|
||||
type1 = get_match_group(match, 1)
|
||||
type2 = get_match_group(match, 2)
|
||||
type3 = get_match_group(match, 3)
|
||||
|
||||
if type1 is None or type3 is None:
|
||||
return
|
||||
is_enum = type1 == "enum " + type3
|
||||
if not is_enum:
|
||||
return
|
||||
|
||||
if type3.startswith("line:"):
|
||||
print("Ignoring invalid enum(2):", type1, type2, type3)
|
||||
return
|
||||
if type3 not in enum_type_map:
|
||||
print("Enum has unknown type:", type3)
|
||||
enum_type = "NSUInteger"
|
||||
else:
|
||||
enum_type = enum_type_map[type3]
|
||||
enum_type_map[type3] = enum_type
|
||||
|
||||
|
||||
# |-ObjCInterfaceDecl 0x10f5d2b60 <SignalDataStoreCommon/ObjCBaseModel.h:15:1, col:8> col:8 SDSDataStore
|
||||
# |-ObjCInterfaceDecl 0x10f5d2c10 <line:17:1, line:29:2> line:17:12 ObjCBaseModel
|
||||
# | |-ObjCPropertyDecl 0x10f5d2d40 <line:19:1, col:43> col:43 uniqueId 'NSString * _Nonnull':'NSString *' readonly nonatomic
|
||||
# ...
|
||||
# |-ObjCInterfaceDecl 0x10f5d3490 <SignalDataStoreCommon/ObjCMessage.h:14:1, line:25:2> line:14:12 ObjCMessage
|
||||
# | |-super ObjCInterface 0x10f5d2c10 'ObjCBaseModel'
|
||||
# | |-ObjCImplementation 0x10f5d3f20 'ObjCMessage'
|
||||
# | |-ObjCPropertyDecl 0x10f5d35d0 <line:16:1, col:43> col:43 body 'NSString * _Nonnull':'NSString *' readonly nonatomic strong
|
||||
def process_objc_interface(
|
||||
namespace: Namespace, file_path: str, lines, decl_prefix, decl_remainder
|
||||
):
|
||||
# |-ObjCInterfaceDecl 0x10ab2fd58 </Users/matthew/code/workspace/ows/Signal-iOS-2/SignalDataStore/SignalDataStoreCommon/ObjCMessageWAuthor.h:13:1, line:26:2> line:13:12 ObjCMessageWAuthor
|
||||
# | |-super ObjCInterface 0x10ab2f490 'ObjCMessage'
|
||||
|
||||
super_class_name = None
|
||||
if lines.hasNext():
|
||||
line = lines.next()
|
||||
prefix, remainder = split_objc_ast_prefix(line)
|
||||
if len(prefix) > len(decl_prefix):
|
||||
splits = remainder.split(" ")
|
||||
if len(splits) >= 2 and splits[0] == "super":
|
||||
super_class_name = splits[-1].strip()
|
||||
if super_class_name.startswith("'") and super_class_name.endswith("'"):
|
||||
super_class_name = super_class_name[1:-1]
|
||||
|
||||
process_objc_class(
|
||||
namespace,
|
||||
file_path,
|
||||
lines,
|
||||
decl_prefix,
|
||||
decl_remainder,
|
||||
super_class_name=super_class_name,
|
||||
)
|
||||
|
||||
|
||||
# |-ObjCCategoryDecl 0x10f5d3d58 <SignalDataStoreCommon/ObjCMessage.m:18:1, line:22:2> line:18:12
|
||||
# | |-ObjCInterface 0x10f5d3490 'ObjCMessage'
|
||||
# | |-ObjCPropertyDecl 0x10f5d3e20 <line:20:1, col:43> col:43 ignore 'NSString * _Nonnull':'NSString *' readonly nonatomic strong
|
||||
# | `-ObjCMethodDecl 0x10f5d3e98 <col:43> col:43 implicit - ignore 'NSString * _Nonnull':'NSString *'
|
||||
def process_objc_category(namespace, file_path, lines, decl_prefix, decl_remainder):
|
||||
# |-ObjCCategoryDecl 0x1092f8440 <line:76:1, line:81:2> line:76:12 SomeCategory
|
||||
# | |-ObjCInterface 0x1092f5d58 'ObjCMessageWAuthor'
|
||||
# | |-ObjCCategoryImpl 0x1092f8608 'SomeCategory'
|
||||
# | |-ObjCPropertyDecl 0x1092f8508 <line:79:1, col:53> col:53 fakeProperty2 'NSString * _Nullable':'NSString *' readonly nonatomic
|
||||
# | `-ObjCMethodDecl 0x1092f8580 <col:53> col:53 implicit - fakeProperty2 'NSString * _Nullable':'NSString *'
|
||||
if not lines.hasNext():
|
||||
fail("Category missing interface.")
|
||||
line = lines.next()
|
||||
prefix, remainder = split_objc_ast_prefix(line)
|
||||
if len(prefix) <= len(decl_prefix):
|
||||
fail("Category missing interface.")
|
||||
class_name = remainder.split(" ")[-1]
|
||||
if class_name.startswith("'") and class_name.endswith("'"):
|
||||
class_name = class_name[1:-1]
|
||||
|
||||
process_objc_class(
|
||||
namespace,
|
||||
file_path,
|
||||
lines,
|
||||
decl_prefix,
|
||||
decl_remainder,
|
||||
custom_class_name=class_name,
|
||||
)
|
||||
|
||||
|
||||
# |-ObjCCategoryDecl 0x10f5d3d58 <SignalDataStoreCommon/ObjCMessage.m:18:1, line:22:2> line:18:12
|
||||
# | |-ObjCInterface 0x10f5d3490 'ObjCMessage'
|
||||
# | |-ObjCPropertyDecl 0x10f5d3e20 <line:20:1, col:43> col:43 ignore 'NSString * _Nonnull':'NSString *' readonly nonatomic strong
|
||||
# | `-ObjCMethodDecl 0x10f5d3e98 <col:43> col:43 implicit - ignore 'NSString * _Nonnull':'NSString *'
|
||||
def process_objc_implementation(
|
||||
namespace, file_path, lines, decl_prefix, decl_remainder
|
||||
):
|
||||
clazz = process_objc_class(namespace, file_path, lines, decl_prefix, decl_remainder)
|
||||
if clazz is not None:
|
||||
clazz.is_implemented = True
|
||||
|
||||
|
||||
def process_objc_protocol_decl(
|
||||
namespace, file_path, lines, decl_prefix, decl_remainder
|
||||
):
|
||||
clazz = process_objc_class(namespace, file_path, lines, decl_prefix, decl_remainder)
|
||||
if clazz is not None:
|
||||
clazz.is_implemented = True
|
||||
|
||||
|
||||
# |-ObjCCategoryDecl 0x10f5d3d58 <SignalDataStoreCommon/ObjCMessage.m:18:1, line:22:2> line:18:12
|
||||
# | |-ObjCInterface 0x10f5d3490 'ObjCMessage'
|
||||
# | |-ObjCPropertyDecl 0x10f5d3e20 <line:20:1, col:43> col:43 ignore 'NSString * _Nonnull':'NSString *' readonly nonatomic strong
|
||||
# | `-ObjCMethodDecl 0x10f5d3e98 <col:43> col:43 implicit - ignore 'NSString * _Nonnull':'NSString *'
|
||||
def process_objc_class(
|
||||
namespace,
|
||||
file_path,
|
||||
lines,
|
||||
decl_prefix,
|
||||
decl_remainder,
|
||||
custom_class_name=None,
|
||||
super_class_name=None,
|
||||
):
|
||||
if custom_class_name is not None:
|
||||
class_name = custom_class_name
|
||||
else:
|
||||
class_name = decl_remainder.split(" ")[-1]
|
||||
|
||||
clazz = namespace.upsert_class(class_name)
|
||||
|
||||
if super_class_name is not None:
|
||||
if clazz.super_class_name is None:
|
||||
clazz.super_class_name = super_class_name
|
||||
elif clazz.super_class_name != super_class_name:
|
||||
fail(
|
||||
"super_class_name does not match:",
|
||||
clazz.super_class_name,
|
||||
super_class_name,
|
||||
)
|
||||
|
||||
while lines.hasNext():
|
||||
line = lines.next()
|
||||
prefix, remainder = split_objc_ast_prefix(line)
|
||||
if len(prefix) <= len(decl_prefix):
|
||||
# Declaration is over.
|
||||
return clazz
|
||||
|
||||
line = lines.popNext()
|
||||
|
||||
# | |-ObjCPropertyDecl 0x10f5d2d40 <line:19:1, col:43> col:43 uniqueId 'NSString * _Nonnull':'NSString *' readonly nonatomic
|
||||
|
||||
"""
|
||||
TODO: We face interesting choices about how to process:
|
||||
|
||||
* properties
|
||||
* private properties
|
||||
* properties with renamed ivars
|
||||
* ivars without properties
|
||||
* properties not backed by ivars (e.g. actually accessors).
|
||||
"""
|
||||
if remainder.startswith("ObjCPropertyDecl "):
|
||||
process_objc_property(clazz, prefix, file_path, line, remainder)
|
||||
elif remainder.startswith("ObjCPropertyImplDecl "):
|
||||
process_objc_property_impl(clazz, prefix, file_path, line, remainder)
|
||||
elif remainder.startswith("ObjCMethodDecl "):
|
||||
process_objc_method_decl(clazz, prefix, file_path, line, remainder)
|
||||
elif remainder.startswith("ObjCProtocol "):
|
||||
process_objc_protocol(namespace, clazz, prefix, file_path, line, remainder)
|
||||
|
||||
return clazz
|
||||
|
||||
|
||||
process_objc_method_decl_regex = re.compile(r" - (sdsFinalize[^ ]*?) 'void'$")
|
||||
|
||||
|
||||
def process_objc_method_decl(clazz, prefix, file_path, line, remainder):
|
||||
match = process_objc_method_decl_regex.search(remainder)
|
||||
if match is None:
|
||||
return
|
||||
method_name = match.group(1).strip()
|
||||
clazz.finalize_method_name = method_name
|
||||
|
||||
|
||||
# | |-ObjCProtocol 0x7f879888b8a8 'AppContext'
|
||||
process_objc_protocol_regex = re.compile(r" '([^']+)'$")
|
||||
|
||||
|
||||
def process_objc_protocol(namespace, clazz, prefix, file_path, line, remainder):
|
||||
match = process_objc_protocol_regex.search(remainder)
|
||||
if match is None:
|
||||
return
|
||||
protocol_name = match.group(1).strip()
|
||||
clazz.inherit_from_protocol(namespace, protocol_name)
|
||||
|
||||
|
||||
# | |-ObjCPropertyImplDecl 0x1092f6d68 <col:1, col:13> col:13 someSynthesizedProperty synthesize
|
||||
# | |-ObjCPropertyImplDecl 0x1092f6f18 <col:1, col:35> col:13 someRenamedProperty synthesize
|
||||
# | |-ObjCPropertyImplDecl 0x1092f7698 <<invalid sloc>, col:53> <invalid sloc> author synthesize
|
||||
# | `-ObjCPropertyImplDecl 0x1092f77f8 <<invalid sloc>, col:53> <invalid sloc> somePrivateOptionalString synthesize
|
||||
#
|
||||
# ObjCPropertyDecl 0x7fc37e08f800 <line:37:1, col:28> col:28 shouldThreadBeVisible 'int' assign readwrite nonatomic unsafe_unretained
|
||||
process_objc_property_impl_regex = re.compile(r"^.+ ([^ ]+) synthesize$")
|
||||
|
||||
|
||||
def process_objc_property_impl(clazz, prefix, file_path, line, remainder):
|
||||
match = process_objc_property_impl_regex.search(remainder)
|
||||
if match is None:
|
||||
print("file_path:", file_path)
|
||||
fail("Could not match line:", line)
|
||||
property_name = match.group(1).strip()
|
||||
property = clazz.get_property(property_name)
|
||||
if property is None:
|
||||
if clazz.name == "AppDelegate" and property_name == "window":
|
||||
# We can't parse properties synthesized locally but
|
||||
# declared in a protocol defined in the iOS frameworks.
|
||||
# So, special case these propert(y/ies) - we don't need
|
||||
# to handle them.
|
||||
return
|
||||
|
||||
print("file_path:", file_path)
|
||||
print("line:", line)
|
||||
print("\t", "clazz", clazz.name, clazz.counter)
|
||||
print("\t", "property_name", property_name)
|
||||
for name in clazz.property_names():
|
||||
print("\t\t", name)
|
||||
fail("Can't find property:", property_name)
|
||||
else:
|
||||
property.is_synthesized = True
|
||||
|
||||
|
||||
# | |-ObjCPropertyDecl 0x11250fd40 <line:19:1, col:43> col:43 uniqueId 'NSString * _Nonnull':'NSString *' readonly nonatomic
|
||||
# | |-ObjCPropertyDecl 0x116afde80 <line:15:1, col:38> col:38 isUnread 'BOOL':'signed char' readonly nonatomic
|
||||
#
|
||||
# | |-ObjCPropertyDecl 0x7f8157089a00 <line:37:1, col:28> col:28 shouldThreadBeVisible 'int' assign readwrite nonatomic unsafe_unretained
|
||||
#
|
||||
# | |-ObjCPropertyDecl 0x7faf139af8e0 <line:37:1, col:28> col:28 shouldThreadBeVisible 'BOOL':'bool' assign readwrite nonatomic unsafe_unretained
|
||||
# | |-ObjCPropertyDecl 0x7f879889f460 <line:46:1, col:40> col:40 mainWindow 'UIWindow * _Nullable':'UIWindow *' readwrite atomic strong
|
||||
process_objc_property_regex = re.compile(r"^.+<.+> col:\d+(.+?)'(.+?)'(:'(.+)')?(.+)$")
|
||||
|
||||
|
||||
# This convenience function handles None results and strips.
|
||||
def get_match_group(match, index):
|
||||
group = match.group(index)
|
||||
if group is None:
|
||||
return ""
|
||||
return group.strip()
|
||||
|
||||
|
||||
def process_objc_property(clazz, prefix, file_path, line, remainder):
|
||||
|
||||
match = process_objc_property_regex.search(remainder)
|
||||
if match is None:
|
||||
print("file_path:", file_path)
|
||||
print("remainder:", remainder)
|
||||
fail("Could not match line:", line)
|
||||
property_name = match.group(1).strip()
|
||||
property_type_1 = get_match_group(match, 2)
|
||||
property_type_2 = get_match_group(match, 4)
|
||||
property_keywords = match.group(5).strip().split(" ")
|
||||
|
||||
is_optional = (property_type_2 + " _Nullable") == property_type_1
|
||||
is_readonly = "readonly" in property_keywords
|
||||
|
||||
property_type = property_type_2
|
||||
if len(property_type_2) < 1:
|
||||
property_type = property_type_1
|
||||
|
||||
primitive_types = ("BOOL", "NSInteger", "NSUInteger", "uint64_t", "int64_t")
|
||||
if property_type_1 in primitive_types:
|
||||
property_type = property_type_1
|
||||
|
||||
property = clazz.get_property(property_name)
|
||||
if property is None:
|
||||
|
||||
property = ParsedProperty(property_name, property_type, is_optional)
|
||||
clazz.add_property(property)
|
||||
else:
|
||||
if property.name != property_name:
|
||||
fail("Property names don't match", property.name, property_name)
|
||||
if property.is_optional != is_optional:
|
||||
if clazz.name.startswith("DD"):
|
||||
# CocoaLumberjack has nullability consistency issues.
|
||||
# Ignore them.
|
||||
return
|
||||
print("file_path:", file_path)
|
||||
print("clazz:", clazz.name)
|
||||
fail("Property is_optional don't match", property_name)
|
||||
if property.objc_type != property_type:
|
||||
# There's a common pattern of using a mutable private property
|
||||
# and exposing a non-mutable public property to prevent
|
||||
# external modification of the property.
|
||||
if (
|
||||
property_type.startswith("NSMutable")
|
||||
and property.objc_type == "NS" + property_type[len("NSMutable") :]
|
||||
):
|
||||
property.objc_type = property_type
|
||||
else:
|
||||
print("file_path:", file_path)
|
||||
print("remainder:", remainder)
|
||||
print("property.objc_type:", property.objc_type)
|
||||
print("property_type:", property_type)
|
||||
print("property_name:", property_name)
|
||||
fail("Property types don't match", property.objc_type, property_type)
|
||||
|
||||
if not is_readonly:
|
||||
property.is_not_readonly = True
|
||||
|
||||
|
||||
def emit_output(file_path, namespace):
|
||||
classes = []
|
||||
for class_name in namespace.class_names():
|
||||
clazz = namespace.upsert_class(class_name)
|
||||
if not clazz.is_implemented:
|
||||
if not class_name.startswith("NS"):
|
||||
pass
|
||||
continue
|
||||
|
||||
properties = []
|
||||
|
||||
for property in clazz.all_properties():
|
||||
if not property.is_synthesized:
|
||||
continue
|
||||
|
||||
property_dict = {
|
||||
"name": property.name,
|
||||
"objc_type": property.objc_type,
|
||||
"is_optional": property.is_optional,
|
||||
"class_name": class_name,
|
||||
# This might not be necessary, thanks to is_synthesized
|
||||
# 'is_readonly': (not property.is_not_readonly),
|
||||
}
|
||||
|
||||
properties.append(property_dict)
|
||||
|
||||
class_dict = {
|
||||
"name": class_name,
|
||||
"properties": properties,
|
||||
"filepath": sds_common.sds_to_relative_path(file_path),
|
||||
"finalize_method_name": clazz.finalize_method_name,
|
||||
}
|
||||
if clazz.super_class_name is not None:
|
||||
class_dict["super_class_name"] = clazz.super_class_name
|
||||
classes.append(class_dict)
|
||||
|
||||
enums = enum_type_map
|
||||
|
||||
root = {
|
||||
"classes": classes,
|
||||
"enums": enums,
|
||||
}
|
||||
|
||||
return json.dumps(root, sort_keys=True, indent=4)
|
||||
|
||||
|
||||
# We need to include search paths for every
|
||||
# non-framework header.
|
||||
def find_header_include_paths(include_path):
|
||||
result = []
|
||||
|
||||
def add_dir_if_has_header(dir_path):
|
||||
# Only include subdirectories with header files.
|
||||
for filename in os.listdir(dir_path):
|
||||
if filename.endswith(".h"):
|
||||
result.append("-I" + dir_path)
|
||||
break
|
||||
|
||||
# Add root if necessary.
|
||||
add_dir_if_has_header(include_path)
|
||||
|
||||
for rootdir, dirnames, filenames in os.walk(include_path):
|
||||
for dirname in dirnames:
|
||||
dir_path = os.path.abspath(os.path.join(rootdir, dirname))
|
||||
add_dir_if_has_header(dir_path)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# --- Modules
|
||||
|
||||
# Framework compilation gathers all framework headers
|
||||
# in an include directory with the framework name, so
|
||||
# that headers can be included like so:
|
||||
#
|
||||
# #import <framework_name/header_name.h>
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# #import <SignalServiceKit/OWSFailedAttachmentDownloadsJob.h>
|
||||
#
|
||||
# To simulate this, we walk the Pods directory and copy
|
||||
# headers into per-framework directories.
|
||||
|
||||
|
||||
def copy_module_headers(src_dir_path, module_name, module_header_dir_path):
|
||||
dst_dir_path = os.path.join(module_header_dir_path, module_name)
|
||||
os.mkdir(dst_dir_path)
|
||||
|
||||
for rootdir, dirnames, filenames in os.walk(src_dir_path):
|
||||
for filename in filenames:
|
||||
if not filename.endswith(".h"):
|
||||
continue
|
||||
src_file_path = os.path.abspath(os.path.join(rootdir, filename))
|
||||
dst_file_path = os.path.abspath(os.path.join(dst_dir_path, filename))
|
||||
shutil.copyfile(src_file_path, dst_file_path)
|
||||
|
||||
|
||||
def gather_pod_headers(pods_dir_path, module_header_dir_path):
|
||||
|
||||
for dirname in os.listdir(pods_dir_path):
|
||||
src_dir_path = os.path.join(pods_dir_path, dirname)
|
||||
if not os.path.isdir(src_dir_path):
|
||||
continue
|
||||
|
||||
copy_module_headers(src_dir_path, dirname, module_header_dir_path)
|
||||
|
||||
|
||||
def gather_module_headers(pods_dir_path):
|
||||
# Make a temp directory to gather framework headers in.
|
||||
module_header_dir_path = tempfile.mkdtemp()
|
||||
|
||||
gather_pod_headers(pods_dir_path, module_header_dir_path)
|
||||
|
||||
for project_name in (
|
||||
"SignalServiceKit",
|
||||
"Signal",
|
||||
):
|
||||
src_dir_path = os.path.join(git_repo_path, project_name)
|
||||
copy_module_headers(src_dir_path, project_name, module_header_dir_path)
|
||||
|
||||
return module_header_dir_path
|
||||
|
||||
|
||||
# --- PCH
|
||||
|
||||
|
||||
def get_pch_include(file_path):
|
||||
ssk_path = os.path.join(git_repo_path, "SignalServiceKit") + os.sep
|
||||
s_path = os.path.join(git_repo_path, "Signal") + os.sep
|
||||
sae_path = os.path.join(git_repo_path, "SignalShareExtension") + os.sep
|
||||
if file_path.startswith(ssk_path):
|
||||
return os.path.join(
|
||||
git_repo_path, "SignalServiceKit/SignalServiceKit-prefix.pch"
|
||||
)
|
||||
elif file_path.startswith(s_path):
|
||||
return os.path.join(git_repo_path, "Signal/Signal-Prefix.pch")
|
||||
elif file_path.startswith(sae_path):
|
||||
return os.path.join(
|
||||
git_repo_path, "SignalShareExtension/SignalShareExtension-Prefix.pch"
|
||||
)
|
||||
else:
|
||||
fail("Couldn't determine .pch for file:", file_path)
|
||||
|
||||
|
||||
# --- Processing
|
||||
|
||||
|
||||
def process_objc(
|
||||
file_path: str,
|
||||
iphoneos_sdk_path: str,
|
||||
swift_bridging_path: str,
|
||||
module_header_dir_path: str,
|
||||
header_include_paths: list[str],
|
||||
) -> None:
|
||||
pch_include = get_pch_include(file_path)
|
||||
|
||||
# These clang args can be found by building our workspace and looking at how XCode invokes clang.
|
||||
clang_args = "-arch arm64 -fmessage-length=0 -fdiagnostics-show-note-include-stack -fmacro-backtrace-limit=0 -std=gnu11 -fobjc-arc -fobjc-weak -fmodules -gmodules -fmodules-prune-interval=86400 -fmodules-prune-after=345600 -Wnon-modular-include-in-framework-module -Werror=non-modular-include-in-framework-module -fapplication-extension -Wno-trigraphs -fpascal-strings -O0 -fno-common -Wno-missing-field-initializers -Wno-missing-prototypes -Werror=return-type -Wdocumentation -Wunreachable-code -Wno-implicit-atomic-properties -Werror=deprecated-objc-isa-usage -Wno-objc-interface-ivars -Werror=objc-root-class -Wno-arc-repeated-use-of-weak -Wimplicit-retain-self -Wduplicate-method-match -Wno-missing-braces -Wparentheses -Wswitch -Wunused-function -Wno-unused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wuninitialized -Wconditional-uninitialized -Wno-unknown-pragmas -Wno-shadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum-conversion -Wno-float-conversion -Wnon-literal-null-conversion -Wobjc-literal-conversion -Wshorten-64-to-32 -Wpointer-sign -Wno-newline-eof -Wno-selector -Wno-strict-selector-match -Wundeclared-selector -Wdeprecated-implementations".split(
|
||||
" "
|
||||
)
|
||||
|
||||
# TODO: We'll never repro the correct search paths, so clang will always emit errors.
|
||||
# We'll want to ignore these errors without silently failing.
|
||||
command = (
|
||||
[
|
||||
"clang",
|
||||
"-x",
|
||||
"objective-c",
|
||||
"-Xclang",
|
||||
"-ast-dump",
|
||||
"-fobjc-arc",
|
||||
]
|
||||
+ clang_args
|
||||
+ [
|
||||
"-isysroot",
|
||||
iphoneos_sdk_path,
|
||||
]
|
||||
+ header_include_paths
|
||||
+ [
|
||||
("-I" + module_header_dir_path),
|
||||
("-I" + swift_bridging_path),
|
||||
"-include",
|
||||
pch_include,
|
||||
file_path,
|
||||
]
|
||||
)
|
||||
|
||||
exit_code, output, error_output = ows_getoutput(command)
|
||||
|
||||
output = output.strip()
|
||||
raw_ast = output
|
||||
|
||||
namespace = Namespace()
|
||||
|
||||
process_objc_ast(namespace, file_path, raw_ast)
|
||||
|
||||
output = emit_output(file_path, namespace)
|
||||
|
||||
parsed_file_path = file_path + sds_common.SDS_JSON_FILE_EXTENSION
|
||||
with open(parsed_file_path, "wt") as f:
|
||||
f.write(output)
|
||||
|
||||
|
||||
def process_file(
|
||||
file_path,
|
||||
iphoneos_sdk_path,
|
||||
swift_bridging_path,
|
||||
module_header_dir_path,
|
||||
header_include_paths,
|
||||
):
|
||||
filename = os.path.basename(file_path)
|
||||
|
||||
# TODO: Fix this file
|
||||
if filename == "OWSDisappearingMessageFinderTest.m":
|
||||
return
|
||||
|
||||
_, file_extension = os.path.splitext(filename)
|
||||
if file_extension == ".m":
|
||||
process_objc(
|
||||
file_path,
|
||||
iphoneos_sdk_path,
|
||||
swift_bridging_path,
|
||||
module_header_dir_path,
|
||||
header_include_paths,
|
||||
)
|
||||
|
||||
|
||||
# ---
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Parse Objective-C AST.")
|
||||
parser.add_argument(
|
||||
"--src-path", required=True, help="used to specify a path to process."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--swift-bridging-path",
|
||||
required=True,
|
||||
help="used to specify a path to process.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
src_path = os.path.abspath(args.src_path)
|
||||
swift_bridging_path = os.path.abspath(args.swift_bridging_path)
|
||||
module_header_dir_path = gather_module_headers("Pods")
|
||||
|
||||
command = [
|
||||
"xcrun",
|
||||
"--show-sdk-path",
|
||||
"--sdk",
|
||||
"iphoneos",
|
||||
]
|
||||
exit_code, output, error_output = ows_getoutput(command)
|
||||
if int(exit_code) != 0:
|
||||
fail("Could not find iOS SDK.")
|
||||
iphoneos_sdk_path = output.strip()
|
||||
|
||||
header_include_paths = []
|
||||
header_include_paths.extend(find_header_include_paths("SignalServiceKit"))
|
||||
|
||||
# SDS code generation uses clang to parse the AST of Objective-C files.
|
||||
# We're parsing these files outside the context of an XCode workspace,
|
||||
# so many things won't work - unless do some legwork.
|
||||
#
|
||||
# * Compiling of dependencies.
|
||||
# * Workspace include and framework search paths.
|
||||
# * Auto-generated files, like -Swift.h bridging headers.
|
||||
# * .pch files.
|
||||
|
||||
print(f"Parsing Obj-C files in {src_path}...")
|
||||
if os.path.isfile(src_path):
|
||||
process_file(
|
||||
src_path, iphoneos_sdk_path, swift_bridging_path, module_header_dir_path
|
||||
)
|
||||
else:
|
||||
# First clear out existing .sdsjson files.
|
||||
for rootdir, dirnames, filenames in os.walk(src_path):
|
||||
for filename in filenames:
|
||||
if filename.endswith(sds_common.SDS_JSON_FILE_EXTENSION):
|
||||
file_path = os.path.abspath(os.path.join(rootdir, filename))
|
||||
os.remove(file_path)
|
||||
|
||||
for rootdir, dirnames, filenames in os.walk(src_path):
|
||||
for filename in filenames:
|
||||
file_path = os.path.abspath(os.path.join(rootdir, filename))
|
||||
process_file(
|
||||
file_path,
|
||||
iphoneos_sdk_path,
|
||||
swift_bridging_path,
|
||||
module_header_dir_path,
|
||||
header_include_paths,
|
||||
)
|
||||
|
||||
|
||||
# TODO: We can't access ivars from Swift without public property accessors.
|
||||
# TODO: We can't access private properties from Swift without public property accessors.
|
||||
# We could generate "SDS Private" headers that exposes these properties, but only if they're backed by an ivar.
|
||||
# TODO: Preprocessor macros & directives won't work properly with this AST parser.
|
222
Scripts/sds_codegen/sds_parse_swift_bridging.py
Executable file
222
Scripts/sds_codegen/sds_parse_swift_bridging.py
Executable file
|
@ -0,0 +1,222 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import datetime
|
||||
import argparse
|
||||
import re
|
||||
import json
|
||||
import sds_common
|
||||
from sds_common import fail
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
|
||||
# We need to generate fake -Swift.h bridging headers that declare the Swift
|
||||
# types that our Objective-C files might use. This script does that.
|
||||
|
||||
|
||||
def ows_getoutput(cmd):
|
||||
proc = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
stdout, stderr = proc.communicate()
|
||||
|
||||
return proc.returncode, stdout, stderr
|
||||
|
||||
|
||||
class Namespace:
|
||||
def __init__(self):
|
||||
self.swift_protocol_names = []
|
||||
self.swift_class_names = []
|
||||
|
||||
|
||||
def parse_swift_ast(file_path, namespace, ast):
|
||||
json_data = json.loads(ast)
|
||||
|
||||
json_maps = json_data.get("key.substructure")
|
||||
if json_maps is None:
|
||||
return
|
||||
|
||||
for json_map in json_maps:
|
||||
kind = json_map.get("key.kind")
|
||||
if kind is None:
|
||||
continue
|
||||
elif kind == "source.lang.swift.decl.protocol":
|
||||
# "key.kind" : "source.lang.swift.decl.protocol",
|
||||
# "key.length" : 1067,
|
||||
# "key.name" : "TypingIndicators",
|
||||
# "key.namelength" : 16,
|
||||
# "key.nameoffset" : 135,
|
||||
# "key.offset" : 126,
|
||||
# "key.runtime_name" : "OWSTypingIndicators",
|
||||
name = json_map.get("key.runtime_name")
|
||||
if name is None or len(name) < 1 or name.startswith("_"):
|
||||
name = json_map.get("key.name")
|
||||
if name is None or len(name) < 1:
|
||||
fail("protocol is missing name.")
|
||||
continue
|
||||
if name.startswith("_"):
|
||||
continue
|
||||
namespace.swift_protocol_names.append(name)
|
||||
elif kind == "source.lang.swift.decl.class":
|
||||
# "key.kind" : "source.lang.swift.decl.class",
|
||||
# "key.length" : 15057,
|
||||
# "key.name" : "TypingIndicatorsImpl",
|
||||
# "key.namelength" : 20,
|
||||
# "key.nameoffset" : 1251,
|
||||
# "key.offset" : 1245,
|
||||
# "key.runtime_name" : "OWSTypingIndicatorsImpl",
|
||||
name = json_map.get("key.runtime_name")
|
||||
if name is None or len(name) < 1 or name.startswith("_"):
|
||||
name = json_map.get("key.name")
|
||||
if name is None or len(name) < 1:
|
||||
fail("class is missing name.")
|
||||
continue
|
||||
if name.startswith("_"):
|
||||
continue
|
||||
namespace.swift_class_names.append(name)
|
||||
|
||||
|
||||
def process_file(file_path, namespace):
|
||||
filename = os.path.basename(file_path)
|
||||
if not filename.endswith(".swift"):
|
||||
return
|
||||
if filename == "EmojiWithSkinTones+String.swift":
|
||||
return
|
||||
|
||||
command = ["sourcekitten", "structure", "--file", file_path]
|
||||
# for part in command:
|
||||
# print '\t', part
|
||||
# command = ' '.join(command).strip()
|
||||
# print 'command', command
|
||||
# output = commands.getoutput(command)
|
||||
|
||||
# command = ' '.join(command).strip()
|
||||
# print 'command', command
|
||||
exit_code, output, error_output = ows_getoutput(command)
|
||||
if exit_code != 0:
|
||||
print("exit_code:", exit_code)
|
||||
fail("Are you missing sourcekitten? Install with homebrew?")
|
||||
if len(error_output.strip()) > 0:
|
||||
print("error_output:", error_output)
|
||||
# print 'output:', len(output)
|
||||
|
||||
# exit(1)
|
||||
|
||||
output = output.strip()
|
||||
# print 'output', output
|
||||
|
||||
parse_swift_ast(file_path, namespace, output)
|
||||
|
||||
|
||||
def generate_swift_bridging_header(namespace, swift_bridging_path):
|
||||
|
||||
output = []
|
||||
|
||||
for name in namespace.swift_protocol_names:
|
||||
output.append(
|
||||
"""
|
||||
@protocol %s
|
||||
@end
|
||||
"""
|
||||
% (name,)
|
||||
)
|
||||
|
||||
for name in namespace.swift_class_names:
|
||||
output.append(
|
||||
"""
|
||||
@interface %s : NSObject
|
||||
@end
|
||||
"""
|
||||
% (name,)
|
||||
)
|
||||
|
||||
output = "\n".join(output).strip()
|
||||
if len(output) < 1:
|
||||
return
|
||||
|
||||
header = """//
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// NOTE: This file is generated by %s.
|
||||
// Do not manually edit it, instead run `sds_codegen.sh`.
|
||||
|
||||
""" % (
|
||||
sds_common.pretty_module_path(__file__),
|
||||
)
|
||||
output = (header + output).strip()
|
||||
|
||||
# print 'output:', output[:500]
|
||||
|
||||
output = sds_common.clean_up_generated_swift(output)
|
||||
|
||||
# print 'output:', output[:500]
|
||||
|
||||
# print 'output', output
|
||||
|
||||
parent_dir_path = os.path.dirname(swift_bridging_path)
|
||||
# print 'parent_dir_path', parent_dir_path
|
||||
if not os.path.exists(parent_dir_path):
|
||||
os.makedirs(parent_dir_path)
|
||||
|
||||
print("Writing:", swift_bridging_path)
|
||||
with open(swift_bridging_path, "wt") as f:
|
||||
f.write(output)
|
||||
|
||||
|
||||
# ---
|
||||
|
||||
|
||||
def process_dir(src_dir_path, dir_name, dst_dir_path):
|
||||
namespace = Namespace()
|
||||
|
||||
dir_path = os.path.abspath(os.path.join(src_dir_path, dir_name))
|
||||
|
||||
file_paths = []
|
||||
for rootdir, dirnames, filenames in os.walk(dir_path):
|
||||
for filename in filenames:
|
||||
file_path = os.path.abspath(os.path.join(rootdir, filename))
|
||||
file_paths.append(file_path)
|
||||
|
||||
print(f"Found {len(file_paths)} files in {dir_path}")
|
||||
for idx, file_path in enumerate(file_paths):
|
||||
process_file(file_path, namespace)
|
||||
if idx % 100 == 99:
|
||||
print(f"... {idx+1} / {len(file_paths)}")
|
||||
|
||||
bridging_header_path = os.path.abspath(
|
||||
os.path.join(dst_dir_path, dir_name, dir_name + "-Swift.h")
|
||||
)
|
||||
generate_swift_bridging_header(namespace, bridging_header_path)
|
||||
|
||||
|
||||
# ---
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser(description="Parse Objective-C AST.")
|
||||
parser.add_argument(
|
||||
"--src-path", required=True, help="used to specify a path to process."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--swift-bridging-path",
|
||||
required=True,
|
||||
help="used to specify a path to process.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
src_dir_path = os.path.abspath(args.src_path)
|
||||
swift_bridging_path = os.path.abspath(args.swift_bridging_path)
|
||||
|
||||
if os.path.exists(swift_bridging_path):
|
||||
shutil.rmtree(swift_bridging_path)
|
||||
|
||||
process_dir(src_dir_path, "SignalServiceKit", swift_bridging_path)
|
11
Scripts/sds_codegen/sds_regenerate.sh
Executable file
11
Scripts/sds_codegen/sds_regenerate.sh
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -eux
|
||||
|
||||
# We generate Swift extensions to handle serialization, etc. for models.
|
||||
RECORD_TYPE_SWIFT="SignalServiceKit/Storage/Database/SDSRecordType.swift"
|
||||
RECORD_TYPE_JSON="Scripts/sds_codegen/sds_config/sds_record_type_map.json"
|
||||
CONFIG_JSON="Scripts/sds_codegen/sds_config/sds-config.json"
|
||||
PROPERTY_ORDER_JSON="Scripts/sds_codegen/sds_config/sds-property_order.json"
|
||||
GENERATE_ARGS="--record-type-swift-path $RECORD_TYPE_SWIFT --record-type-json-path $RECORD_TYPE_JSON --config-json-path $CONFIG_JSON --property-order-json-path $PROPERTY_ORDER_JSON"
|
||||
Scripts/sds_codegen/sds_generate.py --src-path SignalServiceKit/ --search-path . $GENERATE_ARGS
|
13
Scripts/set_release_notes
Executable file
13
Scripts/set_release_notes
Executable file
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
BASE_DIR=$(git rev-parse --show-toplevel)
|
||||
METADATA_ROOT="$BASE_DIR/fastlane/metadata"
|
||||
|
||||
# Open the editor for entering the changelog
|
||||
${VISUAL:-${EDITOR:-vi}} "${METADATA_ROOT}/en-US/release_notes.txt"
|
||||
|
||||
"${BASE_DIR}"/Scripts/translation/push-metadata-source
|
||||
"${BASE_DIR}"/Scripts/translation/pull-metadata-translations
|
34
Scripts/setup_private_pods
Executable file
34
Scripts/setup_private_pods
Executable file
|
@ -0,0 +1,34 @@
|
|||
#!/bin/bash
|
||||
# shellcheck disable=SC2164 # pushd/popd failure
|
||||
|
||||
if [ ! -d Signal.xcodeproj ]; then
|
||||
echo "error: Must be run from the repository root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "${USE_PRIVATE_PODS+x}" ]; then
|
||||
echo "Using private pods"
|
||||
if [ -e "Pods/.git" ]; then
|
||||
pushd Pods
|
||||
# FIXME: Possible failure modes here:
|
||||
# - You have a Signal-Pods-Private remote not named 'private'.
|
||||
# - You have a remote named 'private' that points somewhere else.
|
||||
if ! git remote -v | grep -qi 'signal-pods-private'; then
|
||||
echo "Adding private pods remote"
|
||||
git remote add private git@github.com:signalapp/Signal-Pods-Private.git
|
||||
fi
|
||||
git fetch private
|
||||
popd
|
||||
else
|
||||
echo "Cloning private pods repo"
|
||||
git clone git@github.com:signalapp/Signal-Pods-Private.git -o private Pods
|
||||
# Add the public repo as a remote explicitly.
|
||||
# This is what would happen if you did `git submodule update --init` first,
|
||||
# and it helps avoid confusing Jenkins for the nightly builder.
|
||||
git -C Pods remote add origin git@github.com:signalapp/Signal-Pods.git
|
||||
# Not strictly necessary, but consistent with doing `git submodule update --init` first.
|
||||
git submodule absorbgitdirs Pods
|
||||
fi
|
||||
else
|
||||
echo "Not using private pods. Define USE_PRIVATE_PODS in your environment to enable."
|
||||
fi
|
184
Scripts/sort-Xcode-project-file
Executable file
184
Scripts/sort-Xcode-project-file
Executable file
|
@ -0,0 +1,184 @@
|
|||
#!/usr/bin/env perl
|
||||
|
||||
# Originally copied from: https://github.com/WebKit/WebKit/blob/c2b3ade28fbb3d621dd5a6156889bd16b3f90f39/Tools/Scripts/sort-Xcode-project-file
|
||||
#
|
||||
# Copyright (C) 2007-2021 Apple Inc. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. Neither the name of Apple Inc. ("Apple") nor the names of
|
||||
# its contributors may be used to endorse or promote products derived
|
||||
# from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
|
||||
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
# Script to sort "children" and "files" sections in Xcode project.pbxproj files
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Basename;
|
||||
use File::Spec;
|
||||
use File::Temp qw(tempfile);
|
||||
use Getopt::Long;
|
||||
|
||||
sub sortChildrenByFileName($$);
|
||||
sub sortFilesByFileName($$);
|
||||
|
||||
# Some files without extensions, so they can sort with the other files.
|
||||
# Otherwise, names without extensions are assumed to be groups or directories and sorted last.
|
||||
my %isFile = map { $_ => 1 } qw(
|
||||
create_hash_table
|
||||
);
|
||||
|
||||
my $printWarnings = 1;
|
||||
my $showHelp;
|
||||
|
||||
my $getOptionsResult = GetOptions(
|
||||
'h|help' => \$showHelp,
|
||||
'w|warnings!' => \$printWarnings,
|
||||
);
|
||||
|
||||
if (scalar(@ARGV) == 0 && !$showHelp) {
|
||||
print STDERR "ERROR: No Xcode project files (project.pbxproj) listed on command-line.\n";
|
||||
undef $getOptionsResult;
|
||||
}
|
||||
|
||||
if (!$getOptionsResult || $showHelp) {
|
||||
print STDERR <<__END__;
|
||||
Usage: @{[ basename($0) ]} [options] path/to/project.pbxproj [path/to/project.pbxproj ...]
|
||||
-h|--help show this help message
|
||||
-w|--[no-]warnings show or suppress warnings (default: show warnings)
|
||||
__END__
|
||||
exit 1;
|
||||
}
|
||||
|
||||
for my $projectFile (@ARGV) {
|
||||
if (basename($projectFile) =~ /\.xcodeproj$/) {
|
||||
$projectFile = File::Spec->catfile($projectFile, "project.pbxproj");
|
||||
}
|
||||
|
||||
if (basename($projectFile) ne "project.pbxproj") {
|
||||
print STDERR "WARNING: Not an Xcode project file: $projectFile\n" if $printWarnings;
|
||||
next;
|
||||
}
|
||||
|
||||
# Grab the mainGroup for the project file.
|
||||
my $mainGroup = "";
|
||||
open(IN, "< $projectFile") || die "Could not open $projectFile: $!";
|
||||
while (my $line = <IN>) {
|
||||
$mainGroup = $2 if $line =~ m#^(\s*)mainGroup = ([0-9A-F]{24}( /\* .+ \*/)?);$#;
|
||||
}
|
||||
close(IN);
|
||||
|
||||
my ($OUT, $tempFileName) = tempfile(
|
||||
basename($projectFile) . "-XXXXXXXX",
|
||||
DIR => dirname($projectFile),
|
||||
UNLINK => 0,
|
||||
);
|
||||
|
||||
# Clean up temp file in case of die()
|
||||
$SIG{__DIE__} = sub {
|
||||
close(IN);
|
||||
close($OUT);
|
||||
unlink($tempFileName);
|
||||
};
|
||||
|
||||
my @lastTwo = ();
|
||||
open(IN, "< $projectFile") || die "Could not open $projectFile: $!";
|
||||
while (my $line = <IN>) {
|
||||
if ($line =~ /^(\s*)files = \(\s*$/) {
|
||||
print $OUT $line;
|
||||
my $endMarker = $1 . ");";
|
||||
my @files;
|
||||
while (my $fileLine = <IN>) {
|
||||
if ($fileLine =~ /^\Q$endMarker\E\s*$/) {
|
||||
$endMarker = $fileLine;
|
||||
last;
|
||||
}
|
||||
push @files, $fileLine;
|
||||
}
|
||||
print $OUT sort sortFilesByFileName @files;
|
||||
print $OUT $endMarker;
|
||||
} elsif ($line =~ /^(\s*)children = \(\s*$/) {
|
||||
print $OUT $line;
|
||||
my $endMarker = $1 . ");";
|
||||
my @children;
|
||||
while (my $childLine = <IN>) {
|
||||
if ($childLine =~ /^\Q$endMarker\E\s*$/) {
|
||||
$endMarker = $childLine;
|
||||
last;
|
||||
}
|
||||
push @children, $childLine;
|
||||
}
|
||||
if ($lastTwo[0] =~ m#^\s+\Q$mainGroup\E = \{$#) {
|
||||
# Don't sort mainGroup
|
||||
print $OUT @children;
|
||||
} else {
|
||||
print $OUT sort sortChildrenByFileName @children;
|
||||
}
|
||||
print $OUT $endMarker;
|
||||
} else {
|
||||
print $OUT $line;
|
||||
}
|
||||
|
||||
push @lastTwo, $line;
|
||||
shift @lastTwo if scalar(@lastTwo) > 2;
|
||||
}
|
||||
close(IN);
|
||||
close($OUT);
|
||||
|
||||
unlink($projectFile) || die "Could not delete $projectFile: $!";
|
||||
rename($tempFileName, $projectFile) || die "Could not rename $tempFileName to $projectFile: $!";
|
||||
}
|
||||
|
||||
exit 0;
|
||||
|
||||
sub sortChildrenByFileName($$)
|
||||
{
|
||||
my ($a, $b) = @_;
|
||||
my $aFileName = $1 if $a =~ /^\s*[A-Z0-9]{24} \/\* (.+) \*\/,$/;
|
||||
my $bFileName = $1 if $b =~ /^\s*[A-Z0-9]{24} \/\* (.+) \*\/,$/;
|
||||
my $aSuffix = $1 if $aFileName =~ m/\.([^.]+)$/;
|
||||
my $bSuffix = $1 if $bFileName =~ m/\.([^.]+)$/;
|
||||
my $aIsDirectory = !$aSuffix && !$isFile{$aFileName};
|
||||
my $bIsDirectory = !$bSuffix && !$isFile{$bFileName};
|
||||
return $bIsDirectory <=> $aIsDirectory if $aIsDirectory != $bIsDirectory;
|
||||
if ($aFileName =~ /^UnifiedSource\d+/ && $bFileName =~ /^UnifiedSource\d+/) {
|
||||
my $aNumber = $1 if $aFileName =~ /^UnifiedSource(\d+)/;
|
||||
my $bNumber = $1 if $bFileName =~ /^UnifiedSource(\d+)/;
|
||||
return $aNumber <=> $bNumber if $aNumber != $bNumber;
|
||||
}
|
||||
return lc($aFileName) cmp lc($bFileName) if lc($aFileName) ne lc($bFileName);
|
||||
return $aFileName cmp $bFileName;
|
||||
}
|
||||
|
||||
sub sortFilesByFileName($$)
|
||||
{
|
||||
my ($a, $b) = @_;
|
||||
my $aFileName = $1 if $a =~ /^\s*[A-Z0-9]{24} \/\* (.+) in /;
|
||||
my $bFileName = $1 if $b =~ /^\s*[A-Z0-9]{24} \/\* (.+) in /;
|
||||
if ($aFileName =~ /^UnifiedSource\d+/ && $bFileName =~ /^UnifiedSource\d+/) {
|
||||
my $aNumber = $1 if $aFileName =~ /^UnifiedSource(\d+)/;
|
||||
my $bNumber = $1 if $bFileName =~ /^UnifiedSource(\d+)/;
|
||||
return $aNumber <=> $bNumber if $aNumber != $bNumber;
|
||||
}
|
||||
return lc($aFileName) cmp lc($bFileName) if lc($aFileName) ne lc($bFileName);
|
||||
return $aFileName cmp $bFileName;
|
||||
}
|
242
Scripts/sqlclient
Executable file
242
Scripts/sqlclient
Executable file
|
@ -0,0 +1,242 @@
|
|||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import getpass
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
SIGNAL_BUNDLEID = "org.whispersystems.signal"
|
||||
SIGNAL_APPGROUP = "group.org.whispersystems.signal.group"
|
||||
SIGNAL_APPGROUP_STAGING = "group.org.whispersystems.signal.group.staging"
|
||||
|
||||
SIGNAL_DEBUG_PAYLOAD_NAME = "dbPayload.txt"
|
||||
SIGNAL_DEBUG_PAYLOAD_PASSPHRASE_KEY = "key"
|
||||
SIGNAL_FALLBACK_DATABASE_PATH = "grdb/signal.sqlite"
|
||||
|
||||
DB_BROWSER_FOR_SQLITE_BUNDLEID = "net.sourceforge.sqlitebrowser"
|
||||
|
||||
quietMode=False
|
||||
def failWithError(string):
|
||||
print("Error: " + string, file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
def printInfo(string = ""):
|
||||
if quietMode == False:
|
||||
print(string)
|
||||
|
||||
def runCommand(cmdList):
|
||||
result = subprocess.run(cmdList, text=True, capture_output=True)
|
||||
if result.returncode != 0:
|
||||
failWithError("Failed to run \"" + " ".join(cmdList) + "\". Status: " + str(result.returncode) + "\n" + result.stderr)
|
||||
return result.stdout
|
||||
|
||||
|
||||
class Simulator:
|
||||
def __init__(self, searchString, useStaging):
|
||||
|
||||
# Get JSON list of simulators matching searchString
|
||||
cmd = "xcrun simctl list -j devices " + searchString
|
||||
resultString = runCommand(cmd.split())
|
||||
simDict = json.loads(resultString)
|
||||
devicesByRuntime = simDict["devices"]
|
||||
|
||||
# Parse all candidates
|
||||
candidates = []
|
||||
for runtime, devices in devicesByRuntime.items():
|
||||
os = self.parseOSFromRuntime(runtime)
|
||||
for device in devices:
|
||||
udid = device.get("udid")
|
||||
rawDevice = device.get("deviceTypeIdentifier")
|
||||
name = device.get("name")
|
||||
if udid != None:
|
||||
deviceType = self.parseDeviceTypeFromRaw(rawDevice)
|
||||
candidates.append({"os": os, "type": deviceType, "udid": udid, "name": name})
|
||||
|
||||
# Select a candidate
|
||||
selectedCandidate = None
|
||||
|
||||
if len(candidates) == 0:
|
||||
failWithError("Could not find a \"" + searchString + "\" simulator")
|
||||
elif len(candidates) == 1:
|
||||
selectedCandidate = candidates[0]
|
||||
else:
|
||||
if quietMode:
|
||||
failWithError("Multiple simulator candidates. Interactive selection not supported in quiet mode")
|
||||
for idx, candidate in enumerate(candidates):
|
||||
printInfo("{}:\t{:40}\t{} {} ({})".format(idx, candidate["name"], candidate["type"], candidate["os"], candidate["udid"]))
|
||||
|
||||
while selectedCandidate == None:
|
||||
try:
|
||||
idx = int(input("Select a simulator: "))
|
||||
selectedCandidate = candidates[idx]
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
|
||||
self.udid = selectedCandidate["udid"]
|
||||
self.groupID = SIGNAL_APPGROUP_STAGING if useStaging else SIGNAL_APPGROUP
|
||||
self.groupContainer = self.fetchGroupContainer(self.udid, self.groupID)
|
||||
printInfo("Selected simulator: " + selectedCandidate["name"] + " (" + selectedCandidate["udid"] + ")")
|
||||
printInfo("Using groupID: " + self.groupID)
|
||||
printInfo()
|
||||
|
||||
def parseDebugPayload(self):
|
||||
path = self.groupContainer + "/" + SIGNAL_DEBUG_PAYLOAD_NAME
|
||||
try:
|
||||
fd = open(path, 'r')
|
||||
data = fd.read()
|
||||
payload = json.loads(data)
|
||||
return payload
|
||||
except IOError:
|
||||
return None
|
||||
|
||||
def databasePath(self):
|
||||
return (self.groupContainer + "/" + SIGNAL_FALLBACK_DATABASE_PATH)
|
||||
|
||||
def passphraseIfAvailable(self):
|
||||
debugPayload = self.parseDebugPayload()
|
||||
if debugPayload and SIGNAL_DEBUG_PAYLOAD_PASSPHRASE_KEY in debugPayload:
|
||||
return debugPayload[SIGNAL_DEBUG_PAYLOAD_PASSPHRASE_KEY]
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def parseOSFromRuntime(runtime):
|
||||
lastPeriodIdx = runtime.rfind('.')
|
||||
hypenatedOS = runtime[lastPeriodIdx+1:]
|
||||
return hypenatedOS.replace("-", ".")
|
||||
|
||||
@staticmethod
|
||||
def parseDeviceTypeFromRaw(rawDevice):
|
||||
lastPeriodIdx = rawDevice.rfind('.')
|
||||
hypenatedOS = rawDevice[lastPeriodIdx+1:]
|
||||
return hypenatedOS.replace("-", " ")
|
||||
|
||||
@staticmethod
|
||||
def fetchGroupContainer(udid, groupID):
|
||||
cmd = "xcrun simctl get_app_container {} {} {}".format(udid, SIGNAL_BUNDLEID, groupID)
|
||||
result = runCommand(cmd.split())
|
||||
return result.rstrip()
|
||||
|
||||
def preparePassphrase(passphrase):
|
||||
if len(passphrase) > 0 and passphrase[0] == 'x':
|
||||
return passphrase
|
||||
else:
|
||||
return "x'" + passphrase + "'"
|
||||
|
||||
def writeGuiEnvFile(passphrase, dbPath):
|
||||
dbName = os.path.basename(dbPath)
|
||||
envFilePath = os.path.join(os.path.dirname(dbPath), ".env")
|
||||
|
||||
with open(envFilePath, "w", encoding="utf-8") as envFile:
|
||||
envFile.write(dbName + " = " + passphrase + "\n")
|
||||
envFile.write(dbName + "_plaintextHeaderSize = 32\n")
|
||||
|
||||
return envFilePath
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
description=textwrap.dedent('''\
|
||||
SQLCipher Command Line Interface
|
||||
|
||||
If providing a simulatorID (or accepting the default "Booted" simulator), passphrase retrieval
|
||||
can be simplified by navigating to Signal Settings > Debug UI > Misc > Save plaintext database key.
|
||||
If a database key could not be found and one was not provided through an argument, you'll be prompted
|
||||
to enter one.
|
||||
|
||||
Alternatively, you can provide a sqlcipher path directly via command line arguments. In this case,
|
||||
you'll be required to provide a database key through an argument or stdin.
|
||||
|
||||
If --use-gui is specified, this script will attempt to open the database using the "DB Browser for
|
||||
SQLite" (DBBfS) application.
|
||||
|
||||
If --gui-auto-decrypt-with-plaintext-key is passed alongside --use-gui, the script will place the
|
||||
passphrase in a file next to the database file such that DBBfS is able to automatically decrypt and
|
||||
open the databse. Note that this file is in plaintext, and *ONLY USE* with databases containing
|
||||
test data.
|
||||
'''),
|
||||
usage="%(prog)s [--simulator simID [--staging] | --path dbPath] [--passphrase passphrase] [--quiet] [--use-gui [--gui-auto-decrypt-with-plaintext-key]]")
|
||||
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument("--simulator", metavar="SIM", help="A string identifiying a simulator instance. (default: %(default)s).", default="booted")
|
||||
group.add_argument("--path", help="Path to a sqlcipher DB")
|
||||
parser.add_argument("--passphrase", metavar="PASS", help="The passphrase encrypting the database")
|
||||
parser.add_argument("--staging", action='store_true', help="If a simulator is being targeted, specifies that the staging database should be used")
|
||||
parser.add_argument("remainder", nargs=argparse.REMAINDER, metavar="--", help="All subsequent args will be interpreted as SQL. You probably want quotes here. Be careful with \"*\" since your shell will probably replace it. Ignored if using GUI")
|
||||
parser.add_argument("--quiet", action='store_true', help="Suppress non-failing output")
|
||||
parser.add_argument(
|
||||
"--use-gui",
|
||||
action='store_true',
|
||||
help="Tells the script to try and open DB Browser for SQLite"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--gui-auto-decrypt-with-plaintext-key",
|
||||
action='store_true',
|
||||
help=(
|
||||
"Tells the script to try and have DB Browser for SQLite auto-decrypt the database by "
|
||||
"placing the key in plaintext next to the DB file. ONLY USE with DBs guaranteed to "
|
||||
"only contain test data"
|
||||
)
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
quietMode=args.quiet
|
||||
dbPath = None
|
||||
passphrase = None
|
||||
|
||||
if args.path:
|
||||
dbPath = args.path
|
||||
elif args.simulator:
|
||||
target = Simulator(args.simulator, args.staging)
|
||||
dbPath = target.databasePath()
|
||||
passphrase = target.passphraseIfAvailable()
|
||||
|
||||
if dbPath == None:
|
||||
failWithError("No valid database path")
|
||||
elif os.path.isfile(dbPath) == False:
|
||||
failWithError("Not valid path " + dbPath)
|
||||
|
||||
if args.passphrase:
|
||||
passphrase = args.passphrase
|
||||
if passphrase == None:
|
||||
passphrase = getpass.getpass("Please enter the passphrase. Alternatively, set up a plaintext database key in Debug UI > Misc > Save plaintext database key. Then, rerun the command. ")
|
||||
|
||||
if args.use_gui:
|
||||
if args.gui_auto_decrypt_with_plaintext_key:
|
||||
if passphrase == None or len(passphrase) == 0:
|
||||
failWithError("Missing sqlcipher passphrase for auto-decryption")
|
||||
|
||||
passphrase = preparePassphrase(passphrase)
|
||||
envFilePath = writeGuiEnvFile(passphrase, dbPath)
|
||||
|
||||
printInfo("Warning: saved passphrase to " + envFilePath + " for auto-decryption.")
|
||||
else:
|
||||
printInfo(textwrap.dedent('''\
|
||||
When prompted for the passphrase, select the SQLCipher 4 default settings.
|
||||
Then, select "Custom" and set the "Plaintext Header Size" to 32 from 0.
|
||||
Finally, select "Raw Key" instead of "Passphrase", manually enter "0x", and paste the key.
|
||||
'''))
|
||||
|
||||
runCommand(["open", "-b", DB_BROWSER_FOR_SQLITE_BUNDLEID, dbPath])
|
||||
else:
|
||||
if passphrase == None or len(passphrase) == 0:
|
||||
failWithError("No valid sqlcipher passphrase")
|
||||
|
||||
passphrase = preparePassphrase(passphrase)
|
||||
|
||||
sqlArgs = args.remainder
|
||||
if len(sqlArgs) > 0 and sqlArgs[0] == "--":
|
||||
sqlArgs.pop(0)
|
||||
sqlArgString = " ".join(sqlArgs)
|
||||
|
||||
allArgs = [
|
||||
"sqlcipher",
|
||||
"-cmd", "PRAGMA key = \"" + passphrase + "\";",
|
||||
"-cmd", "PRAGMA cipher_plaintext_header_size = 32;",
|
||||
dbPath
|
||||
]
|
||||
if len(sqlArgString) > 0:
|
||||
allArgs.append(sqlArgString)
|
||||
|
||||
os.execvp("sqlcipher", allArgs)
|
23
Scripts/sqlclient-legacy
Executable file
23
Scripts/sqlclient-legacy
Executable file
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# e.g. put in whatever passphrase is actually being used
|
||||
PASSPHRASE="x'e057e05681c8123bfa60c391eb075da2a022164eb230e88201b5447a9a5fc62b6d0b88087f5fa283391945b845087016'"
|
||||
|
||||
BIN_NAME=$0
|
||||
|
||||
function usage() {
|
||||
echo "$BIN_NAME <path-to-database>"
|
||||
echo "e.g. $BIN_NAME /Home/Users/foo/Library/Simulator/blah/database/signal.sqlite"
|
||||
}
|
||||
|
||||
DB_PATH=$1
|
||||
if [ -z $DB_PATH ]
|
||||
then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sqlcipher -cmd "PRAGMA key = \"${PASSPHRASE}\";" \
|
||||
-cmd "PRAGMA cipher_plaintext_header_size = 32;" \
|
||||
-cmd "PRAGMA cipher_compatibility = 3;" \
|
||||
$DB_PATH
|
160
Scripts/symbolicate.py
Executable file
160
Scripts/symbolicate.py
Executable file
|
@ -0,0 +1,160 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ENV_NAME = "SIGNAL_IOS_DSYMS"
|
||||
|
||||
|
||||
def error_and_die(to_log):
|
||||
print(file=sys.stderr)
|
||||
print(to_log, file=sys.stderr)
|
||||
print(file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def run(args):
|
||||
return subprocess.run(
|
||||
args, capture_output=True, check=True, encoding="utf8"
|
||||
).stdout.rstrip()
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Symbolicates .ips files passed as arguments. "
|
||||
"The symbolicated file is written to *.symbolicated.ips. "
|
||||
f"The script assumes you’ve saved the dSYM files in ${ENV_NAME}."
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"--open",
|
||||
"-o",
|
||||
action="store_true",
|
||||
help="Open files after they’re symbolicated.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"path",
|
||||
nargs="+",
|
||||
metavar="log.ips",
|
||||
help="Paths to files that should be symbolicated.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def parse_json_v2(path):
|
||||
# The new format has a second JSON payload.
|
||||
with open(path, "rb") as file:
|
||||
next(file)
|
||||
return json.load(file)
|
||||
|
||||
|
||||
def get_version(path):
|
||||
try:
|
||||
parse_json_v2(path)
|
||||
return 2
|
||||
except json.decoder.JSONDecodeError:
|
||||
return 1
|
||||
|
||||
|
||||
SCRIPT_PATH_V1 = "Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash"
|
||||
|
||||
|
||||
def symbolicate_v1(xcode_path, path, output_path):
|
||||
script_path = os.path.join(xcode_path, SCRIPT_PATH_V1)
|
||||
args = [script_path, path]
|
||||
env = {**os.environ, "DEVELOPER_DIR": xcode_path}
|
||||
with open(output_path, "wb") as file:
|
||||
subprocess.run(args, check=True, stdout=file, env=env)
|
||||
|
||||
|
||||
SCRIPT_PATH_V2 = "Contents/SharedFrameworks/CoreSymbolicationDT.framework/Resources/CrashSymbolicator.py"
|
||||
|
||||
|
||||
def symbolicate_v2(xcode_path, path, output_path):
|
||||
script_path = os.path.join(xcode_path, SCRIPT_PATH_V2)
|
||||
# Don’t put this on the Desktop or in Documents -- the script can’t find it there.
|
||||
# This directory is searched recursively for .dSYM files.
|
||||
symbols_path = os.getenv(ENV_NAME)
|
||||
if symbols_path is None or not os.path.exists(symbols_path):
|
||||
error_and_die(
|
||||
f"The {ENV_NAME} environment variable should be set to a directory containing .dSYM files."
|
||||
)
|
||||
args = [
|
||||
"python3",
|
||||
script_path,
|
||||
"--dsym",
|
||||
symbols_path,
|
||||
"--output",
|
||||
output_path,
|
||||
"--pretty",
|
||||
path,
|
||||
]
|
||||
subprocess.run(args, check=True)
|
||||
|
||||
|
||||
def omit(d, key_to_remove):
|
||||
return {key: value for key, value in d.items() if key != key_to_remove}
|
||||
|
||||
|
||||
def ensure_symbolication_happened_v2(path, output_path):
|
||||
def equal(a, b):
|
||||
"""
|
||||
Like `==` but ignores changes to `symbolLocation` because those can
|
||||
change even if symbolication didn't happen.
|
||||
"""
|
||||
if not isinstance(a, type(b)):
|
||||
return False
|
||||
if isinstance(a, dict):
|
||||
cleaned_a = omit(a, "symbolLocation")
|
||||
cleaned_b = omit(b, "symbolLocation")
|
||||
if len(cleaned_a) != len(cleaned_b):
|
||||
return False
|
||||
for key, a_value in cleaned_a.items():
|
||||
if key not in cleaned_b:
|
||||
return False
|
||||
b_value = cleaned_b[key]
|
||||
if not equal(a_value, b_value):
|
||||
return False
|
||||
return True
|
||||
if isinstance(a, list):
|
||||
if len(a) != len(b):
|
||||
return False
|
||||
for a_item, b_item in zip(a, b):
|
||||
if not equal(a_item, b_item):
|
||||
return False
|
||||
return True
|
||||
return a == b
|
||||
|
||||
original = parse_json_v2(path)
|
||||
allegedly_symbolicated = parse_json_v2(output_path)
|
||||
if equal(original, allegedly_symbolicated):
|
||||
error_and_die(
|
||||
"Nothing happened when you symbolicated. Do you have the version downloaded in the right place? Did you extract the relevant dSYMs.zip?"
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
ns = parse_args()
|
||||
|
||||
dev_path = run(["xcode-select", "-p"])
|
||||
xcode_path = os.path.normpath(os.path.join(dev_path, *([os.pardir] * 2)))
|
||||
|
||||
for path in ns.path:
|
||||
version = get_version(path)
|
||||
base, ext = os.path.splitext(path)
|
||||
output_path = base + ".symbolicated" + ext
|
||||
if version == 2:
|
||||
symbolicate_v2(xcode_path, path, output_path)
|
||||
ensure_symbolication_happened_v2(path, output_path)
|
||||
else:
|
||||
symbolicate_v1(xcode_path, path, output_path)
|
||||
if ns.open:
|
||||
subprocess.run(["open", output_path], check=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
2
Scripts/translation-tool/.gitignore
vendored
Normal file
2
Scripts/translation-tool/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
.build/
|
||||
.swiftpm/
|
20
Scripts/translation-tool/Package.swift
Normal file
20
Scripts/translation-tool/Package.swift
Normal file
|
@ -0,0 +1,20 @@
|
|||
// swift-tools-version: 5.6
|
||||
//
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "translation-tool",
|
||||
platforms: [.macOS(.v12)],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.executableTarget(
|
||||
name: "translation-tool",
|
||||
dependencies: [],
|
||||
path: "src"
|
||||
)
|
||||
]
|
||||
)
|
131
Scripts/translation-tool/src/CLI.swift
Normal file
131
Scripts/translation-tool/src/CLI.swift
Normal file
|
@ -0,0 +1,131 @@
|
|||
//
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum Constant {
|
||||
static let concurrentRequestLimit = 12
|
||||
static let projectIdentifier = "4b899d72e"
|
||||
static let repositoryURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
|
||||
}
|
||||
|
||||
@main
|
||||
struct CLI {
|
||||
|
||||
// MARK: - Entrypoint
|
||||
|
||||
static func main() async throws {
|
||||
var remainingArgs = CommandLine.arguments.dropFirst()
|
||||
while let arg = remainingArgs.popFirst() {
|
||||
switch arg {
|
||||
case "upload-metadata":
|
||||
try await loadCLI().uploadFiles(metadataFiles)
|
||||
case "upload-resources":
|
||||
try await loadCLI().uploadFiles(resourceFiles)
|
||||
case "download-metadata":
|
||||
try MetadataFile.checkForUnusedLocalizations(in: Constant.repositoryURL)
|
||||
try await loadCLI().downloadFiles(metadataFiles)
|
||||
case "download-resources":
|
||||
try ResourceFile.checkForUnusedLocalizations(in: Constant.repositoryURL)
|
||||
try await loadCLI().downloadFiles(resourceFiles)
|
||||
case "genstrings-pluralaware":
|
||||
guard let temporaryDirectoryPath = remainingArgs.popFirst() else {
|
||||
print("Missing temporary directory path")
|
||||
exit(1)
|
||||
}
|
||||
try Genstrings.filterPluralAware(
|
||||
resourceFile: pluralAwareFile,
|
||||
repositoryURL: Constant.repositoryURL,
|
||||
temporaryDirectoryURL: URL(fileURLWithPath: temporaryDirectoryPath)
|
||||
)
|
||||
default:
|
||||
print("Unknown action: \(arg)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static var loadedCLI: CLI?
|
||||
static func loadCLI() throws -> CLI {
|
||||
if let result = loadedCLI {
|
||||
return result
|
||||
}
|
||||
guard let (userIdentifier, userSecret) = try loadUserParameters() else {
|
||||
showIntructionsForUserParameters()
|
||||
}
|
||||
let client = Smartling(
|
||||
projectIdentifier: Constant.projectIdentifier,
|
||||
userIdentifier: userIdentifier,
|
||||
userSecret: userSecret
|
||||
)
|
||||
let result = CLI(repositoryURL: Constant.repositoryURL, client: client)
|
||||
loadedCLI = result
|
||||
return result
|
||||
}
|
||||
|
||||
// MARK: - Upload & Download
|
||||
|
||||
private static let metadataFiles: [MetadataFile] = [
|
||||
MetadataFile(filename: "release_notes.txt"),
|
||||
MetadataFile(filename: "description.txt")
|
||||
]
|
||||
|
||||
private static let pluralAwareFile = ResourceFile(filename: "PluralAware.stringsdict")
|
||||
|
||||
private static let resourceFiles: [ResourceFile] = [
|
||||
ResourceFile(filename: "InfoPlist.strings"),
|
||||
ResourceFile(filename: "Localizable.strings"),
|
||||
pluralAwareFile
|
||||
]
|
||||
|
||||
var repositoryURL: URL
|
||||
var client: Smartling
|
||||
|
||||
private func uploadFiles(_ files: [TranslatableFile]) async throws {
|
||||
try await withLimitedThrowingTaskGroup(limit: Constant.concurrentRequestLimit) { taskGroup in
|
||||
for translatableFile in files {
|
||||
try await taskGroup.addTask {
|
||||
try await client.uploadSourceFile(
|
||||
at: repositoryURL.appendingPathComponent(translatableFile.relativeSourcePath)
|
||||
)
|
||||
print("Uploaded \(translatableFile.filename)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func downloadFiles(_ files: [TranslatableFile]) async throws {
|
||||
// Each of these kicks off a bunch of downloads in parallel, so it's fine to do these serially.
|
||||
for translatableFile in files {
|
||||
try await translatableFile.downloadAllTranslations(to: repositoryURL, using: client)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Config
|
||||
|
||||
private static func loadUserParameters() throws -> (String, String)? {
|
||||
let fileURL = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".smartling/auth")
|
||||
let fileContent: String
|
||||
do {
|
||||
fileContent = try String(contentsOf: fileURL).trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
} catch CocoaError.fileReadNoSuchFile {
|
||||
return nil
|
||||
}
|
||||
let components = fileContent.split(separator: "\n")
|
||||
guard components.count == 2 else {
|
||||
return nil
|
||||
}
|
||||
return (String(components[0]), String(components[1]))
|
||||
}
|
||||
|
||||
private static func showIntructionsForUserParameters() -> Never {
|
||||
print("")
|
||||
print("Couldn't load user identifier/user secret from ~/.smartling/auth.")
|
||||
print("")
|
||||
print("That file should contain two lines: (1) user identifier & (2) user secret.")
|
||||
print("You can create a token using Smartling's web interface.")
|
||||
print("")
|
||||
exit(1)
|
||||
}
|
||||
}
|
19
Scripts/translation-tool/src/FileManager.swift
Normal file
19
Scripts/translation-tool/src/FileManager.swift
Normal file
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension FileManager {
|
||||
/// Copies an item, replacing the destination if it already exists.
|
||||
func copyItem(at srcURL: URL, replacingItemAt dstURL: URL) throws {
|
||||
do {
|
||||
try removeItem(at: dstURL)
|
||||
} catch CocoaError.fileNoSuchFile {
|
||||
// not an error if the file doesn't exist
|
||||
}
|
||||
try createDirectory(at: dstURL.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||
try copyItem(at: srcURL, to: dstURL)
|
||||
}
|
||||
}
|
65
Scripts/translation-tool/src/Genstrings.swift
Normal file
65
Scripts/translation-tool/src/Genstrings.swift
Normal file
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Genstrings {
|
||||
enum DecodingError: Error {
|
||||
case nonStringKeys
|
||||
}
|
||||
|
||||
/// Filters a .stringsdict file based on genstrings output.
|
||||
///
|
||||
/// The `genstrings` command can find NSLocalizedString/OWSLocalizedString
|
||||
/// references within the codebase, but it always produces a .strings file.
|
||||
/// Despite this, it's useful for auto-genstrings to remove strings that are
|
||||
/// no longer referenced. This attempts to bridge that gap by filtering a
|
||||
/// .stringsdict file based on the keys from a .strings file.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - resourceFile: A `ResourceFile` that refers to a .stringsdict file.
|
||||
/// - repositoryURL: The URL of the repository containing `resourceFile`.
|
||||
/// - temporaryDirectoryURL:
|
||||
/// A temporary URL to a directory where a `genstrings`-produced
|
||||
/// `.strings` file is located. The `.strings` file should have the
|
||||
/// same base name as `resourceFile`.
|
||||
static func filterPluralAware(
|
||||
resourceFile: ResourceFile,
|
||||
repositoryURL: URL,
|
||||
temporaryDirectoryURL: URL
|
||||
) throws {
|
||||
precondition(resourceFile.filename.hasSuffix(".stringsdict"))
|
||||
|
||||
// The URL where the .stringsdict is stored in the repository.
|
||||
let stringsDictURL = repositoryURL.appendingPathComponent(resourceFile.relativeSourcePath)
|
||||
|
||||
// The URL where a .strings file was generated with keys that *should* exist.
|
||||
let generatedStringsURL = temporaryDirectoryURL.appendingPathComponent(resourceFile.filename)
|
||||
.deletingPathExtension().appendingPathExtension("strings")
|
||||
|
||||
let existingValues = try loadExistingValues(at: stringsDictURL)
|
||||
let expectedKeys = try loadExpectedKeys(at: generatedStringsURL)
|
||||
// Remove any values that genstrings didn't discover
|
||||
let filteredValues = existingValues.filter { expectedKeys.contains($0.key) }
|
||||
// Write the new version back to disk.
|
||||
for removedKey in Set(existingValues.keys).subtracting(filteredValues.keys).sorted() {
|
||||
print("\(resourceFile.filename): Removed \(removedKey)")
|
||||
}
|
||||
let dataValue = try PropertyListSerialization.data(fromPropertyList: filteredValues, format: .xml, options: 0)
|
||||
try dataValue.write(to: stringsDictURL, options: [.atomic])
|
||||
}
|
||||
|
||||
private static func loadExistingValues(at url: URL) throws -> [String: Any] {
|
||||
let decodedPropertyList = try PropertyListSerialization.propertyList(from: Data(contentsOf: url), format: nil)
|
||||
guard let result = decodedPropertyList as? [String: Any] else {
|
||||
throw DecodingError.nonStringKeys
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private static func loadExpectedKeys(at url: URL) throws -> Set<String> {
|
||||
Set(try String(contentsOf: url).propertyListFromStringsFileFormat().keys)
|
||||
}
|
||||
}
|
30
Scripts/translation-tool/src/LimitedThrowingTaskGroup.swift
Normal file
30
Scripts/translation-tool/src/LimitedThrowingTaskGroup.swift
Normal file
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct LimitedThrowingTaskGroup {
|
||||
var taskGroup: ThrowingTaskGroup<Void, Error>
|
||||
var remainingCapacity: Int
|
||||
|
||||
mutating func addTask(operation: @escaping @Sendable () async throws -> Void) async throws {
|
||||
if remainingCapacity > 0 {
|
||||
remainingCapacity -= 1
|
||||
} else {
|
||||
// Once we've kicked off the maximum number of concurrent tasks, we always
|
||||
// wait for one to finish before starting the next one.
|
||||
try await taskGroup.next()
|
||||
}
|
||||
taskGroup.addTask(operation: operation)
|
||||
}
|
||||
}
|
||||
|
||||
func withLimitedThrowingTaskGroup(limit: Int, body: (inout LimitedThrowingTaskGroup) async throws -> Void) async rethrows {
|
||||
try await withThrowingTaskGroup(of: Void.self) { taskGroup in
|
||||
var limitedTaskGroup = LimitedThrowingTaskGroup(taskGroup: taskGroup, remainingCapacity: limit)
|
||||
try await body(&limitedTaskGroup)
|
||||
try await taskGroup.waitForAll()
|
||||
}
|
||||
}
|
107
Scripts/translation-tool/src/MetadataFile.swift
Normal file
107
Scripts/translation-tool/src/MetadataFile.swift
Normal file
|
@ -0,0 +1,107 @@
|
|||
//
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// Maps Smartling language codes to App Store Connect language codes.
|
||||
private let languageMap: [String: [String]] = [
|
||||
// These languages are returned from Smartling and need to be moved to their correct final destination.
|
||||
"ar": ["ar-SA"],
|
||||
"ca": ["ca"],
|
||||
"cs": ["cs"],
|
||||
"da": ["da"],
|
||||
"de": ["de-DE"],
|
||||
"el": ["el"],
|
||||
"es": ["es-ES", "es-MX"],
|
||||
"fi": ["fi"],
|
||||
"fr": ["fr-CA", "fr-FR"],
|
||||
"he": ["he"],
|
||||
"hi-IN": ["hi"],
|
||||
"hr-HR": ["hr"],
|
||||
"hu": ["hu"],
|
||||
"id": ["id"],
|
||||
"it": ["it"],
|
||||
"ja": ["ja"],
|
||||
"ko": ["ko"],
|
||||
"ms": ["ms"],
|
||||
"nb": ["no"],
|
||||
"nl": ["nl-NL"],
|
||||
"pl": ["pl"],
|
||||
"pt-BR": ["pt-BR"],
|
||||
"pt-PT": ["pt-PT"],
|
||||
"ro-RO": ["ro"],
|
||||
"ru": ["ru"],
|
||||
"sk-SK": ["sk"],
|
||||
"sv": ["sv"],
|
||||
"th": ["th"],
|
||||
"tr": ["tr"],
|
||||
"uk-UA": ["uk"],
|
||||
"vi": ["vi"],
|
||||
"zh-CN": ["zh-Hans"],
|
||||
"zh-HK": ["zh-Hant"]
|
||||
|
||||
// These don't exist in App Store Connect, so there's no need to fetch them from Smartling.
|
||||
// "be-BY": [],
|
||||
// "bn-BD": [],
|
||||
// "fa-IR": [],
|
||||
// "ga-IE": [],
|
||||
// "gu-IN": [],
|
||||
// "mr-IN": [],
|
||||
// "sr-YR": [],
|
||||
// "ug": [],
|
||||
// "ur": [],
|
||||
// "yue": [],
|
||||
// "zh-TW": [],
|
||||
]
|
||||
|
||||
private let extraEnglishLanguages: [String] = ["en-AU", "en-CA", "en-GB"]
|
||||
|
||||
struct MetadataFile: TranslatableFile {
|
||||
var filename: String
|
||||
|
||||
var relativeSourcePath: String { relativePath(for: "en-US") }
|
||||
|
||||
private static let relativeDirectoryPath = "fastlane/metadata"
|
||||
|
||||
private func relativePath(for languageCode: String) -> String {
|
||||
return "\(Self.relativeDirectoryPath)/\(languageCode)/\(filename)"
|
||||
}
|
||||
|
||||
func downloadAllTranslations(to repositoryURL: URL, using client: Smartling) async throws {
|
||||
try await withLimitedThrowingTaskGroup(limit: Constant.concurrentRequestLimit) { taskGroup in
|
||||
try await taskGroup.addTask {
|
||||
// English is special. Instead of downloading a file, we copy the file we
|
||||
// uploaded to other English languages.
|
||||
let fileURL = repositoryURL.appendingPathComponent(relativeSourcePath)
|
||||
try processDownloadedFile(at: fileURL, repositoryURL: repositoryURL, localIdentifiers: extraEnglishLanguages)
|
||||
}
|
||||
for (remoteIdentifier, localIdentifiers) in languageMap {
|
||||
try await taskGroup.addTask {
|
||||
let fileURL = try await client.downloadTranslatedFile(for: filename, in: remoteIdentifier)
|
||||
try processDownloadedFile(at: fileURL, repositoryURL: repositoryURL, localIdentifiers: localIdentifiers)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func processDownloadedFile(at fileURL: URL, repositoryURL: URL, localIdentifiers: [String]) throws {
|
||||
for localIdentifier in localIdentifiers {
|
||||
let localRelativePath = relativePath(for: localIdentifier)
|
||||
try FileManager.default.copyItem(
|
||||
at: fileURL,
|
||||
replacingItemAt: repositoryURL.appendingPathComponent(localRelativePath)
|
||||
)
|
||||
print("Saved \(localRelativePath)")
|
||||
}
|
||||
}
|
||||
|
||||
static func checkForUnusedLocalizations(in repositoryURL: URL) throws {
|
||||
try checkForUnusedLocalizations(
|
||||
in: repositoryURL.appendingPathComponent(Self.relativeDirectoryPath),
|
||||
suffix: "",
|
||||
expectedLocalizations: languageMap.flatMap { $1 } + extraEnglishLanguages + ["en-US"]
|
||||
)
|
||||
}
|
||||
}
|
90
Scripts/translation-tool/src/ResourceFile.swift
Normal file
90
Scripts/translation-tool/src/ResourceFile.swift
Normal file
|
@ -0,0 +1,90 @@
|
|||
//
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// Maps Smartling language codes to .strings/.stringsdict language codes.
|
||||
private let languageMap: [String: String] = [
|
||||
"ar": "ar",
|
||||
"be-BY": "be",
|
||||
"bn-BD": "bn",
|
||||
"ca": "ca",
|
||||
"cs": "cs",
|
||||
"da": "da",
|
||||
"de": "de",
|
||||
"el": "el",
|
||||
"es": "es",
|
||||
"fa-IR": "fa",
|
||||
"fi": "fi",
|
||||
"fr": "fr",
|
||||
"ga-IE": "ga",
|
||||
"gu-IN": "gu",
|
||||
"he": "he",
|
||||
"hi-IN": "hi",
|
||||
"hr-HR": "hr",
|
||||
"hu": "hu",
|
||||
"id": "id",
|
||||
"it": "it",
|
||||
"ja": "ja",
|
||||
"ko": "ko",
|
||||
"mr-IN": "mr",
|
||||
"ms": "ms",
|
||||
"nb": "nb",
|
||||
"nl": "nl",
|
||||
"pl": "pl",
|
||||
"pt-BR": "pt_BR",
|
||||
"pt-PT": "pt_PT",
|
||||
"ro-RO": "ro",
|
||||
"ru": "ru",
|
||||
"sk-SK": "sk",
|
||||
"sr-YR": "sr",
|
||||
"sv": "sv",
|
||||
"th": "th",
|
||||
"tr": "tr",
|
||||
"ug": "ug",
|
||||
"uk-UA": "uk",
|
||||
"ur": "ur",
|
||||
"vi": "vi",
|
||||
"zh-CN": "zh_CN",
|
||||
"zh-HK": "zh_HK",
|
||||
"zh-TW": "zh_TW",
|
||||
"zh-YU": "yue"
|
||||
]
|
||||
|
||||
struct ResourceFile: TranslatableFile {
|
||||
var filename: String
|
||||
|
||||
var relativeSourcePath: String {
|
||||
"\(Self.relativeDirectoryPath)/en.lproj/\(filename)"
|
||||
}
|
||||
|
||||
private static let relativeDirectoryPath = "Signal/translations"
|
||||
|
||||
private func relativePath(for languageCode: String) -> String {
|
||||
return "\(Self.relativeDirectoryPath)/\(languageCode).lproj/\(filename)"
|
||||
}
|
||||
|
||||
func downloadAllTranslations(to repositoryURL: URL, using client: Smartling) async throws {
|
||||
try await withLimitedThrowingTaskGroup(limit: Constant.concurrentRequestLimit) { taskGroup in
|
||||
for (remoteIdentifier, localIdentifier) in languageMap {
|
||||
let fileURL = try await client.downloadTranslatedFile(for: filename, in: remoteIdentifier)
|
||||
let localRelativePath = relativePath(for: localIdentifier)
|
||||
try FileManager.default.copyItem(
|
||||
at: fileURL,
|
||||
replacingItemAt: repositoryURL.appendingPathComponent(localRelativePath)
|
||||
)
|
||||
print("Saved \(localRelativePath)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func checkForUnusedLocalizations(in repositoryURL: URL) throws {
|
||||
try checkForUnusedLocalizations(
|
||||
in: repositoryURL.appendingPathComponent(relativeDirectoryPath),
|
||||
suffix: ".lproj",
|
||||
expectedLocalizations: languageMap.map { $1 } + ["en"]
|
||||
)
|
||||
}
|
||||
}
|
200
Scripts/translation-tool/src/Smartling.swift
Normal file
200
Scripts/translation-tool/src/Smartling.swift
Normal file
|
@ -0,0 +1,200 @@
|
|||
//
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Smartling {
|
||||
let projectIdentifier: String
|
||||
let userIdentifier: String
|
||||
let userSecret: String
|
||||
|
||||
init(projectIdentifier: String, userIdentifier: String, userSecret: String) {
|
||||
self.projectIdentifier = projectIdentifier
|
||||
self.userIdentifier = userIdentifier
|
||||
self.userSecret = userSecret
|
||||
}
|
||||
|
||||
fileprivate struct Token {
|
||||
var accessToken: String
|
||||
var expirationDate: Date
|
||||
}
|
||||
|
||||
private var latestTokenTask: Task<Token, Error>?
|
||||
|
||||
@MainActor
|
||||
private func fetchToken() async throws -> Token {
|
||||
assert(Thread.isMainThread)
|
||||
if let latestToken = try await latestTokenTask?.value, latestToken.expirationDate.timeIntervalSinceNow > 5 {
|
||||
return latestToken
|
||||
}
|
||||
let task = Task {
|
||||
let rawToken = try await fetchNewToken()
|
||||
let newToken = Token(
|
||||
accessToken: rawToken.accessToken,
|
||||
expirationDate: Date(timeIntervalSinceNow: TimeInterval(rawToken.expiresIn))
|
||||
)
|
||||
print("Got new token that expires at \(newToken.expirationDate)")
|
||||
return newToken
|
||||
}
|
||||
latestTokenTask = task
|
||||
return try await task.value
|
||||
}
|
||||
|
||||
struct FetchedToken: Decodable {
|
||||
var accessToken: String
|
||||
var expiresIn: Int
|
||||
}
|
||||
|
||||
private func fetchNewToken() async throws -> FetchedToken {
|
||||
struct AuthenticationRequest: Encodable {
|
||||
var userIdentifier: String
|
||||
var userSecret: String
|
||||
}
|
||||
|
||||
let request = AuthenticationRequest(userIdentifier: userIdentifier, userSecret: userSecret)
|
||||
return try await postRequest(urlPath: "/auth-api/v2/authenticate", request: request)
|
||||
}
|
||||
|
||||
func uploadSourceFile(at fileURL: URL) async throws {
|
||||
let urlPath = "/files-api/v2/projects/\(projectIdentifier)/file"
|
||||
var urlRequest = buildRequest(url: buildURL(path: urlPath), token: try await fetchToken())
|
||||
urlRequest.httpMethod = "POST"
|
||||
try urlRequest.addFile(at: fileURL)
|
||||
_ = try await URLSession.shared.data(for: urlRequest, expecting: 200)
|
||||
}
|
||||
|
||||
func downloadTranslatedFile(for filename: String, in localeIdentifier: String) async throws -> URL {
|
||||
let urlPath = "/files-api/v2/projects/\(projectIdentifier)/locales/\(localeIdentifier)/file"
|
||||
let url = buildURL(path: urlPath, queryItems: [
|
||||
"fileUri": filename,
|
||||
"retrievalType": "published",
|
||||
"includeOriginalStrings": "true"
|
||||
])
|
||||
var urlRequest = buildRequest(url: url, token: try await fetchToken())
|
||||
urlRequest.httpMethod = "GET"
|
||||
return try await URLSession.shared.download(for: urlRequest, expecting: 200)
|
||||
}
|
||||
}
|
||||
|
||||
private extension Smartling {
|
||||
func buildURL(path: String, queryItems: [String: String]? = nil) -> URL {
|
||||
var urlComponents = URLComponents()
|
||||
urlComponents.scheme = "https"
|
||||
urlComponents.host = "api.smartling.com"
|
||||
urlComponents.path = path
|
||||
urlComponents.queryItems = queryItems?.map { URLQueryItem(name: $0.key, value: $0.value) }
|
||||
return urlComponents.url!
|
||||
}
|
||||
|
||||
func buildRequest(url: URL, token: Token? = nil) -> URLRequest {
|
||||
var request = URLRequest(url: url)
|
||||
if let token = token {
|
||||
request.addValue("Bearer \(token.accessToken)", forHTTPHeaderField: "Authorization")
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
// Wraps a JSON response from Smartling.
|
||||
struct Response<T: Decodable>: Decodable {
|
||||
var response: WrappedResponse
|
||||
struct WrappedResponse: Decodable {
|
||||
var data: T
|
||||
}
|
||||
}
|
||||
|
||||
func postRequest<Req: Encodable, Resp: Decodable>(urlPath: String, request: Req) async throws -> Resp {
|
||||
var urlRequest = buildRequest(url: buildURL(path: urlPath))
|
||||
urlRequest.httpMethod = "POST"
|
||||
urlRequest.httpBody = try JSONEncoder().encode(request)
|
||||
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
let responseData = try await URLSession.shared.data(for: urlRequest, expecting: 200)
|
||||
let wrappedResponse = try JSONDecoder().decode(Response<Resp>.self, from: responseData)
|
||||
return wrappedResponse.response.data
|
||||
}
|
||||
}
|
||||
|
||||
extension URLRequest {
|
||||
mutating func addFile(at fileURL: URL) throws {
|
||||
let boundary = UUID().uuidString
|
||||
setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
|
||||
let filename = fileURL.lastPathComponent
|
||||
httpBody = Self.multipartFormData(boundary: boundary, items: [
|
||||
("file", .file(name: filename, value: try Data(contentsOf: fileURL))),
|
||||
("fileUri", .text(value: filename)),
|
||||
("fileType", .text(value: Self.fileType(for: fileURL)))
|
||||
])
|
||||
}
|
||||
|
||||
private enum MultipartFormDataItem {
|
||||
case text(value: String)
|
||||
case file(name: String, value: Data)
|
||||
}
|
||||
|
||||
private static func multipartFormData(boundary: String, items: [(String, MultipartFormDataItem)]) -> Data {
|
||||
var result = Data()
|
||||
func addLine(_ dataValue: Data) {
|
||||
result.append(dataValue)
|
||||
result.append("\r\n".data(using: .utf8)!)
|
||||
}
|
||||
func addLine(_ stringValue: String) {
|
||||
addLine(stringValue.data(using: .utf8)!)
|
||||
}
|
||||
for (fieldName, item) in items {
|
||||
addLine("--\(boundary)")
|
||||
switch item {
|
||||
case let .text(value):
|
||||
addLine("Content-Disposition: form-data; name=\"\(fieldName)\"")
|
||||
addLine("")
|
||||
addLine(value)
|
||||
case let .file(name, value):
|
||||
addLine("Content-Disposition: form-data; name=\"\(fieldName)\"; filename=\"\(name)\"")
|
||||
addLine("Content-Type: application/octet-stream")
|
||||
addLine("")
|
||||
addLine(value)
|
||||
}
|
||||
}
|
||||
addLine("--\(boundary)--")
|
||||
return result
|
||||
}
|
||||
|
||||
private static func fileType(for url: URL) -> String {
|
||||
let pathExtension = url.pathExtension
|
||||
switch pathExtension {
|
||||
case "txt":
|
||||
return "plain_text"
|
||||
case "strings":
|
||||
return "ios"
|
||||
case "stringsdict":
|
||||
return "stringsdict"
|
||||
default:
|
||||
fatalError("Can't upload file with .\(pathExtension) extension")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension URLSession {
|
||||
enum HTTPError: Error {
|
||||
case statusCode(Int?)
|
||||
}
|
||||
|
||||
func data(for urlRequest: URLRequest, expecting expectedStatusCode: Int) async throws -> Data {
|
||||
let (data, urlResponse) = try await data(for: urlRequest)
|
||||
try handleResponse(urlResponse: urlResponse, expecting: expectedStatusCode)
|
||||
return data
|
||||
}
|
||||
|
||||
func download(for urlRequest: URLRequest, expecting expectedStatusCode: Int) async throws -> URL {
|
||||
let (data, urlResponse) = try await download(for: urlRequest)
|
||||
try handleResponse(urlResponse: urlResponse, expecting: expectedStatusCode)
|
||||
return data
|
||||
}
|
||||
|
||||
private func handleResponse(urlResponse: URLResponse, expecting expectedStatusCode: Int) throws {
|
||||
let statusCode = (urlResponse as? HTTPURLResponse)?.statusCode
|
||||
guard statusCode == expectedStatusCode else {
|
||||
throw HTTPError.statusCode(statusCode)
|
||||
}
|
||||
}
|
||||
}
|
55
Scripts/translation-tool/src/TranslatableFile.swift
Normal file
55
Scripts/translation-tool/src/TranslatableFile.swift
Normal file
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol TranslatableFile {
|
||||
var filename: String { get }
|
||||
var relativeSourcePath: String { get }
|
||||
|
||||
/// Checks if there are localizations without a remote counterpart.
|
||||
///
|
||||
/// This is most useful when adding or removing languages.
|
||||
static func checkForUnusedLocalizations(in repositoryURL: URL) throws
|
||||
|
||||
/// Downloads translations for all languages.
|
||||
func downloadAllTranslations(to repositoryURL: URL, using client: Smartling) async throws
|
||||
}
|
||||
|
||||
extension TranslatableFile {
|
||||
|
||||
/// Compares localization directories on disk against what's expected.
|
||||
///
|
||||
/// If there are localization directories on disk that aren't being updated
|
||||
/// by this script, that likely means we're shipping stale translations. We
|
||||
/// check this each time we download translations.
|
||||
static func checkForUnusedLocalizations(in directoryURL: URL, suffix: String, expectedLocalizations: [String]) throws {
|
||||
let localLocalizationCodes = try fetchLocalLocalizationCodes(in: directoryURL, suffix: suffix)
|
||||
let unusedLocalizationCodes = localLocalizationCodes.subtracting(expectedLocalizations)
|
||||
|
||||
guard unusedLocalizationCodes.isEmpty else {
|
||||
let sortedLocalizationCodes = unusedLocalizationCodes.sorted(by: <)
|
||||
print("We're shipping languages that aren't pulling translations: \(sortedLocalizationCodes)")
|
||||
print("(stored in \(directoryURL.path))")
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
private static func fetchLocalLocalizationCodes(in directoryURL: URL, suffix: String) throws -> Set<String> {
|
||||
var result = Set<String>()
|
||||
for fileURL in try FileManager.default.contentsOfDirectory(at: directoryURL, includingPropertiesForKeys: [.isDirectoryKey]) {
|
||||
let resourceValues = try fileURL.resourceValues(forKeys: [.isDirectoryKey])
|
||||
guard resourceValues.isDirectory == true else {
|
||||
continue
|
||||
}
|
||||
let filename = fileURL.lastPathComponent
|
||||
guard filename.hasSuffix(suffix) else {
|
||||
continue
|
||||
}
|
||||
result.insert(String(filename.dropLast(suffix.count)))
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
67
Scripts/translation/auto-genstrings
Executable file
67
Scripts/translation/auto-genstrings
Executable file
|
@ -0,0 +1,67 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
BIN_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
REPO_ROOT=$BIN_DIR/../..
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
SSK_DIR="SignalServiceKit"
|
||||
SAE_DIR="SignalShareExtension"
|
||||
SUI_DIR="SignalUI"
|
||||
NSE_DIR="SignalNSE"
|
||||
|
||||
TARGETS=(Signal "${SSK_DIR}" "${SAE_DIR}" "${NSE_DIR}" "${SUI_DIR}")
|
||||
TMP="$(mktemp -d)"
|
||||
STRINGFILE="Signal/translations/en.lproj/Localizable.strings"
|
||||
|
||||
# Assert preconditions before we do any work
|
||||
# We're more likely to notice errors this way
|
||||
|
||||
for TARGET_DIR in "${TARGETS[@]}"; do
|
||||
if [[ ! -d "$TARGET_DIR" ]]; then
|
||||
echo "Unable to find required directory: ${TARGET_DIR}."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Now that we've check all our pre-conditions, proceed with the work.
|
||||
|
||||
# Search directories for .m & .h files and collect string definitions with genstrings.
|
||||
# Exclude the files that define OWSLocalizedString for Swift and ObjC, though.
|
||||
find "${TARGETS[@]}" \
|
||||
'(' \
|
||||
-name 'test' -o \
|
||||
-name 'tests' -o \
|
||||
-name 'OWSLocalizedString.swift' -o \
|
||||
-name 'SignalServiceKit.h' \
|
||||
')' \
|
||||
-prune \
|
||||
-o \
|
||||
'(' \
|
||||
-name "*.m" -or \
|
||||
-name "*.h" -or \
|
||||
-name "*.swift" \
|
||||
')' \
|
||||
-exec genstrings -s OWSLocalizedString -o "$TMP" "{}" "+"
|
||||
|
||||
# We have to convert the new .strings files to UTF-8 in order to deal with them
|
||||
# STRINGFILE is already UTF-8.
|
||||
OLDUTF8=$(cat $STRINGFILE)
|
||||
NEWUTF8=$(iconv -f UTF-16 -t UTF-8 "$TMP"/Localizable.strings)
|
||||
|
||||
# Let's merge the old with the new .strings file:
|
||||
# 1. Select old string definition lines
|
||||
# 2. Setup field separators
|
||||
# 3. Read old string definitions as associative array
|
||||
# 4. In new file, if possible, insert old definition
|
||||
# 5. Add separator and semicolon only for string definition lines
|
||||
echo "$OLDUTF8" | grep -Eo '^".*"' | \
|
||||
awk 'BEGIN {FS = "\" = \""; OFS = ""}
|
||||
NR == FNR {a[$1] = $2; next}
|
||||
{$2 = ($1 in a ? a[$1] : $2);
|
||||
if($2 ~ /"[;]*$/){$2 = "\" = \""$2};
|
||||
if($2 ~ /"$/){$2 = $2";"};
|
||||
print}' - <(echo "$NEWUTF8") > $STRINGFILE
|
||||
|
||||
swift run --package-path Scripts/translation-tool translation-tool genstrings-pluralaware "$TMP"
|
12
Scripts/translation/pull-metadata-translations
Executable file
12
Scripts/translation/pull-metadata-translations
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
BIN_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
REPO_ROOT=$BIN_DIR/../..
|
||||
cd $REPO_ROOT
|
||||
|
||||
echo "Pulling metadata translations..."
|
||||
swift run --package-path Scripts/translation-tool translation-tool download-metadata
|
44
Scripts/translation/pull-translations
Executable file
44
Scripts/translation/pull-translations
Executable file
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
BIN_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
REPO_ROOT=$BIN_DIR/../..
|
||||
cd $REPO_ROOT
|
||||
swift run --package-path Scripts/translation-tool translation-tool download-resources
|
||||
|
||||
LOCALIZATION_ROOT=$REPO_ROOT/Signal/translations
|
||||
cd $LOCALIZATION_ROOT
|
||||
|
||||
# Parse the PluralAware.stringsdict files to ensure they're not malformed.
|
||||
lang_errors=()
|
||||
for dir in *.lproj
|
||||
do
|
||||
pushd "$dir"
|
||||
if [ -e PluralAware.stringsdict ]; then
|
||||
plutil PluralAware.stringsdict || lang_errors+=("$dir")
|
||||
fi
|
||||
popd
|
||||
done
|
||||
if [ "${#lang_errors[@]}" -gt 0 ]; then
|
||||
1>&2 echo "Some languages have malformed .stringsdict files: ${lang_errors[*]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get and build iStringsCheck from https://github.com/signalapp/l10n_lint
|
||||
# This does some checks to make sure all strings are present and that interpolated strings have the right number of arguments
|
||||
LINT_CMD=$(command -v l10n_lint)
|
||||
LINT_CMD=${LINT_CMD:-$REPO_ROOT/../l10n_lint/target/debug/l10n_lint}
|
||||
|
||||
if [ -e $LINT_CMD ]
|
||||
then
|
||||
$LINT_CMD en.lproj/Localizable.strings .
|
||||
$LINT_CMD en.lproj/InfoPlist.strings .
|
||||
else
|
||||
echo "Missing string linter. See: https://github.com/signalapp/l10n_lint"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Make sure you register any new localizations in XCode! (Go to Project > Signal > Localizations > Add Localizations)"
|
11
Scripts/translation/push-metadata-source
Executable file
11
Scripts/translation/push-metadata-source
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
BIN_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
REPO_ROOT=$BIN_DIR/../..
|
||||
cd $REPO_ROOT
|
||||
|
||||
swift run --package-path Scripts/translation-tool translation-tool upload-metadata
|
12
Scripts/translation/push-translation-source
Executable file
12
Scripts/translation/push-translation-source
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
BIN_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
REPO_ROOT=$BIN_DIR/../..
|
||||
cd $REPO_ROOT
|
||||
|
||||
swift run --package-path Scripts/translation-tool translation-tool upload-resources
|
||||
|
38
Scripts/translation/sync-translations
Executable file
38
Scripts/translation/sync-translations
Executable file
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/env bash
|
||||
set -x
|
||||
set -e
|
||||
|
||||
BIN_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
REPO_ROOT=$BIN_DIR/../..
|
||||
cd $REPO_ROOT
|
||||
|
||||
cat <<EOS
|
||||
|
||||
Begin Pushing translation source
|
||||
################################
|
||||
|
||||
EOS
|
||||
|
||||
$BIN_DIR/push-translation-source
|
||||
$BIN_DIR/push-metadata-source
|
||||
|
||||
cat <<EOS
|
||||
|
||||
Done Pushing translation source
|
||||
###############################
|
||||
Begin Pulling translations
|
||||
|
||||
EOS
|
||||
|
||||
$BIN_DIR/pull-metadata-translations
|
||||
$BIN_DIR/pull-translations
|
||||
|
||||
cat <<EOS
|
||||
|
||||
Done Pulling translations
|
||||
#########################
|
||||
|
||||
EOS
|
||||
|
||||
|
114
Scripts/unused_strings.py
Executable file
114
Scripts/unused_strings.py
Executable file
|
@ -0,0 +1,114 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Find unused localization strings.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import pathlib
|
||||
from typing import Iterable
|
||||
|
||||
|
||||
project_root = pathlib.Path(__file__).parent.parent.resolve()
|
||||
key_re = re.compile('^"([^"]+)" =')
|
||||
|
||||
|
||||
def project_path(from_root: str) -> pathlib.Path:
|
||||
return project_root / from_root
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Find unused localization strings.")
|
||||
parser.add_argument(
|
||||
"--strings",
|
||||
type=pathlib.Path,
|
||||
default=project_path("Signal/translations/en.lproj/Localizable.strings"),
|
||||
help="A Localizable.strings file",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--src_dirs",
|
||||
default=map(
|
||||
project_path,
|
||||
[
|
||||
"Signal",
|
||||
"SignalNSE",
|
||||
"SignalServiceKit",
|
||||
"SignalShareExtension",
|
||||
"SignalUI",
|
||||
],
|
||||
),
|
||||
nargs="+",
|
||||
type=pathlib.Path,
|
||||
help="One or more source directories",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--extensions",
|
||||
default=[
|
||||
".swift",
|
||||
".m",
|
||||
".h",
|
||||
],
|
||||
nargs="+",
|
||||
help="one or more file extensions",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def get_all_keys(strings_file: pathlib.Path) -> Iterable[str]:
|
||||
with open(strings_file, "r") as file:
|
||||
for line in file:
|
||||
match = key_re.match(line)
|
||||
if match:
|
||||
yield match.group(1)
|
||||
|
||||
|
||||
def get_all_file_paths(src_dir: os.PathLike, extensions: set[str]) -> Iterable[str]:
|
||||
for root, _, files in os.walk(src_dir):
|
||||
for name in files:
|
||||
full_name: str = os.path.join(root, name)
|
||||
extension = os.path.splitext(full_name)[1]
|
||||
if extension in extensions:
|
||||
yield os.path.join(root, name)
|
||||
|
||||
|
||||
def matching_substrings(file_path: str, substrings: Iterable[bytes]) -> Iterable[bytes]:
|
||||
with open(file_path, "rb") as file:
|
||||
contents = file.read()
|
||||
return filter(lambda s: s in contents, substrings)
|
||||
|
||||
|
||||
def get_unused_keys(
|
||||
strings_file: pathlib.Path,
|
||||
src_dirs: Iterable[pathlib.Path],
|
||||
extensions: set[str],
|
||||
) -> Iterable[str]:
|
||||
all_keys = get_all_keys(strings_file)
|
||||
all_keys_as_bytes = [key.encode() for key in all_keys]
|
||||
|
||||
keys_not_seen = set(all_keys_as_bytes)
|
||||
|
||||
for src_dir in src_dirs:
|
||||
for file_path in get_all_file_paths(src_dir, extensions):
|
||||
keys_seen = matching_substrings(file_path, keys_not_seen)
|
||||
keys_not_seen -= set(keys_seen)
|
||||
|
||||
return map(lambda k: k.decode(), keys_not_seen)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
|
||||
unused_keys = get_unused_keys(args.strings, args.src_dirs, set(args.extensions))
|
||||
sorted_unused_keys = sorted(unused_keys)
|
||||
|
||||
for key in sorted_unused_keys:
|
||||
print(key)
|
||||
|
||||
if len(sorted_unused_keys) != 0:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
31
Scripts/update_plist_info.sh
Executable file
31
Scripts/update_plist_info.sh
Executable file
|
@ -0,0 +1,31 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# PROJECT_DIR will be set when run from xcode, else we infer it
|
||||
if [ "${PROJECT_DIR}" = "" ]; then
|
||||
PROJECT_DIR=`git rev-parse --show-toplevel`
|
||||
echo "inferred ${PROJECT_DIR}"
|
||||
fi
|
||||
|
||||
# Capture project hashes that we want to add to the Info.plist
|
||||
cd $PROJECT_DIR
|
||||
_git_commit_signal=`git log --pretty=oneline --decorate=no | head -1`
|
||||
|
||||
# Remove existing .plist entry, if any.
|
||||
/usr/libexec/PlistBuddy -c "Delete BuildDetails" Signal/Signal-Info.plist || true
|
||||
# Add new .plist entry.
|
||||
/usr/libexec/PlistBuddy -c "add BuildDetails dict" Signal/Signal-Info.plist
|
||||
|
||||
echo "CONFIGURATION: ${CONFIGURATION}"
|
||||
if [ "${CONFIGURATION}" = "App Store Release" ]; then
|
||||
/usr/libexec/PlistBuddy -c "add :BuildDetails:XCodeVersion string '${XCODE_VERSION_MAJOR}.${XCODE_VERSION_MINOR}'" Signal/Signal-Info.plist
|
||||
/usr/libexec/PlistBuddy -c "add :BuildDetails:SignalCommit string '$_git_commit_signal'" Signal/Signal-Info.plist
|
||||
|
||||
# Use UTC
|
||||
_build_datetime=`date -u`
|
||||
/usr/libexec/PlistBuddy -c "add :BuildDetails:DateTime string '$_build_datetime'" Signal/Signal-Info.plist
|
||||
|
||||
_build_timestamp=`date +%s`
|
||||
/usr/libexec/PlistBuddy -c "add :BuildDetails:Timestamp integer $_build_timestamp" Signal/Signal-Info.plist
|
||||
fi
|
10
Scripts/upload_metadata
Executable file
10
Scripts/upload_metadata
Executable file
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
BASE_DIR="$(git rev-parse --show-toplevel)"
|
||||
|
||||
cd "$BASE_DIR"
|
||||
|
||||
bundle exec fastlane deliver --skip-screenshots --skip-binary-upload --username "$FASTLANE_USERNAME" --app_identifier 'org.whispersystems.signal'
|
20540
Signal.xcodeproj/project.pbxproj
Normal file
20540
Signal.xcodeproj/project.pbxproj
Normal file
File diff suppressed because it is too large
Load diff
7
Signal.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
Signal.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:Signal.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -0,0 +1,22 @@
|
|||
<?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>classNames</key>
|
||||
<dict>
|
||||
<key>GroupsPerfTest</key>
|
||||
<dict>
|
||||
<key>testMembershipSerialization()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>18.1</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,244 @@
|
|||
<?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>classNames</key>
|
||||
<dict>
|
||||
<key>MessageProcessingPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_messageProcessing()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>10.494</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>MessageSendingPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_messageSending_contactThread()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>5.0476</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_messageSending_groupThread()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>8.0126</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYapDBPerf_messageSending_groupThread()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>6.4592</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SDSPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_enumerateMessagesBatched()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.175</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_enumerateMessagesUnbatched()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.176</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_fetchMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.306</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_insertMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.197</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_enumerateMessagesBatched()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.0288</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_enumerateMessagesUnbatched()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.029</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_fetchMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.154</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_insertMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.1995</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>ThreadFinderPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_enumerateVisibleThreads()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.0143</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_enumerateVisibleThreads_isArchived()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.00838</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_enumerateVisibleThreads()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.015624</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_enumerateVisibleThreads_isArchived()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.00747</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>ThreadPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_writeAndDeleteThreadAndInteractions()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>3.27</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_writeAndUpdateAndDeleteThreadAndInteractions()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>5.05</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_writeThreadAndInteractions()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>1.91</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_writeAndDeleteThreadAndInteractions()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>3.41</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_writeAndUpdateAndDeleteThreadAndInteractions()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>5.62</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_writeThreadAndInteractions()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>1.93</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,178 @@
|
|||
<?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>classNames</key>
|
||||
<dict>
|
||||
<key>MessageProcessingPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_messageProcessing()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>11.1</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYapDBPerf_messageProcessing()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>12</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>MessageSendingPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_messageSending_contactThread()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>5.95</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_messageSending_groupThread()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>10.4</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYapDBPerf_messageSending_contactThread()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>5.43</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYapDBPerf_messageSending_groupThread()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>7.35</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SDSPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_enumerateMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.127</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_enumerateMessagesBatched()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.13696</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Oct 2, 2019 at 11:35:38 AM</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_enumerateMessagesUnbatched()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.13425</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Oct 2, 2019 at 11:35:38 AM</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_fetchMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.22541</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Oct 2, 2019 at 11:35:38 AM</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_insertMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.11905</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Oct 2, 2019 at 11:35:38 AM</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_enumerateMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.0231</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_enumerateMessagesBatched()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.023204</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Oct 2, 2019 at 11:35:38 AM</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_enumerateMessagesUnbatched()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.023133</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Oct 2, 2019 at 11:35:38 AM</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_fetchMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.12264</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Oct 2, 2019 at 11:35:38 AM</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_insertMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.17806</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Oct 2, 2019 at 11:35:38 AM</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,24 @@
|
|||
<?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>classNames</key>
|
||||
<dict>
|
||||
<key>MessageProcessingPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_messageProcessing()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>1.44</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
<key>maxPercentRelativeStandardDeviation</key>
|
||||
<real>5</real>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,72 @@
|
|||
<?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>classNames</key>
|
||||
<dict>
|
||||
<key>SDSPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_enumerateMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.113</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_fetchMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.191</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_insertMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.131</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_enumerateMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.0171</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_fetchMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.0672</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_insertMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.19</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,168 @@
|
|||
<?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>classNames</key>
|
||||
<dict>
|
||||
<key>MessageProcessingPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_messageProcessing()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>11.4</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYapDBPerf_messageProcessing()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>10.015</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>test_messageProcessing_performance()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>1.3446</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>MessageSendingPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_messageSending()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>5.2224</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_messageSending_contactThread()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>6.69</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_messageSending_groupThread()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>9.6442</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYapDBPerf_messageSending()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>2.312</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYapDBPerf_messageSending_contactThread()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>4.02</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYapDBPerf_messageSending_groupThread()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>5.356</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SDSPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_enumerateMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.10592</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_fetchMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.19588</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_insertMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.12945</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_enumerateMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.018093</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_fetchMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.069584</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_insertMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.17957</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,158 @@
|
|||
<?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>classNames</key>
|
||||
<dict>
|
||||
<key>MessageProcessingPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_messageProcessing()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>9.8856</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYapDBPerf_messageProcessing()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>9.9594</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>MessageSendingPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_messageSending_contactThread()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>4.2868</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_messageSending_groupThread()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>5.6435</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYapDBPerf_messageSending_contactThread()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>3.88</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYapDBPerf_messageSending_groupThread()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>4.95</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SDSPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_enumerateMessagesBatched()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.10914</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_enumerateMessagesUnbatched()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.10563</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_fetchMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.2</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_insertMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.105</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_enumerateMessagesBatched()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.017627</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_enumerateMessagesUnbatched()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.01791</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_fetchMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.0708</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_insertMessages()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.13926</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,115 @@
|
|||
<?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>classNames</key>
|
||||
<dict>
|
||||
<key>ThreadFinderPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_enumerateVisibleThreads()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.0245</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_enumerateVisibleThreads_isArchived()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.018849</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_enumerateVisibleThreads()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.0239</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_enumerateVisibleThreads_isArchived()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.0066804</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>ThreadPerformanceTest</key>
|
||||
<dict>
|
||||
<key>testGRDBPerf_writeAndDeleteThreadAndInteractions()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>2.17</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_writeAndUpdateAndDeleteThreadAndInteractions()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>3.78</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGRDBPerf_writeThreadAndInteractions()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>1.17</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_writeAndDeleteThreadAndInteractions()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>2.31</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_writeAndUpdateAndDeleteThreadAndInteractions()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>3.86</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testYDBPerf_writeThreadAndInteractions()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>1.28</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,200 @@
|
|||
<?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>runDestinationsByUUID</key>
|
||||
<dict>
|
||||
<key>09383BD3-F3BC-48E2-9723-537003DB7DF4</key>
|
||||
<dict>
|
||||
<key>localComputer</key>
|
||||
<dict>
|
||||
<key>busSpeedInMHz</key>
|
||||
<integer>400</integer>
|
||||
<key>cpuCount</key>
|
||||
<integer>1</integer>
|
||||
<key>cpuKind</key>
|
||||
<string>6-Core Intel Core i7</string>
|
||||
<key>cpuSpeedInMHz</key>
|
||||
<integer>2600</integer>
|
||||
<key>logicalCPUCoresPerPackage</key>
|
||||
<integer>12</integer>
|
||||
<key>modelCode</key>
|
||||
<string>MacBookPro16,1</string>
|
||||
<key>physicalCPUCoresPerPackage</key>
|
||||
<integer>6</integer>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.macosx</string>
|
||||
</dict>
|
||||
<key>targetArchitecture</key>
|
||||
<string>x86_64</string>
|
||||
<key>targetDevice</key>
|
||||
<dict>
|
||||
<key>modelCode</key>
|
||||
<string>iPhone12,1</string>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.iphonesimulator</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>2A9B7238-1F44-4D6A-ABE4-8203B463EBB8</key>
|
||||
<dict>
|
||||
<key>targetArchitecture</key>
|
||||
<string>arm64</string>
|
||||
<key>targetDevice</key>
|
||||
<dict>
|
||||
<key>modelCode</key>
|
||||
<string>iPhone8,1</string>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.iphoneos</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>3A07A21C-5553-4483-A370-CD3C99B8B0DA</key>
|
||||
<dict>
|
||||
<key>targetArchitecture</key>
|
||||
<string>arm64</string>
|
||||
<key>targetDevice</key>
|
||||
<dict>
|
||||
<key>modelCode</key>
|
||||
<string>iPhone9,3</string>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.iphoneos</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>43FCDABA-A342-4D4E-9E33-17434BF867F1</key>
|
||||
<dict>
|
||||
<key>localComputer</key>
|
||||
<dict>
|
||||
<key>busSpeedInMHz</key>
|
||||
<integer>400</integer>
|
||||
<key>cpuCount</key>
|
||||
<integer>1</integer>
|
||||
<key>cpuKind</key>
|
||||
<string>6-Core Intel Core i7</string>
|
||||
<key>cpuSpeedInMHz</key>
|
||||
<integer>2600</integer>
|
||||
<key>logicalCPUCoresPerPackage</key>
|
||||
<integer>12</integer>
|
||||
<key>modelCode</key>
|
||||
<string>MacBookPro15,1</string>
|
||||
<key>physicalCPUCoresPerPackage</key>
|
||||
<integer>6</integer>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.macosx</string>
|
||||
</dict>
|
||||
<key>targetArchitecture</key>
|
||||
<string>x86_64</string>
|
||||
<key>targetDevice</key>
|
||||
<dict>
|
||||
<key>modelCode</key>
|
||||
<string>iPhone12,5</string>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.iphonesimulator</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>DABF98F2-C093-481E-85D7-95CD7E3C6268</key>
|
||||
<dict>
|
||||
<key>localComputer</key>
|
||||
<dict>
|
||||
<key>busSpeedInMHz</key>
|
||||
<integer>100</integer>
|
||||
<key>cpuCount</key>
|
||||
<integer>1</integer>
|
||||
<key>cpuKind</key>
|
||||
<string>Intel Core i7</string>
|
||||
<key>cpuSpeedInMHz</key>
|
||||
<integer>2900</integer>
|
||||
<key>logicalCPUCoresPerPackage</key>
|
||||
<integer>8</integer>
|
||||
<key>modelCode</key>
|
||||
<string>MacBookPro14,3</string>
|
||||
<key>physicalCPUCoresPerPackage</key>
|
||||
<integer>4</integer>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.macosx</string>
|
||||
</dict>
|
||||
<key>targetArchitecture</key>
|
||||
<string>x86_64</string>
|
||||
<key>targetDevice</key>
|
||||
<dict>
|
||||
<key>modelCode</key>
|
||||
<string>iPad4,2</string>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.iphonesimulator</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>DC544670-CFDC-4C31-A32E-C4646274F958</key>
|
||||
<dict>
|
||||
<key>localComputer</key>
|
||||
<dict>
|
||||
<key>busSpeedInMHz</key>
|
||||
<integer>100</integer>
|
||||
<key>cpuCount</key>
|
||||
<integer>1</integer>
|
||||
<key>cpuKind</key>
|
||||
<string>Intel Core i7</string>
|
||||
<key>cpuSpeedInMHz</key>
|
||||
<integer>2900</integer>
|
||||
<key>logicalCPUCoresPerPackage</key>
|
||||
<integer>8</integer>
|
||||
<key>modelCode</key>
|
||||
<string>MacBookPro14,3</string>
|
||||
<key>physicalCPUCoresPerPackage</key>
|
||||
<integer>4</integer>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.macosx</string>
|
||||
</dict>
|
||||
<key>targetArchitecture</key>
|
||||
<string>x86_64</string>
|
||||
<key>targetDevice</key>
|
||||
<dict>
|
||||
<key>modelCode</key>
|
||||
<string>iPhone7,2</string>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.iphonesimulator</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>E0D1316A-E4EB-4B8E-878F-2D6E7D027E18</key>
|
||||
<dict>
|
||||
<key>localComputer</key>
|
||||
<dict>
|
||||
<key>busSpeedInMHz</key>
|
||||
<integer>100</integer>
|
||||
<key>cpuCount</key>
|
||||
<integer>1</integer>
|
||||
<key>cpuKind</key>
|
||||
<string>Intel Core i7</string>
|
||||
<key>cpuSpeedInMHz</key>
|
||||
<integer>2900</integer>
|
||||
<key>logicalCPUCoresPerPackage</key>
|
||||
<integer>8</integer>
|
||||
<key>modelCode</key>
|
||||
<string>MacBookPro14,3</string>
|
||||
<key>physicalCPUCoresPerPackage</key>
|
||||
<integer>4</integer>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.macosx</string>
|
||||
</dict>
|
||||
<key>targetArchitecture</key>
|
||||
<string>x86_64</string>
|
||||
<key>targetDevice</key>
|
||||
<dict>
|
||||
<key>modelCode</key>
|
||||
<string>iPhone8,1</string>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.iphonesimulator</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>F8C9511F-97CD-4866-96E4-AB0D4A1A155A</key>
|
||||
<dict>
|
||||
<key>targetArchitecture</key>
|
||||
<string>arm64</string>
|
||||
<key>targetDevice</key>
|
||||
<dict>
|
||||
<key>modelCode</key>
|
||||
<string>iPhone10,6</string>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.iphoneos</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,52 @@
|
|||
<?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>classNames</key>
|
||||
<dict>
|
||||
<key>OWSDatabaseConverterTest</key>
|
||||
<dict>
|
||||
<key>testGranularKeySpecFetchingStrategy</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.039171</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testGranularPassphraseFetchingStrategy</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.22846</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testWideKeyFetchingStrategy</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.039649</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testWidePassphraseFetchingStrategy</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.21819</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,22 @@
|
|||
<?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>classNames</key>
|
||||
<dict>
|
||||
<key>MantlePerfTest</key>
|
||||
<dict>
|
||||
<key>testPerformanceExample()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>3.19</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,22 @@
|
|||
<?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>classNames</key>
|
||||
<dict>
|
||||
<key>MantlePerfTest</key>
|
||||
<dict>
|
||||
<key>testPerformanceExample()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.62862</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,102 @@
|
|||
<?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>runDestinationsByUUID</key>
|
||||
<dict>
|
||||
<key>8A553EB1-B9DF-4DDE-8F93-10474ECF05C2</key>
|
||||
<dict>
|
||||
<key>localComputer</key>
|
||||
<dict>
|
||||
<key>busSpeedInMHz</key>
|
||||
<integer>100</integer>
|
||||
<key>cpuCount</key>
|
||||
<integer>1</integer>
|
||||
<key>cpuKind</key>
|
||||
<string>Intel Core i7</string>
|
||||
<key>cpuSpeedInMHz</key>
|
||||
<integer>2900</integer>
|
||||
<key>logicalCPUCoresPerPackage</key>
|
||||
<integer>8</integer>
|
||||
<key>modelCode</key>
|
||||
<string>MacBookPro13,3</string>
|
||||
<key>physicalCPUCoresPerPackage</key>
|
||||
<integer>4</integer>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.macosx</string>
|
||||
</dict>
|
||||
<key>targetArchitecture</key>
|
||||
<string>x86_64</string>
|
||||
<key>targetDevice</key>
|
||||
<dict>
|
||||
<key>modelCode</key>
|
||||
<string>iPhone8,1</string>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.iphonesimulator</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>F29A0D38-3F89-44CA-8BDC-C99EE6C1AB81</key>
|
||||
<dict>
|
||||
<key>localComputer</key>
|
||||
<dict>
|
||||
<key>busSpeedInMHz</key>
|
||||
<integer>100</integer>
|
||||
<key>cpuCount</key>
|
||||
<integer>1</integer>
|
||||
<key>cpuKind</key>
|
||||
<string>Intel Core i7</string>
|
||||
<key>cpuSpeedInMHz</key>
|
||||
<integer>2900</integer>
|
||||
<key>logicalCPUCoresPerPackage</key>
|
||||
<integer>8</integer>
|
||||
<key>modelCode</key>
|
||||
<string>MacBookPro14,3</string>
|
||||
<key>physicalCPUCoresPerPackage</key>
|
||||
<integer>4</integer>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.macosx</string>
|
||||
</dict>
|
||||
<key>targetArchitecture</key>
|
||||
<string>x86_64</string>
|
||||
<key>targetDevice</key>
|
||||
<dict>
|
||||
<key>modelCode</key>
|
||||
<string>iPhone10,6</string>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.iphonesimulator</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>F934A594-2E7D-49A5-9B1B-63A05DABCE3C</key>
|
||||
<dict>
|
||||
<key>localComputer</key>
|
||||
<dict>
|
||||
<key>busSpeedInMHz</key>
|
||||
<integer>100</integer>
|
||||
<key>cpuCount</key>
|
||||
<integer>1</integer>
|
||||
<key>cpuKind</key>
|
||||
<string>Intel Core i7</string>
|
||||
<key>cpuSpeedInMHz</key>
|
||||
<integer>2900</integer>
|
||||
<key>logicalCPUCoresPerPackage</key>
|
||||
<integer>8</integer>
|
||||
<key>modelCode</key>
|
||||
<string>MacBookPro14,3</string>
|
||||
<key>physicalCPUCoresPerPackage</key>
|
||||
<integer>4</integer>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.macosx</string>
|
||||
</dict>
|
||||
<key>targetArchitecture</key>
|
||||
<string>x86_64</string>
|
||||
<key>targetDevice</key>
|
||||
<dict>
|
||||
<key>modelCode</key>
|
||||
<string>iPhone7,2</string>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.iphonesimulator</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
165
Signal.xcodeproj/xcshareddata/xcschemes/Signal-Staging.xcscheme
Normal file
165
Signal.xcodeproj/xcshareddata/xcschemes/Signal-Staging.xcscheme
Normal file
|
@ -0,0 +1,165 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1600"
|
||||
version = "1.8">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D221A088169C9E5E00537ABF"
|
||||
BuildableName = "Signal.app"
|
||||
BlueprintName = "Signal"
|
||||
ReferencedContainer = "container:Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "NO">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D221A088169C9E5E00537ABF"
|
||||
BuildableName = "Signal.app"
|
||||
BlueprintName = "Signal"
|
||||
ReferencedContainer = "container:Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "runningTests_dontStartApp"
|
||||
value = "YES"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D221A0A9169C9E5F00537ABF"
|
||||
BuildableName = "SignalTests.xctest"
|
||||
BlueprintName = "SignalTests"
|
||||
ReferencedContainer = "container:Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "34A954B0271A471300B05242"
|
||||
BuildableName = "SignalUITests.xctest"
|
||||
BlueprintName = "SignalUITests"
|
||||
ReferencedContainer = "container:Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F9C5C89D289451B900548EEE"
|
||||
BuildableName = "SignalServiceKitTests.xctest"
|
||||
BlueprintName = "SignalServiceKitTests"
|
||||
ReferencedContainer = "container:Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
<SkippedTests>
|
||||
<Test
|
||||
Identifier = "MessageSendLogTests/testPlaintextMismatchFails()">
|
||||
</Test>
|
||||
</SkippedTests>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
disableMainThreadChecker = "YES"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
migratedStopOnEveryIssue = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES"
|
||||
showNonLocalizedStrings = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D221A088169C9E5E00537ABF"
|
||||
BuildableName = "Signal.app"
|
||||
BlueprintName = "Signal"
|
||||
ReferencedContainer = "container:Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<CommandLineArguments>
|
||||
<CommandLineArgument
|
||||
argument = "-AppLaunchesAttempted 1"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "OS_ACTIVITY_MODE"
|
||||
value = "disable"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "USE_STAGING"
|
||||
value = "1"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "UBSAN_OPTIONS"
|
||||
value = "suppressions=SignalUBSan.supp"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "TSAN_OPTIONS"
|
||||
value = "suppressions=SignalTSan.supp"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "DYLD_PRINT_STATISTICS"
|
||||
value = "1"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profiling"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D221A088169C9E5E00537ABF"
|
||||
BuildableName = "Signal.app"
|
||||
BlueprintName = "Signal"
|
||||
ReferencedContainer = "container:Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "App Store Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue