From f23190cf29f093869023fd2cd773fcf04519195d Mon Sep 17 00:00:00 2001 From: Mattermost Build Date: Tue, 20 Oct 2020 02:21:37 +0200 Subject: [PATCH] Build Improvements (#4884) (#4902) * Use AppGroupId from Info.plists instead of hardcoded constant * Update script, ci & Makefile * Update Cocoapods to 1.9.3 * Split android builds using ABI filters * Update Fastlane deps & build scripts * Update CI to use latests scripts * Display app version & build number in select server screen * Make generate scripts compatible with node < 12 * Build scripts * add build script to package.json * Update to use bundler 2.1.4 and CI with Xcode 12 * Fix script name for build:ios-unsigned * Fix RN iOS scripts * Update CI pods-dependencies step * Add pipefail to android executor * Update Fastlane * Fix type in postinstall script * update android executor and set TERM * Fix S3 bucket name variable * Apply suggestions from code review Co-authored-by: Miguel Alatzar * Fix master unit tests * use requireActual in jest setup * Jest setup to use react instead of React Co-authored-by: Miguel Alatzar (cherry picked from commit 30d4aa2a3e9239d61f8bd9e866fb07d7cdeafda5) Co-authored-by: Elias Nahum --- .circleci/config.yml | 80 ++--- Makefile | 232 -------------- android/app/build.gradle | 9 +- .../announcement_banner.test.js.snap | 6 +- app/components/app_version.tsx | 38 +++ .../__snapshots__/index.test.js.snap | 3 +- .../__snapshots__/emoji_picker.test.js.snap | 27 +- .../emoji_picker/emoji_picker.test.js | 18 +- .../interactive_dialog_controller.test.js | 3 - .../network_indicator.test.js.snap | 3 +- .../camera_quick_action.test.js.snap | 3 +- .../file_quick_action.test.js.snap | 3 +- .../image_quick_action.test.js.snap | 3 +- .../__snapshots__/read_only.test.js.snap | 4 +- .../__snapshots__/send.test.js.snap | 9 +- .../__snapshots__/post_header.test.js.snap | 9 +- .../post_pre_header.test.js.snap | 18 +- .../more_messages_button.test.js.snap | 8 +- .../reaction_picker.test.js.snap | 6 +- .../__snapshots__/reactions.test.js.snap | 3 +- .../show_more_button.test.js.snap | 9 +- .../__snapshots__/user_status.test.js.snap | 16 +- .../__snapshots__/radio_setting.test.js.snap | 4 +- app/screens/channel/channel_base.test.js | 3 - .../settings_drawer_button.test.js.snap | 3 +- .../channel_drawer_button.test.js.snap | 3 +- .../channel_search_button.test.js.snap | 3 +- .../__snapshots__/channel_title.test.js.snap | 12 +- .../forgot_password.test.js.snap | 9 +- .../__snapshots__/image_preview.test.js.snap | 9 +- .../__snapshots__/long_post.test.js.snap | 3 +- .../__snapshots__/more_channels.test.js.snap | 4 +- .../options_modal_list.test.js.snap | 12 +- .../__snapshots__/permalink.test.js.snap | 7 +- .../__snapshots__/recent_item.test.js.snap | 3 +- app/screens/select_server/select_server.js | 2 + fastlane/Fastfile | 284 +++++++++--------- fastlane/Gemfile.lock | 24 +- Gemfile => ios/Gemfile | 2 +- Gemfile.lock => ios/Gemfile.lock | 67 +++-- ios/Mattermost/Info.plist | 4 + ios/Mattermost/MattermostManaged.m | 11 +- ios/MattermostShare/Config.swift | 3 +- ios/MattermostShare/Info.plist | 2 + ios/NotificationService/Info.plist | 2 + ios/Podfile.lock | 4 +- .../UploadAttachments/MMMConstants.h | 1 - .../UploadAttachments/MMMConstants.m | 1 - .../UploadAttachments/MattermostBucket.m | 14 +- .../UploadAttachments/StoreManager.m | 4 +- .../UploadAttachments/UploadSession.swift | 6 +- .../UploadSessionManager.swift | 5 +- ios/bundleReactNative.sh | 2 - ios/react-native-xcode.sh | 24 -- package-lock.json | 9 - package.json | 23 +- ...atch => react-native-elements+2.3.2.patch} | 0 patches/react-native-mmkv-storage+0.3.7.patch | 7 +- scripts/build.sh | 97 ++++++ scripts/clean.sh | 11 + ...make-dist-assets.js => generate-assets.js} | 22 +- scripts/postinstall.sh | 19 ++ test/setup.js | 25 ++ 63 files changed, 586 insertions(+), 674 deletions(-) delete mode 100644 Makefile create mode 100644 app/components/app_version.tsx rename Gemfile => ios/Gemfile (50%) rename Gemfile.lock => ios/Gemfile.lock (51%) rename patches/{react-native-elements+2.3.0.patch => react-native-elements+2.3.2.patch} (100%) create mode 100755 scripts/build.sh create mode 100755 scripts/clean.sh rename scripts/{make-dist-assets.js => generate-assets.js} (85%) create mode 100755 scripts/postinstall.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 8a34c7270..002eeff39 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,7 @@ executors: NODE_ENV: production BABEL_ENV: production docker: - - image: circleci/android:api-27-node + - image: circleci/android:api-29-node working_directory: ~/mattermost-mobile resource_class: <> @@ -23,7 +23,7 @@ executors: NODE_ENV: production BABEL_ENV: production macos: - xcode: "11.0.0" + xcode: "12.0.0" working_directory: ~/mattermost-mobile shell: /bin/bash --login -o pipefail @@ -44,7 +44,6 @@ commands: for: type: string steps: - - ruby-setup - restore_cache: name: Restore Fastlane cache key: v1-gems-<< parameters.for >>-{{ checksum "fastlane/Gemfile.lock" }}-{{ arch }} @@ -82,7 +81,7 @@ commands: key: v1-assets-{{ checksum "assets/base/config.json" }}-{{ arch }} - run: name: Generate assets - command: make dist/assets + command: node ./scripts/generate-assets.js - save_cache: name: Save assets cache key: v1-assets-{{ checksum "assets/base/config.json" }}-{{ arch }} @@ -104,8 +103,8 @@ commands: paths: - node_modules - run: - name: "Run post install scripts" - command: make post-install + name: "Patch dependencies" + command: npx patch-package pods-dependencies: description: "Get cocoapods dependencies" @@ -113,10 +112,12 @@ commands: - restore_cache: name: Restore cocoapods specs and pods key: v1-cocoapods-{{ checksum "ios/Podfile.lock" }}-{{ arch }} + - run: + name: iOS gems + command: npm run ios-gems - run: name: Getting cocoapods dependencies - working_directory: ios - command: pod install + command: npm run pod-install - save_cache: name: Save cocoapods specs and pods cache key: v1-cocoapods-{{ checksum "ios/Podfile.lock" }}-{{ arch }} @@ -143,11 +144,14 @@ commands: echo MATTERMOST_RELEASE_STORE_FILE=${STORE_FILE} | tee -a android/gradle.properties > /dev/null echo ${STORE_ALIAS} | tee -a android/gradle.properties > /dev/null echo ${STORE_PASSWORD} | tee -a android/gradle.properties > /dev/null + - run: + name: Jetify android libraries + command: ./node_modules/.bin/jetify - run: working_directory: fastlane name: Run fastlane to build android no_output_timeout: 30m - command: bundle exec fastlane android build + command: export TERM=xterm && bundle exec fastlane android build build-ios: description: "Build the iOS app" @@ -165,7 +169,7 @@ commands: no_output_timeout: 30m command: | HOMEBREW_NO_AUTO_UPDATE=1 brew install watchman - bundle exec fastlane ios build + export TERM=xterm && bundle exec fastlane ios build deploy-to-store: description: "Deploy build to store" @@ -201,12 +205,6 @@ commands: - store_artifacts: path: ~/mattermost-mobile/<> - ruby-setup: - steps: - - run: - name: Set Ruby Version - command: echo "ruby-2.6.3" > ~/.ruby-version - jobs: test: working_directory: ~/mattermost-mobile @@ -225,19 +223,7 @@ jobs: command: npm test - run: name: Check i18n - command: | - mkdir -p tmp - cp assets/base/i18n/en.json tmp/en.json - mkdir -p tmp/fake-webapp-dir/i18n/ - echo '{}' > tmp/fake-webapp-dir/i18n/en.json - - npm run mmjstool -- i18n extract-mobile --webapp-dir tmp/fake-webapp-dir --mobile-dir . - diff tmp/en.json assets/base/i18n/en.json - # Address weblate behavior which does not remove whole translation item when translation string is set to empty - npm run mmjstool -- i18n clean-empty --webapp-dir tmp/fake-webapp-dir --mobile-dir . --check - - rm -rf tmp - + command: ./scripts/precommit/i18n.sh check-deps: parameters: @@ -297,7 +283,7 @@ jobs: - build-android - persist - save: - filename: "Mattermost_Beta.apk" + filename: "*.apk" build-android-release: executor: android @@ -305,7 +291,7 @@ jobs: - build-android - persist - save: - filename: "Mattermost.apk" + filename: "*.apk" build-android-pr: executor: android @@ -314,7 +300,7 @@ jobs: steps: - build-android - save: - filename: "Mattermost_Beta.apk" + filename: "*.apk" build-android-unsigned: executor: android @@ -326,6 +312,9 @@ jobs: - fastlane-dependencies: for: android - gradle-dependencies + - run: + name: Jetify Android libraries + command: ./node_modules/.bin/jetify - run: working_directory: fastlane name: Run fastlane to build unsigned android @@ -333,7 +322,7 @@ jobs: command: bundle exec fastlane android unsigned - persist - save: - filename: "Mattermost-unsigned.apk" + filename: "*.apk" build-ios-beta: executor: ios @@ -341,7 +330,7 @@ jobs: - build-ios - persist - save: - filename: "Mattermost_Beta.ipa" + filename: "*.ipa" build-ios-release: executor: ios @@ -349,7 +338,7 @@ jobs: - build-ios - persist - save: - filename: "Mattermost.ipa" + filename: "*.ipa" build-ios-pr: executor: ios @@ -358,14 +347,13 @@ jobs: steps: - build-ios - save: - filename: "Mattermost_Beta.ipa" + filename: "*.ipa" build-ios-unsigned: executor: ios steps: - checkout: path: ~/mattermost-mobile - - ruby-setup - npm-dependencies - pods-dependencies - assets @@ -381,16 +369,15 @@ jobs: - persist_to_workspace: root: ~/ paths: - - mattermost-mobile/Mattermost-unsigned.ipa + - mattermost-mobile/*.ipa - save: - filename: "Mattermost-unsigned.ipa" + filename: "*.ipa" build-ios-simulator: executor: ios steps: - checkout: path: ~/mattermost-mobile - - ruby-setup - npm-dependencies - pods-dependencies - assets @@ -415,47 +402,42 @@ jobs: name: android resource_class: medium steps: - - ruby-setup - deploy-to-store: task: "Deploy to Google Play" target: android - file: Mattermost.apk + file: "*.apk" deploy-android-beta: executor: name: android resource_class: medium steps: - - ruby-setup - deploy-to-store: task: "Deploy to Google Play" target: android - file: Mattermost_Beta.apk + file: "*.apk" deploy-ios-release: executor: ios steps: - - ruby-setup - deploy-to-store: task: "Deploy to TestFlight" target: ios - file: Mattermost.ipa + file: "*.ipa" deploy-ios-beta: executor: ios steps: - - ruby-setup - deploy-to-store: task: "Deploy to TestFlight" target: ios - file: Mattermost_Beta.ipa + file: "*.ipa" github-release: executor: name: android resource_class: medium steps: - - ruby-setup - attach_workspace: at: ~/ - run: diff --git a/Makefile b/Makefile deleted file mode 100644 index 86ded9dbf..000000000 --- a/Makefile +++ /dev/null @@ -1,232 +0,0 @@ -.PHONY: pre-run pre-build clean -.PHONY: check-style -.PHONY: start stop -.PHONY: run run-ios run-android -.PHONY: build build-ios build-android unsigned-ios unsigned-android ios-sim-x86_64 -.PHONY: build-pr can-build-pr prepare-pr -.PHONY: test help - -OS := $(shell sh -c 'uname -s 2>/dev/null') -BASE_ASSETS = $(shell find assets/base -type d) $(shell find assets/base -type f -name '*') -OVERRIDE_ASSETS = $(shell find assets/override -type d 2> /dev/null) $(shell find assets/override -type f -name '*' 2> /dev/null) -MM_UTILITIES_DIR = ../mattermost-utilities - -node_modules: package.json - @if ! [ $(shell which npm 2> /dev/null) ]; then \ - echo "npm is not installed https://npmjs.com"; \ - exit 1; \ - fi - - @echo Getting Javascript dependencies - @npm install - -npm-ci: package.json - @if ! [ $(shell which npm 2> /dev/null) ]; then \ - echo "npm is not installed https://npmjs.com"; \ - exit 1; \ - fi - - @echo Getting Javascript dependencies - @npm ci - -.podinstall: -ifeq ($(OS), Darwin) - @echo "Required version of Cocoapods is not installed" - @echo Installing gems; - @bundle install - @echo Getting Cocoapods dependencies; - @cd ios && bundle exec pod install; -endif - @touch $@ - -dist/assets: $(BASE_ASSETS) $(OVERRIDE_ASSETS) - @mkdir -p dist - - @if [ -e dist/assets ] ; then \ - rm -rf dist/assets; \ - fi - - @echo "Generating app assets" - @node scripts/make-dist-assets.js - -pre-run: | node_modules .podinstall dist/assets ## Installs dependencies and assets - -pre-build: | npm-ci .podinstall dist/assets ## Install dependencies and assets before building - -check-style: node_modules ## Runs eslint - @echo Checking for style guide compliance - @npm run check - -clean: ## Cleans dependencies, previous builds and temp files - @echo Cleaning started - - @rm -f .podinstall - @rm -rf ios/Pods - @rm -rf node_modules - @rm -rf dist - @rm -rf ios/build - @rm -rf android/app/build - - @echo Cleanup finished - -post-install: - @./node_modules/.bin/patch-package - @./node_modules/.bin/jetify - - @rm -f node_modules/intl/.babelrc - @# Hack to get react-intl and its dependencies to work with react-native - @# Based off of https://github.com/este/este/blob/master/gulp/native-fix.js - @sed -i'' -e 's|"./locale-data/index.js": false|"./locale-data/index.js": "./locale-data/index.js"|g' node_modules/react-intl/package.json - @sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-messageformat/package.json - @sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-relativeformat/package.json - @sed -i'' -e 's|"./locale-data/complete.js": false|"./locale-data/complete.js": "./locale-data/complete.js"|g' node_modules/intl/package.json - -start: | pre-run ## Starts the React Native packager server - $(call start_packager) - -stop: ## Stops the React Native packager server - $(call stop_packager) - -check-device-ios: - @if ! [ $(shell which xcodebuild) ]; then \ - echo "xcode is not installed"; \ - exit 1; \ - fi - @if ! [ $(shell which watchman) ]; then \ - echo "watchman is not installed"; \ - exit 1; \ - fi - -check-device-android: - @if ! [ $(ANDROID_HOME) ]; then \ - echo "ANDROID_HOME is not set"; \ - exit 1; \ - fi - @if ! [ $(shell which adb 2> /dev/null) ]; then \ - echo "adb is not installed"; \ - exit 1; \ - fi - - @echo "Connect your Android device or open the emulator" - @adb wait-for-device - - @if ! [ $(shell which watchman 2> /dev/null) ]; then \ - echo "watchman is not installed"; \ - exit 1; \ - fi - -prepare-android-build: - @rm -rf ./node_modules/react-native/local-cli/templates/HelloWorld - @rm -rf ./node_modules/react-native-linear-gradient/Examples/ - @rm -rf ./node_modules/react-native-orientation/demo/ - -run: run-ios ## alias for run-ios - -run-ios: | check-device-ios pre-run ## Runs the app on an iOS simulator - @if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \ - echo Starting React Native packager server; \ - npm start & echo Running iOS app in development; \ - if [ ! -z "${SIMULATOR}" ]; then \ - react-native run-ios --simulator="${SIMULATOR}"; \ - else \ - react-native run-ios; \ - fi; \ - wait; \ - else \ - echo Running iOS app in development; \ - if [ ! -z "${SIMULATOR}" ]; then \ - react-native run-ios --simulator="${SIMULATOR}"; \ - else \ - react-native run-ios; \ - fi; \ - fi - -run-android: | check-device-android pre-run prepare-android-build ## Runs the app on an Android emulator or dev device - @if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \ - echo Starting React Native packager server; \ - npm start & echo Running Android app in development; \ - if [ ! -z ${VARIANT} ]; then \ - react-native run-android --no-packager --variant=${VARIANT}; \ - else \ - react-native run-android --no-packager; \ - fi; \ - wait; \ - else \ - echo Running Android app in development; \ - if [ ! -z ${VARIANT} ]; then \ - react-native run-android --no-packager --variant=${VARIANT}; \ - else \ - react-native run-android --no-packager; \ - fi; \ - fi - -build: | stop pre-build check-style ## Builds the app for Android & iOS - $(call start_packager) - @echo "Building App" - @cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane build - $(call stop_packager) - - -build-ios: | stop pre-build check-style ## Builds the iOS app - $(call start_packager) - @echo "Building iOS app" - @cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane ios build - $(call stop_packager) - -build-android: | stop pre-build check-style prepare-android-build ## Build the Android app - $(call start_packager) - @echo "Building Android app" - @cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane android build - $(call stop_packager) - -unsigned-ios: stop pre-build check-style ## Build an unsigned version of the iOS app - $(call start_packager) - @cd fastlane && NODE_ENV=production bundle exec fastlane ios unsigned - $(call stop_packager) - -ios-sim-x86_64: stop pre-build check-style ## Build an unsigned x86_64 version of the iOS app for iPhone simulator - $(call start_packager) - @echo "Building unsigned x86_64 iOS app for iPhone simulator" - @cd fastlane && NODE_ENV=production bundle exec fastlane ios simulator - $(call stop_packager) - -unsigned-android: stop pre-build check-style prepare-android-build ## Build an unsigned version of the Android app - @cd fastlane && NODE_ENV=production bundle exec fastlane android unsigned - -test: | pre-run check-style ## Runs tests - @npm test - -build-pr: | can-build-pr stop pre-build check-style ## Build a PR from the mattermost-mobile repo - $(call start_packager) - @echo "Building App from PR ${PR_ID}" - @cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane build_pr pr:PR-${PR_ID} - $(call stop_packager) - -can-build-pr: - @if [ -z ${PR_ID} ]; then \ - echo a PR number needs to be specified; \ - exit 1; \ - fi - -## Help documentation https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html -help: - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' - -define start_packager - @if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \ - echo Starting React Native packager server; \ - npm start & echo; \ - else \ - echo React Native packager server already running; \ - fi -endef - -define stop_packager - @echo Stopping React Native packager server - @if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 1 ]; then \ - ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9; \ - echo React Native packager server stopped; \ - else \ - echo No React Native packager server running; \ - fi -endef diff --git a/android/app/build.gradle b/android/app/build.gradle index d0a780c32..08705c421 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -101,7 +101,7 @@ if (System.getenv("SENTRY_ENABLED") == "true") { * Upload all the APKs to the Play Store and people will download * the correct one based on the CPU architecture of their device. */ -def enableSeparateBuildPerCPUArchitecture = false +def enableSeparateBuildPerCPUArchitecture = project.hasProperty('separateApk') ? project.property('separateApk').toBoolean() : false /** * Run Proguard to shrink the Java bytecode in release builds. @@ -135,9 +135,6 @@ android { versionCode 330 versionName "1.36.0" multiDexEnabled = true - ndk { - abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64' - } testBuildType System.getProperty('testBuildType', 'debug') testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } @@ -155,7 +152,7 @@ android { abi { reset() enable enableSeparateBuildPerCPUArchitecture - universalApk false // If true, also generate a universal APK + universalApk enableSeparateBuildPerCPUArchitecture // If true, also generate a universal APK include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" } } @@ -184,7 +181,7 @@ android { def abi = output.getFilter(OutputFile.ABI) if (abi != null) { // null for the universal-debug, universal-release variants output.versionCodeOverride = - versionCodes.get(abi) * 1048576 + defaultConfig.versionCode + versionCodes.get(abi) * 1000000 + defaultConfig.versionCode } } } diff --git a/app/components/announcement_banner/__snapshots__/announcement_banner.test.js.snap b/app/components/announcement_banner/__snapshots__/announcement_banner.test.js.snap index aff700187..ec043ae11 100644 --- a/app/components/announcement_banner/__snapshots__/announcement_banner.test.js.snap +++ b/app/components/announcement_banner/__snapshots__/announcement_banner.test.js.snap @@ -51,8 +51,7 @@ exports[`AnnouncementBanner should match snapshot 1`] = ` value="Banner Text" /> - - { + return ( + + + + + + ); +}; + +export default AppVersion; \ No newline at end of file diff --git a/app/components/attachment_button/__snapshots__/index.test.js.snap b/app/components/attachment_button/__snapshots__/index.test.js.snap index 884b02fa1..31cc82178 100644 --- a/app/components/attachment_button/__snapshots__/index.test.js.snap +++ b/app/components/attachment_button/__snapshots__/index.test.js.snap @@ -13,8 +13,7 @@ exports[`AttachmentButton should match snapshot 1`] = ` } type="opacity" > - - - - - - - - - - { const state = { ...initialState, @@ -59,17 +61,17 @@ describe('components/emoji_picker/emoji_picker.ios', () => { ]; testCases.forEach((testCase) => { - test(`'${testCase.input}' should return '${testCase.output}'`, () => { + test(`'${testCase.input}' should return '${testCase.output}'`, async () => { expect(filterEmojiSearchInput(testCase.input)).toEqual(testCase.output); }); }); - test('should match snapshot', () => { + test('should match snapshot', async () => { const wrapper = shallowWithIntl(); expect(wrapper.getElement()).toMatchSnapshot(); }); - test('searchEmojis should return the right values on fuse', () => { + test('searchEmojis should return the right values on fuse', async () => { const input = '1'; const output = ['100', '1234', '1st_place_medal', '+1', '-1', 'u7121']; @@ -78,7 +80,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => { expect(result).toEqual(output); }); - test('should set rebuildEmojis to true when deviceWidth changes', () => { + test('should set rebuildEmojis to true when deviceWidth changes', async () => { const wrapper = shallowWithIntl(); const instance = wrapper.instance(); @@ -90,7 +92,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => { expect(instance.rebuildEmojis).toBe(true); }); - test('should rebuild emojis emojis when emojis change', () => { + test('should rebuild emojis emojis when emojis change', async () => { const wrapper = shallowWithIntl(); const instance = wrapper.instance(); const renderableEmojis = jest.spyOn(instance, 'renderableEmojis'); @@ -103,7 +105,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => { expect(renderableEmojis).toHaveBeenCalledWith(baseProps.emojisBySection, baseProps.deviceWidth); }); - test('should set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is true', () => { + test('should set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is true', async () => { const wrapper = shallowWithIntl(); const instance = wrapper.instance(); instance.setState = jest.fn(); @@ -118,7 +120,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => { expect(instance.rebuildEmojis).toBe(false); }); - test('should not set rebuilt emojis when rebuildEmojis is false and searchBarAnimationComplete is true', () => { + test('should not set rebuilt emojis when rebuildEmojis is false and searchBarAnimationComplete is true', async () => { const wrapper = shallowWithIntl(); const instance = wrapper.instance(); instance.setState = jest.fn(); @@ -131,7 +133,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => { expect(instance.setState).not.toHaveBeenCalled(); }); - test('should not set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is false', () => { + test('should not set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is false', async () => { const wrapper = shallowWithIntl(); const instance = wrapper.instance(); instance.setState = jest.fn(); diff --git a/app/components/interactive_dialog_controller/interactive_dialog_controller.test.js b/app/components/interactive_dialog_controller/interactive_dialog_controller.test.js index 604225f11..2cde95a9b 100644 --- a/app/components/interactive_dialog_controller/interactive_dialog_controller.test.js +++ b/app/components/interactive_dialog_controller/interactive_dialog_controller.test.js @@ -7,9 +7,6 @@ import {shallow} from 'enzyme'; import InteractiveDialogController from './interactive_dialog_controller'; jest.mock('react-intl'); -jest.mock('react-native-vector-icons/MaterialIcons', () => ({ - getImageSource: jest.fn().mockResolvedValue({}), -})); describe('InteractiveDialogController', () => { test('should open interactive dialog as alert or screen depending on with or without element', () => { diff --git a/app/components/network_indicator/__snapshots__/network_indicator.test.js.snap b/app/components/network_indicator/__snapshots__/network_indicator.test.js.snap index eab171711..a63701d5b 100644 --- a/app/components/network_indicator/__snapshots__/network_indicator.test.js.snap +++ b/app/components/network_indicator/__snapshots__/network_indicator.test.js.snap @@ -54,8 +54,7 @@ exports[`AttachmentFooter matches snapshot 1`] = ` } } > - - - - - - - - - - - - - - - - - - - - - - - - - - ({ - getImageSource: jest.fn().mockResolvedValue(null), -})); describe('ChannelBase', () => { const channelBaseComponentId = 'component-0'; diff --git a/app/screens/channel/channel_nav_bar/__snapshots__/settings_drawer_button.test.js.snap b/app/screens/channel/channel_nav_bar/__snapshots__/settings_drawer_button.test.js.snap index ba6d23035..3a18e4496 100644 --- a/app/screens/channel/channel_nav_bar/__snapshots__/settings_drawer_button.test.js.snap +++ b/app/screens/channel/channel_nav_bar/__snapshots__/settings_drawer_button.test.js.snap @@ -24,8 +24,7 @@ exports[`SettingDrawerButton should match, full snapshot 1`] = ` } } > - - - My Channel - Other user - Other user - - - - - - - - - - diff --git a/app/screens/options_modal/__snapshots__/options_modal_list.test.js.snap b/app/screens/options_modal/__snapshots__/options_modal_list.test.js.snap index b10e5663d..24d8456c9 100644 --- a/app/screens/options_modal/__snapshots__/options_modal_list.test.js.snap +++ b/app/screens/options_modal/__snapshots__/options_modal_list.test.js.snap @@ -54,8 +54,7 @@ exports[`OptionModalList should match snapshot for Android 1`] = ` ] } /> - - - - - - - + ); diff --git a/fastlane/Fastfile b/fastlane/Fastfile index b3cc93f9f..71ccbdbbe 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,5 +1,4 @@ fastlane_version '2.71.0' - fastlane_require 'aws-sdk-s3' fastlane_require 'erb' fastlane_require 'json' @@ -46,10 +45,6 @@ end after_all do |lane| if ENV['CIRCLECI'] != 'true' - if lane.to_s == 'build' - submit_to_store - end - if ENV['RESET_GIT_BRANCH'] == 'true' branch = ENV['BRANCH_TO_BUILD'] || 'master' package_id = ENV['MAIN_APP_IDENTIFIER'] || 'com.mattermost.rnbeta' @@ -174,106 +169,106 @@ lane :configure do configured = true end -desc 'Build the app for Android and iOS' -lane :build do |options| - configure - set_app_version - set_app_build_number - - # Build the android app - self.runner.current_platform = :android - build - - # Build the ios app - self.runner.current_platform = :ios - build -end - desc 'Upload file to s3' lane :upload_file_to_s3 do |options| - os_type = options[:os_type] - file = options[:file] - file_plist = "" - - if file.nil? || file.empty? - app_name = ENV['APP_NAME'] || 'Mattermost Beta' - filename = app_name.gsub(" ", "_") - - if os_type == 'Android' - file = "#{filename}.apk" - elsif os_type == 'iOS' - file = "#{filename}.ipa" - file_plist = "#{filename}.plist" - end + os_type = options[:os_type].downcase + extension = os_type == "android" ? "*.apk" : "*.ipa" + build_folder_path = Dir[File.expand_path('..')].first + files = [] + + unless options[:file].nil? || options[:file].empty? + files.push("#{build_folder_path}/#{options[:file]}") + else + files = Dir.glob("#{build_folder_path}/#{extension}").select { |f| File.file? f} end - build_folder_path = Dir[File.expand_path('..')].first - file_path = "#{build_folder_path}/#{file}" - - unless ENV['AWS_BUCKET_NAME'].nil? || ENV['AWS_BUCKET_NAME'].empty? || ENV['AWS_REGION'].nil? || ENV['AWS_REGION'].empty? || file_path.nil? - s3_region = ENV['AWS_REGION'] - s3_bucket = ENV['AWS_BUCKET_NAME'] - s3_folder = '' - + unless ENV['AWS_BUCKET_NAME'].nil? || ENV['AWS_BUCKET_NAME'].empty? || ENV['AWS_REGION'].nil? || ENV['AWS_REGION'].empty? || files.length == 0 version_number = '' build_number = '' + pr_file = '' + plist_file = '' if is_build_pr - s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{ENV['BRANCH_TO_BUILD']}" + pr_file = File.basename(files.first) + plist_file = "#{File.basename(files.first, '.*')}.plist" else - if os_type == 'Android' - version_number = android_get_version_name(gradle_file: './android/app/build.gradle') - build_number = android_get_version_code(gradle_file: './android/app/build.gradle') - elsif os_type == 'iOS' - version_number = get_version_number(xcodeproj: './ios/Mattermost.xcodeproj', target: 'Mattermost') - build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj') - end - - s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}" + version_number = os_type == "android" ? + android_get_version_name(gradle_file: './android/app/build.gradle') : + get_version_number(xcodeproj: './ios/Mattermost.xcodeproj', target: 'Mattermost') + build_number = os_type == "android" ? + android_get_version_code(gradle_file: './android/app/build.gradle') : + get_build_number(xcodeproj: './ios/Mattermost.xcodeproj') end + s3_region = ENV['AWS_REGION'] + s3_bucket_name = ENV['AWS_BUCKET_NAME'] + s3_folder = is_build_pr ? + "#{ENV['AWS_FOLDER_NAME']}/#{ENV['BRANCH_TO_BUILD']}" : + "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}" + s3 = Aws::S3::Resource.new(region: s3_region) - file_obj = s3.bucket(s3_bucket).object("#{s3_folder}/#{file}") - file_obj.upload_file("#{file_path}") + s3_bucket = s3.bucket(s3_bucket_name) + mutex = Mutex.new + threads = [] + file_number = 0 + total_files = files.length + links = [] + total_files.times do |i| + threads[i] = Thread.new { + until files.empty? + mutex.synchronize do + file_number += 1 + Thread.current["file_number"] = file_number + end + file = files.pop rescue nil + next unless file + + filename = File.basename(file) + puts "[#{Thread.current["file_number"]}/#{total_files}] #{filename} uploading..." + s3_file = s3_bucket.object("#{s3_folder}/#{filename}") + s3_file.upload_file("#{file}") + links.push(s3_file.public_url) + end + } + end + threads.each { |t| t.join } if is_build_pr if os_type == 'Android' - install_url = "https://#{s3_bucket}/#{s3_folder}/#{file}" + install_url = "https://#{s3_bucket_name}/#{s3_folder}/#{pr_file}" elsif os_type == 'iOS' current_build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj') plist_template = File.read('plist.erb') plist_body = ERB.new(plist_template).result(binding) - plist_obj = s3.bucket(s3_bucket).object("#{s3_folder}/#{file_plist}") + plist_obj = s3_bucket.object("#{s3_folder}/#{file_plist}") plist_obj.put(body: plist_body) - install_url = "itms-services://?action=download-manifest&url=https://#{s3_bucket}/#{s3_folder}/#{file_plist}" + install_url = "itms-services://?action=download-manifest&url=https://#{s3_bucket_name}/#{s3_folder}/#{file_plist}" end qa_build_message({ - :os_type => os_type, - :install_url => install_url - }) + :os_type => os_type, + :install_url => install_url + }) end - - public_link = "https://#{s3_bucket}/#{s3_folder}/#{file}" - if file == 'Mattermost-simulator-x86_64.app.zip' + + if options[:file] == 'Mattermost-simulator-x86_64.app.zip' pretext = '#### New iOS build for VM/Simulator' - msg = "Download link: #{public_link}" + msg = "Download link: #{links.first}" send_message_to_mattermost({ - :version_number => version_number, - :build_number => build_number, - :pretext => pretext, - :title => '', - :thumb_url => 'https://support.apple.com/library/content/dam/edam/applecare/images/en_US/iOS/move-to-ios-icon.png', - :msg => msg, - :default_payloads => [], - :success => true, - }) + :version_number => version_number, + :build_number => build_number, + :pretext => pretext, + :title => '', + :thumb_url => 'https://support.apple.com/library/content/dam/edam/applecare/images/en_US/iOS/move-to-ios-icon.png', + :msg => msg, + :default_payloads => [], + :success => true, + }) end - UI.success("S3 bucket @#{s3_bucket}, object @#{s3_folder}/#{file}") - UI.success("S3 public path: #{public_link}") + UI.message("S3 public paths:\n#{links.join("\n")}") end end @@ -285,8 +280,15 @@ lane :github do version = android_get_version_name(gradle_file: './android/app/build.gradle') build = android_get_version_code(gradle_file: './android/app/build.gradle') changelog = File.read("metadata/changelog") - changelog.concat("* Android [Mattermost.apk](https://releases.mattermost.com/mattermost-mobile/#{version}/#{build}/Mattermost.apk)\n") - changelog.concat("* iOS [Mattermost.ipa](https://releases.mattermost.com/mattermost-mobile/#{version}/#{build}/Mattermost.ipa)") + android = [ + " * [Mattermost arm64-v8a](https://releases.mattermost.com/mattermost-mobile/#{version}/#{build}/Mattermost-arm64-v8a.apk)", + " * [Mattermost armeabi-v7a](https://releases.mattermost.com/mattermost-mobile/#{version}/#{build}/Mattermost-armeabi-v7a.apk)", + " * [Mattermost Universal](https://releases.mattermost.com/mattermost-mobile/#{version}/#{build}/Mattermost-universal.apk)", + " * [Mattermost x86](https://releases.mattermost.com/mattermost-mobile/#{version}/#{build}/Mattermost-x86.apk)", + " * [Mattermost x86_64](https://releases.mattermost.com/mattermost-mobile/#{version}/#{build}/Mattermost-x86_64.apk)", + ] + changelog.concat("* Android\n#{android.join("\n")}\n") + changelog.concat("* iOS\n * [Mattermost](https://releases.mattermost.com/mattermost-mobile/#{version}/#{build}/Mattermost.ipa)") github_release = set_github_release( repository_name: "mattermost/mattermost-mobile", @@ -334,12 +336,12 @@ platform :ios do ENV['APP_NAME'] = 'Mattermost' ENV['REPLACE_ASSETS'] = 'true' ENV['BUILD_FOR_RELEASE'] = 'true' - ENV['APP_SCHEME'] = 'mattermost-mobile' + ENV['APP_SCHEME'] = 'mattermost' update_identifiers replace_assets - sh 'mkdir -p ../build-ios' + sh 'rm -rf ../build-ios/' sh 'cd ../ios/ && xcodebuild -workspace Mattermost.xcworkspace/ -scheme Mattermost -sdk iphoneos -configuration Release -parallelizeTargets -resultBundlePath ../build-ios/result -derivedDataPath ../build-ios/ CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO' sh 'cd ../build-ios/ && mkdir -p Payload && cp -R Build/Products/Release-iphoneos/Mattermost.app Payload/ && zip -r Mattermost-unsigned.ipa Payload/' sh 'mv ../build-ios/Mattermost-unsigned.ipa ../' @@ -352,20 +354,17 @@ platform :ios do ENV['APP_NAME'] = 'Mattermost' ENV['REPLACE_ASSETS'] = 'true' ENV['BUILD_FOR_RELEASE'] = 'true' - ENV['APP_SCHEME'] = 'mattermost-mobile' + ENV['APP_SCHEME'] = 'mattermost' + data_path = 'build-ios' update_identifiers replace_assets + sh 'rm -rf ../build-ios/' sh 'cd ../ios && xcodebuild -workspace Mattermost.xcworkspace -scheme Mattermost -arch x86_64 -sdk iphonesimulator -configuration Release -parallelizeTargets -derivedDataPath ../build-ios ENABLE_BITCODE=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ENABLE_BITCODE=NO' - - data_path = 'ios' - if ENV['CIRCLECI'] == 'true' - data_path = 'build-ios' - end - sh "cd ../#{data_path}/Build/Products/Release-iphonesimulator && zip -r Mattermost-simulator-x86_64.app.zip Mattermost.app" sh "mv ../#{data_path}/Build/Products/Release-iphonesimulator/Mattermost-simulator-x86_64.app.zip ../" + sh 'rm -rf ../build-ios/' upload_file_to_s3({ :os_type => "iOS", @@ -457,16 +456,12 @@ platform :ios do app_group_identifiers: [app_group_id] ) - find_replace_string( - path_to_file: './ios/UploadAttachments/UploadAttachments/MMMConstants.m', - old_string: 'group.com.mattermost.rnbeta', - new_string: app_group_id - ) - - find_replace_string( - path_to_file: './node_modules/react-native-mmkv-storage/ios/MMKVStorage.m', - old_string: 'group.com.mattermost.rnbeta', - new_string: app_group_id + update_info_plist( + xcodeproj: './ios/Mattermost.xcodeproj', + plist_path: 'Mattermost/Info.plist', + block: proc do |plist| + plist["AppGroupIdentifier"] = app_group_id + end ) update_app_group_identifiers( @@ -474,11 +469,27 @@ platform :ios do app_group_identifiers: [app_group_id] ) + update_info_plist( + xcodeproj: './ios/Mattermost.xcodeproj', + plist_path: 'MattermostShare/Info.plist', + block: proc do |plist| + plist["AppGroupIdentifier"] = app_group_id + end + ) + update_app_group_identifiers( entitlements_file: './ios/NotificationService/NotificationService.entitlements', app_group_identifiers: [app_group_id] ) + update_info_plist( + xcodeproj: './ios/Mattermost.xcodeproj', + plist_path: 'NotificationService/Info.plist', + block: proc do |plist| + plist["AppGroupIdentifier"] = app_group_id + end + ) + # Sync the provisioning profiles using match if ENV['SYNC_PROVISIONING_PROFILES'] == 'true' && !ENV['MATCH_PASSWORD'].nil? && !ENV['MATCH_PASSWORD'].empty? match(type: ENV['MATCH_TYPE'] || 'adhoc') @@ -590,7 +601,7 @@ platform :android do ENV['APP_NAME'] = 'Mattermost' ENV['REPLACE_ASSETS'] = 'true' ENV['BUILD_FOR_RELEASE'] = 'true' - ENV['APP_SCHEME'] = 'mattermost-mobile' + ENV['APP_SCHEME'] = 'mattermost' update_identifiers replace_assets @@ -727,20 +738,37 @@ platform :android do gradle( task: 'app:assemble', build_type: config_mode, - project_dir: 'android/' + project_dir: 'android/', + properties: { + 'separateApk' => ENV["SEPARATE_APKS"] || false, + 'universalApk' => ENV["SEPARATE_APKS"] || false, + } ) end def move_apk_to_root app_name = ENV['APP_NAME'] || 'Mattermost Beta' app_name_sub = app_name.gsub(" ", "_") - new_apk_path = "#{Pathname.new(File.expand_path(File.dirname(__FILE__))).parent.to_s}/#{app_name_sub}.apk" - apk_path = lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH] - - unless apk_path.nil? - sh "mv #{apk_path} #{new_apk_path}" - lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH] = new_apk_path + apks = lane_context[SharedValues::GRADLE_ALL_APK_OUTPUT_PATHS] + outputJson = load_config_json(lane_context[SharedValues::GRADLE_OUTPUT_JSON_OUTPUT_PATH]) + paths = [] + apks.each do |apk| + filename = File.basename(apk) + output = outputJson.find { |o| o["path"] === filename } + unless output.nil? + filterName = output["apkData"]["filterName"] + name = "#{app_name_sub}" + unless filterName.nil? || filterName.empty? + name += "-#{filterName}" + end + new_apk_path = "#{Pathname.new(File.expand_path(File.dirname(__FILE__))).parent.to_s}/#{name}.apk" + paths.push(new_apk_path) + sh "mv #{apk} #{new_apk_path}" + UI.message("APK PATH \"#{new_apk_path}\", #{filterName}") + end end + + lane_context[SharedValues::GRADLE_ALL_APK_OUTPUT_PATHS] = paths end def link_sentry_android @@ -802,23 +830,23 @@ def send_message_to_mattermost(options) end def submit_to_testflight(ipa_path) - app_name = ENV['APP_NAME'] || 'Mattermost Beta' - app_name_sub = app_name.gsub(" ", "_") + ipa = (Dir.glob(ipa_path).select { |f| File.file? f}).first - if(File.file?(ipa_path)) - UI.success("ipa file #{ipa_path}") + if !ipa.nil? + UI.success("ipa file #{ipa}") pilot( - ipa: ipa_path + ipa: ipa ) else - UI.user_error! "ipa file does not exist #{ipa_path}" + UI.user_error! "ipa file does not exist #{ipa}" return end + filename = File.basename(ipa) version_number = get_version_number(xcodeproj: './ios/Mattermost.xcodeproj', target: 'Mattermost') build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj') s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}" - public_link = "https://#{ENV['AWS_BUCKET_NAME']}/#{s3_folder}/#{app_name_sub}.ipa" + public_link = "https://#{ENV['AWS_BUCKET_NAME']}/#{s3_folder}/#{filename}" # Send a build message to Mattermost @@ -843,34 +871,35 @@ def submit_to_testflight(ipa_path) end def submit_to_google_play(apk_path) - app_name = ENV['APP_NAME'] || 'Mattermost Beta' - app_name_sub = app_name.gsub(" ", "_") + apks = Dir.glob(apk_path).select { |f| File.file? f} + filenames = apks.map {|f| File.basename(f)} - if(File.file?(apk_path)) - UI.success("apk file #{apk_path}") + if(apks.length > 0) unless ENV['SUPPLY_JSON_KEY'].nil? || ENV['SUPPLY_JSON_KEY'].empty? supply( + package_name: ENV['MAIN_APP_IDENTIFIER'] || ENV['SUPPLY_PACKAGE_NAME'], track: ENV['SUPPLY_TRACK'] || 'alpha', - apk: apk_path + apk_paths: apks ) end else - UI.user_error! "apk file does not exist #{apk_path}" + UI.user_error! "There are no apks in #{apk_path}" return end version_number = android_get_version_name(gradle_file: './android/app/build.gradle') build_number = android_get_version_code(gradle_file: './android/app/build.gradle') s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}" - public_link = "https://#{ENV['AWS_BUCKET_NAME']}/#{s3_folder}/#{app_name_sub}.apk" + public_path = "https://#{ENV['AWS_BUCKET_NAME']}/#{s3_folder}" + links = (filenames.map { |f| "* #{public_path}/#{f}"}).join("\n") # Send a build message to Mattermost pretext = '#### New Android released ready to be published' - msg = "Release has been cut and is on the Beta track ready to be published.\nDownload link: #{public_link}" + msg = "Release has been cut and is on the Beta track ready to be published.\nDownload links:\n#{links}" if ENV['BETA_BUILD'] == 'true' pretext = '#### New Android beta published to Google Play' - msg = "Sign up as a beta tester [here](https://play.google.com/apps/testing/com.mattermost.rnbeta).\nDownload link: #{public_link}" + msg = "Sign up as a beta tester [here](https://play.google.com/apps/testing/com.mattermost.rnbeta).\nDownload links:\n#{links}" end send_message_to_mattermost({ @@ -885,21 +914,6 @@ def submit_to_google_play(apk_path) }) end -def submit_to_store - apk_path = lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH] - ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH] - - # Submit to Google Play if required - if !apk_path.nil? && ENV['SUBMIT_ANDROID_TO_GOOGLE_PLAY'] == 'true' - submit_to_google_play(apk_path) - end - - # Submit to TestFlight if required - if !ipa_path.nil? && ENV['SUBMIT_IOS_TO_TESTFLIGHT'] == 'true' - submit_to_testflight(ipa_path) - end -end - def qa_build_message(options) abbreviated_commit_hash = last_git_commit[:abbreviated_commit_hash] os_type = options[:os_type] diff --git a/fastlane/Gemfile.lock b/fastlane/Gemfile.lock index 80ad60be6..91f7344c4 100644 --- a/fastlane/Gemfile.lock +++ b/fastlane/Gemfile.lock @@ -6,22 +6,22 @@ GEM public_suffix (>= 2.0.2, < 5.0) atomos (0.1.3) aws-eventstream (1.1.0) - aws-partitions (1.366.0) - aws-sdk-core (3.105.0) + aws-partitions (1.381.0) + aws-sdk-core (3.109.1) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.37.0) - aws-sdk-core (~> 3, >= 3.99.0) + aws-sdk-kms (1.39.0) + aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.79.1) - aws-sdk-core (~> 3, >= 3.104.3) + aws-sdk-s3 (1.83.0) + aws-sdk-core (~> 3, >= 3.109.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) aws-sigv4 (1.2.2) aws-eventstream (~> 1, >= 1.0.2) - babosa (1.0.3) + babosa (1.0.4) claide (1.0.3) colored (1.2) colored2 (3.1.2) @@ -44,7 +44,7 @@ GEM faraday_middleware (1.0.0) faraday (~> 1.0) fastimage (2.2.0) - fastlane (2.158.0) + fastlane (2.162.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) aws-sdk-s3 (~> 1.0) @@ -100,14 +100,14 @@ GEM google-cloud-env (1.3.3) faraday (>= 0.17.3, < 2.0) google-cloud-errors (1.0.1) - google-cloud-storage (1.28.0) + google-cloud-storage (1.29.1) addressable (~> 2.5) digest-crc (~> 0.4) google-api-client (~> 0.33) google-cloud-core (~> 1.2) googleauth (~> 0.9) mini_mime (~> 1.0) - googleauth (0.13.1) + googleauth (0.14.0) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -165,7 +165,7 @@ GEM unf_ext (0.0.7.7) unicode-display_width (1.7.0) word_wrap (1.0.0) - xcodeproj (1.18.0) + xcodeproj (1.19.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -188,4 +188,4 @@ DEPENDENCIES nokogiri BUNDLED WITH - 2.0.2 + 2.1.4 diff --git a/Gemfile b/ios/Gemfile similarity index 50% rename from Gemfile rename to ios/Gemfile index 2a0817241..51400845a 100644 --- a/Gemfile +++ b/ios/Gemfile @@ -1,3 +1,3 @@ source "https://rubygems.org" -gem "cocoapods", "1.7.5" +gem "cocoapods", "1.10.0.rc.1" diff --git a/Gemfile.lock b/ios/Gemfile.lock similarity index 51% rename from Gemfile.lock rename to ios/Gemfile.lock index 2396d675c..68ecd199d 100644 --- a/Gemfile.lock +++ b/ios/Gemfile.lock @@ -2,23 +2,27 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (3.0.2) - activesupport (4.2.11.1) - i18n (~> 0.7) + activesupport (5.2.4.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + algoliasearch (1.27.4) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) atomos (0.1.3) claide (1.0.3) - cocoapods (1.7.5) - activesupport (>= 4.0.2, < 5) + cocoapods (1.10.0.rc.1) + addressable (~> 2.6) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.7.5) + cocoapods-core (= 1.10.0.rc.1) cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.2.2, < 2.0) + cocoapods-downloader (>= 1.4.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.3.1, < 2.0) + cocoapods-trunk (>= 1.4.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) @@ -27,50 +31,63 @@ GEM molinillo (~> 0.6.6) nap (~> 1.0) ruby-macho (~> 1.4) - xcodeproj (>= 1.10.0, < 2.0) - cocoapods-core (1.7.5) - activesupport (>= 4.0.2, < 6) + xcodeproj (>= 1.17.0, < 2.0) + cocoapods-core (1.10.0.rc.1) + activesupport (> 5.0, < 6) + addressable (~> 2.6) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) fuzzy_match (~> 2.0.4) nap (~> 1.0) + netrc (~> 0.11) + public_suffix + typhoeus (~> 1.0) cocoapods-deintegrate (1.0.4) - cocoapods-downloader (1.3.0) + cocoapods-downloader (1.4.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) - cocoapods-stats (1.1.0) - cocoapods-trunk (1.4.1) + cocoapods-trunk (1.5.0) nap (>= 0.8, < 2.0) netrc (~> 0.11) - cocoapods-try (1.1.0) + cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.1.5) + concurrent-ruby (1.1.7) escape (0.0.4) + ethon (0.12.0) + ffi (>= 1.3.0) + ffi (1.13.1) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - i18n (0.9.5) + httpclient (2.8.3) + i18n (1.8.5) concurrent-ruby (~> 1.0) - minitest (5.14.0) + json (2.3.1) + minitest (5.14.2) molinillo (0.6.6) - nanaimo (0.2.6) + nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) + public_suffix (4.0.6) ruby-macho (1.4.0) thread_safe (0.3.6) - tzinfo (1.2.6) + typhoeus (1.4.0) + ethon (>= 0.9.0) + tzinfo (1.2.7) thread_safe (~> 0.1) - xcodeproj (1.15.0) + xcodeproj (1.18.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.6) + nanaimo (~> 0.3.0) PLATFORMS ruby DEPENDENCIES - cocoapods (= 1.7.5) + cocoapods (= 1.10.0.rc.1) BUNDLED WITH - 2.0.2 + 2.1.4 diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index 355f656a5..87719d838 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -2,6 +2,10 @@ + AppGroupIdentifier + group.com.mattermost.rnbeta + BundleEntryFilename + index.js CFBundleDevelopmentRegion en CFBundleDisplayName diff --git a/ios/Mattermost/MattermostManaged.m b/ios/Mattermost/MattermostManaged.m index d8124a1ee..263365a92 100644 --- a/ios/Mattermost/MattermostManaged.m +++ b/ios/Mattermost/MattermostManaged.m @@ -65,11 +65,16 @@ RCT_EXPORT_MODULE(); return safeAreaInsets; } -- (NSDictionary *)constantsToExport { +-(NSString *)appGroupId { + NSBundle *bundle = [NSBundle mainBundle]; + NSString *appGroupId = [bundle objectForInfoDictionaryKey:@"AppGroupIdentifier"]; + return appGroupId; +} +- (NSDictionary *)constantsToExport { return @{ @"hasSafeAreaInsets": @([self hasSafeAreaInsets]), - @"appGroupIdentifier": APP_GROUP_ID + @"appGroupIdentifier": [self appGroupId] }; } @@ -87,7 +92,7 @@ RCT_EXPORT_MODULE(); - (instancetype)init { self = [super init]; if (self) { - _sharedUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:APP_GROUP_ID]; + _sharedUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:[self appGroupId]]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(managedConfigDidChange:) name:@"managedConfigDidChange" object:nil]; [[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification diff --git a/ios/MattermostShare/Config.swift b/ios/MattermostShare/Config.swift index f23253560..12833d787 100644 --- a/ios/MattermostShare/Config.swift +++ b/ios/MattermostShare/Config.swift @@ -1,5 +1,6 @@ let configurationKey = "com.apple.configuration.managed" func getManagedConfig() -> [String : Any] { - return UserDefaults.init(suiteName: APP_GROUP_ID)?.dictionary(forKey: configurationKey) ?? [:] + let appGroupId = Bundle.main.infoDictionary!["AppGroupIdentifier"] as! String + return UserDefaults.init(suiteName: appGroupId)?.dictionary(forKey: configurationKey) ?? [:] } diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index c7bc7688d..7522250b2 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -2,6 +2,8 @@ + AppGroupIdentifier + group.com.mattermost.rnbeta CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist index 40a5a790e..63f513b07 100644 --- a/ios/NotificationService/Info.plist +++ b/ios/NotificationService/Info.plist @@ -2,6 +2,8 @@ + AppGroupIdentifier + group.com.mattermost.rnbeta CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d359b8875..086dc02c2 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -441,7 +441,7 @@ DEPENDENCIES: - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: - https://github.com/cocoapods/specs.git: + https://github.com/CocoaPods/Specs.git: - boost-for-react-native - libwebp - MMKV @@ -669,4 +669,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 1aabd16b6a9ed2037b4d7443027d222817790468 -COCOAPODS: 1.7.5 +COCOAPODS: 1.10.0.rc.1 diff --git a/ios/UploadAttachments/UploadAttachments/MMMConstants.h b/ios/UploadAttachments/UploadAttachments/MMMConstants.h index f0ff53c60..49f129a38 100644 --- a/ios/UploadAttachments/UploadAttachments/MMMConstants.h +++ b/ios/UploadAttachments/UploadAttachments/MMMConstants.h @@ -1,7 +1,6 @@ #import @interface MMMConstants : NSObject -extern NSString *APP_GROUP_ID; extern UInt64 DEFAULT_SERVER_MAX_IMAGE_PIXELS; extern UInt64 DEFAULT_SERVER_MAX_FILE_SIZE; extern UInt64 DEFAULT_SERVER_MAX_POST_SIZE; diff --git a/ios/UploadAttachments/UploadAttachments/MMMConstants.m b/ios/UploadAttachments/UploadAttachments/MMMConstants.m index bf5144c0c..bc189b0a4 100644 --- a/ios/UploadAttachments/UploadAttachments/MMMConstants.m +++ b/ios/UploadAttachments/UploadAttachments/MMMConstants.m @@ -1,7 +1,6 @@ #import "MMMConstants.h" @implementation MMMConstants -NSString *APP_GROUP_ID = @"group.com.mattermost.rnbeta"; UInt64 DEFAULT_SERVER_MAX_IMAGE_PIXELS = 6048 * 4032; UInt64 DEFAULT_SERVER_MAX_FILE_SIZE = 50 * 1024 * 1024; UInt64 DEFAULT_SERVER_MAX_POST_SIZE = 4000; diff --git a/ios/UploadAttachments/UploadAttachments/MattermostBucket.m b/ios/UploadAttachments/UploadAttachments/MattermostBucket.m index 8be32f6a3..4e3568383 100644 --- a/ios/UploadAttachments/UploadAttachments/MattermostBucket.m +++ b/ios/UploadAttachments/UploadAttachments/MattermostBucket.m @@ -7,13 +7,19 @@ return [[NSUserDefaults alloc] initWithSuiteName: name]; } +-(NSString *)appGroupId { + NSBundle *bundle = [NSBundle mainBundle]; + NSString *appGroupId = [bundle objectForInfoDictionaryKey:@"AppGroupIdentifier"]; + return appGroupId; +} + -(NSString *)fileUrl:(NSString *)fileName { - NSURL *fileManagerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:APP_GROUP_ID]; + NSURL *fileManagerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:[self appGroupId]]; return [NSString stringWithFormat:@"%@/%@", fileManagerURL.path, fileName]; } -(id) getPreference:(NSString *)key { - NSUserDefaults* bucket = [self bucketByName: APP_GROUP_ID]; + NSUserDefaults* bucket = [self bucketByName: [self appGroupId]]; return [bucket objectForKey:key]; } @@ -50,12 +56,12 @@ } -(void) removePreference:(NSString *)key { - NSUserDefaults* bucket = [self bucketByName: APP_GROUP_ID]; + NSUserDefaults* bucket = [self bucketByName: [self appGroupId]]; [bucket removeObjectForKey: key]; } -(void) setPreference:(NSString *)key value:(NSString *) value { - NSUserDefaults* bucket = [self bucketByName: APP_GROUP_ID]; + NSUserDefaults* bucket = [self bucketByName: [self appGroupId]]; if (bucket && [key length] > 0 && [value length] > 0) { [bucket setObject:value forKey:key]; } diff --git a/ios/UploadAttachments/UploadAttachments/StoreManager.m b/ios/UploadAttachments/UploadAttachments/StoreManager.m index ba2a29e1e..5292ff3ba 100644 --- a/ios/UploadAttachments/UploadAttachments/StoreManager.m +++ b/ios/UploadAttachments/UploadAttachments/StoreManager.m @@ -155,8 +155,10 @@ } -(NSString *)getToken { + NSBundle *bundle = [NSBundle mainBundle]; + NSString *appGroupId = [bundle objectForInfoDictionaryKey:@"AppGroupIdentifier"]; NSDictionary *options = @{ - @"accessGroup": APP_GROUP_ID + @"accessGroup": appGroupId }; NSString* serverUrl = [self getServerUrl]; diff --git a/ios/UploadAttachments/UploadAttachments/UploadSession.swift b/ios/UploadAttachments/UploadAttachments/UploadSession.swift index 6236c54a7..bed189b52 100644 --- a/ios/UploadAttachments/UploadAttachments/UploadSession.swift +++ b/ios/UploadAttachments/UploadAttachments/UploadSession.swift @@ -55,7 +55,8 @@ import os.log os_log(OSLogType.default, "Mattermost Attached session with completionHandler identifier=%{public}@", identifier) } let sessionConfig = URLSessionConfiguration.background(withIdentifier: identifier) - sessionConfig.sharedContainerIdentifier = APP_GROUP_ID + let appGroupId = Bundle.main.infoDictionary!["AppGroupIdentifier"] as! String + sessionConfig.sharedContainerIdentifier = appGroupId if #available(iOS 11.0, *) { sessionConfig.waitsForConnectivity = true } @@ -65,7 +66,8 @@ import os.log public func createURLSession(identifier: String) -> URLSession { let sessionConfig = URLSessionConfiguration.background(withIdentifier: identifier) - sessionConfig.sharedContainerIdentifier = APP_GROUP_ID + let appGroupId = Bundle.main.infoDictionary!["AppGroupIdentifier"] as! String + sessionConfig.sharedContainerIdentifier = appGroupId if #available(iOS 11.0, *) { sessionConfig.waitsForConnectivity = true } diff --git a/ios/UploadAttachments/UploadAttachments/UploadSessionManager.swift b/ios/UploadAttachments/UploadAttachments/UploadSessionManager.swift index 5406862a1..e28533a82 100644 --- a/ios/UploadAttachments/UploadAttachments/UploadSessionManager.swift +++ b/ios/UploadAttachments/UploadAttachments/UploadSessionManager.swift @@ -8,7 +8,7 @@ import Foundation } @objc @objcMembers public class UploadSessionManager: NSObject { - private let bucket = MattermostBucket().bucket(byName: APP_GROUP_ID) + private let bucket = MattermostBucket().bucket(byName: Bundle.main.infoDictionary!["AppGroupIdentifier"] as! String) public class var shared :UploadSessionManager { struct Singleton { @@ -60,7 +60,8 @@ import Foundation public func tempContainerURL() -> URL? { let filemgr = FileManager.default - let containerURL = filemgr.containerURL(forSecurityApplicationGroupIdentifier: APP_GROUP_ID) + let appGroupId = Bundle.main.infoDictionary!["AppGroupIdentifier"] as! String + let containerURL = filemgr.containerURL(forSecurityApplicationGroupIdentifier: appGroupId) guard let tempDirectoryURL = containerURL?.appendingPathComponent("shareTempItems") else {return nil} var isDirectory = ObjCBool(false) let exists = filemgr.fileExists(atPath: tempDirectoryURL.path, isDirectory: &isDirectory) diff --git a/ios/bundleReactNative.sh b/ios/bundleReactNative.sh index 1b66f787e..15a76640d 100755 --- a/ios/bundleReactNative.sh +++ b/ios/bundleReactNative.sh @@ -2,8 +2,6 @@ export NODE_OPTIONS=--max_old_space_size=12000 export BUNDLE_COMMAND="ram-bundle" -export NODE_BINARY=node - if [[ "${SENTRY_ENABLED}" = "true" ]]; then echo "Sentry native integration is enabled" diff --git a/ios/react-native-xcode.sh b/ios/react-native-xcode.sh index 7b56dcd7e..82b978ec2 100755 --- a/ios/react-native-xcode.sh +++ b/ios/react-native-xcode.sh @@ -67,9 +67,6 @@ esac PROJECT_ROOT=${PROJECT_ROOT:-$PWD} cd "$PROJECT_ROOT/.." || exit -# Define NVM_DIR and source the nvm.sh setup script -[ -z "$NVM_DIR" ] && export NVM_DIR="$HOME/.nvm" - # Define entry file if [[ "$ENTRY_FILE" ]]; then # Use ENTRY_FILE defined by user @@ -80,27 +77,6 @@ elif [[ -s "index.ios.js" ]]; then ENTRY_FILE=${1:-index.js} fi -if [[ -s "$HOME/.nvm/nvm.sh" ]]; then - . "$HOME/.nvm/nvm.sh" -elif [[ -x "$(command -v brew)" && -s "$(brew --prefix nvm)/nvm.sh" ]]; then - . "$(brew --prefix nvm)/nvm.sh" -fi - -# Set up the nodenv node version manager if present -if [[ -x "$HOME/.nodenv/bin/nodenv" ]]; then - eval "$("$HOME/.nodenv/bin/nodenv" init -)" -elif [[ -x "$(command -v brew)" && -x "$(brew --prefix nodenv)/bin/nodenv" ]]; then - eval "$("$(brew --prefix nodenv)/bin/nodenv" init -)" -fi - -# Set up the ndenv of anyenv if preset -if [[ ! -x node && -d ${HOME}/.anyenv/bin ]]; then - export PATH=${HOME}/.anyenv/bin:${PATH} - if [[ "$(anyenv envs | grep -c ndenv )" -eq 1 ]]; then - eval "$(anyenv init -)" - fi -fi - # Path to react-native folder inside node_modules REACT_NATIVE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/node_modules/react-native" && pwd)" # check and assign NODE_BINARY env diff --git a/package-lock.json b/package-lock.json index 875b57317..5996dd705 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6065,15 +6065,6 @@ "@types/react-native": "*" } }, - "@types/react-test-renderer": { - "version": "16.9.3", - "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.9.3.tgz", - "integrity": "sha512-wJ7IlN5NI82XMLOyHSa+cNN4Z0I+8/YaLl04uDgcZ+W+ExWCmCiVTLT/7fRNqzy4OhStZcUwIqLNF7q+AdW43Q==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, "@types/shallow-equals": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/shallow-equals/-/shallow-equals-1.0.0.tgz", diff --git a/package.json b/package.json index 9695919e0..376c81f06 100644 --- a/package.json +++ b/package.json @@ -151,19 +151,30 @@ } }, "scripts": { - "start": "node ./node_modules/react-native/local-cli/cli.js start", - "check": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet . && npm run tsc", - "fix": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet . --fix", - "postinstall": "make post-install", + "android": "react-native run-android", + "ios": "react-native run-ios", + "start": "react-native start", + "check": "npm run lint && npm run tsc", + "clean": "./scripts/clean.sh", + "ios-gems": "cd ios && bundle install", + "fix": "npm run lint -- --fix", + "lint": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet .", + "pod-install": "cd ios && bundle exec pod install", + "postinstall": "patch-package && ./scripts/postinstall.sh", "tsc": "NODE_OPTIONS=--max_old_space_size=12000 tsc --noEmit", "test": "jest --forceExit --runInBand --detectOpenHandles", - "test:watch": "jest --watch --runInBand --detectOpenHandles", + "test:watch": "npm test -- --watch", "test:coverage": "jest --coverage", "updatesnapshot": "jest --updateSnapshot", "mmjstool": "mmjstool", "i18n-extract": "npm run mmjstool -- i18n extract-mobile", "i18n-clean-empty": "npm run mmjstool -- i18n clean-empty --mobile-dir .", "e2e:android": "cd detox && npm run e2e:android-build && npm run e2e:android-test && cd ..", - "e2e:ios": "cd detox && npm run e2e:ios-test && cd .." + "e2e:ios": "cd detox && npm run e2e:ios-test && cd ..", + "build:ios": "./scripts/build.sh ipa", + "build:ios-unsigned": "./scripts/build.sh ipa unsigned", + "build:ios-sim": "./scripts/build.sh ipa simulator", + "build:android": "./scripts/build.sh apk", + "build:android-unsigned": "./scripts/build.sh apk unsigned" } } diff --git a/patches/react-native-elements+2.3.0.patch b/patches/react-native-elements+2.3.2.patch similarity index 100% rename from patches/react-native-elements+2.3.0.patch rename to patches/react-native-elements+2.3.2.patch diff --git a/patches/react-native-mmkv-storage+0.3.7.patch b/patches/react-native-mmkv-storage+0.3.7.patch index 17cddf0e2..6f72f2fde 100644 --- a/patches/react-native-mmkv-storage+0.3.7.patch +++ b/patches/react-native-mmkv-storage+0.3.7.patch @@ -1,13 +1,14 @@ diff --git a/node_modules/react-native-mmkv-storage/ios/MMKVStorage.m b/node_modules/react-native-mmkv-storage/ios/MMKVStorage.m -index 34c764c..34fd83d 100644 +index fe0ee2b..5bdde1d 100644 --- a/node_modules/react-native-mmkv-storage/ios/MMKVStorage.m +++ b/node_modules/react-native-mmkv-storage/ios/MMKVStorage.m -@@ -37,7 +37,9 @@ - (id)init +@@ -45,7 +45,10 @@ - (id)init { self = [super init]; if (self) { - [MMKV initialize]; -+ NSString *APP_GROUP_ID = @"group.com.mattermost.rnbeta"; ++ NSBundle *bundle = [NSBundle mainBundle]; ++ NSString *APP_GROUP_ID = [bundle objectForInfoDictionaryKey:@"AppGroupIdentifier"]; + NSString *groupDir = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:APP_GROUP_ID].path; + [MMKV initializeMMKV:nil groupDir:groupDir logLevel:MMKVLogInfo]; secureStorage = [[SecureStorage alloc]init]; diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 000000000..f65bcb25c --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,97 @@ +#!/bin/sh + +function execute() { + cd fastlane && NODE_ENV=production bundle exec fastlane $1 $2 +} + +function apk() { + case $1 in + unsigned) + echo "Building Android unsigned app" + setup android + execute android unsigned + ;; + *) + echo "Building Android app" + setup android + execute android build + esac +} + +function ipa() { + case $1 in + unsigned) + echo "Building iOS unsigned app" + setup ios + execute ios unsigned + ;; + simulator) + echo "Building unsigned x86_64 iOS app for iPhone simulator" + setup ios + execute ios simulator + ;; + *) + echo "Building iOS app" + setup ios + execute ios build + esac +} + +function setup() { + if [[ -z "$SKIP_SETUP" ]]; then + npm run clean || exit 1 + npm install --ignore-scripts || exit 1 + npx patch-package || exit 1 + + if [[ "$1" == "ios"* ]]; then + echo "Installing Gems" + npm run ios-gems &> /dev/null || exit 1 + echo "Getting Cocoapods dependencies" + npm run pod-install || exit 1 + fi + + ASSETS=$(node scripts/generate-assets.js) + if [ -z "$ASSETS" ]; then + echo "Error Generating app assets" + exit 1 + else + echo "Generating app assets" + fi + + echo "Installing Fastane" + if !gem list bundler -i --version 2.1.4 > /dev/null 2>&1; then + gem install bundler --versio 2.1.4 + fi + cd fastlane && bundle install && cd .. || exit 1 + fi + + if [ "$1" = "android" ]; then + ./node_modules/.bin/jetify + fi +} + +case $1 in + apk) + apk $2 + ;; + ipa) + if [[ "$OSTYPE" == "darwin"* ]]; then + ipa $2 + else + echo "You need a MacOS to build the iOS mobile app" + exit 1 + fi + ;; + *) + echo "Build the mobile app for Android or iOS + Usage: build.sh [options] + + Type: + apk Builds Android APK(s) + ipa Builds iOS IPA + + Options: + apk: unsigned + ipa: unsigned or simulator" + ;; +esac \ No newline at end of file diff --git a/scripts/clean.sh b/scripts/clean.sh new file mode 100755 index 000000000..782ec8dbe --- /dev/null +++ b/scripts/clean.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +echo Cleaning started + +rm -rf ios/Pods +rm -rf node_modules +rm -rf dist +rm -rf ios/build +rm -rf android/app/build + +echo Cleanup finished \ No newline at end of file diff --git a/scripts/make-dist-assets.js b/scripts/generate-assets.js similarity index 85% rename from scripts/make-dist-assets.js rename to scripts/generate-assets.js index d9f82ac58..f8ae746cd 100644 --- a/scripts/make-dist-assets.js +++ b/scripts/generate-assets.js @@ -4,6 +4,7 @@ /* eslint-disable no-console */ const fs = require('fs'); +const fsPath = require('path'); // Takes the files in rootA/path, overwrites or merges them with the corresponding file in rootB/path, and places the // resulting file in dest/path. JSON files that exist in both places are shallowly (TODO maybe deeply) merged and all @@ -13,7 +14,7 @@ function leftMergeDirs(rootA, rootB, dest, path) { const pathB = rootB + path; try { - fs.mkdirSync(dest + path); + fs.mkdirSync(dest + path, {recursive: true}); } catch (e) { if (e.code !== 'EEXIST') { console.error('Failed to create destination dir ' + dest + path); @@ -83,6 +84,25 @@ function leftMergeDirs(rootA, rootB, dest, path) { } } +const rmdir = (path) => { + const list = fs.readdirSync(path); + for (let i = 0; i < list.length; i++) { + const filename = fsPath.join(path, list[i]); + const stat = fs.statSync(filename); + + if (stat.isDirectory()) { + rmdir(filename); + } else { + fs.unlinkSync(filename); + } + } + fs.rmdirSync(path); +}; + +if (fs.existsSync('dist')) { + rmdir('dist'); +} + // Assumes dist/assets exists and is empty leftMergeDirs('assets/base/', 'assets/override/', 'dist/assets/', ''); /* eslint-enable no-console */ diff --git a/scripts/postinstall.sh b/scripts/postinstall.sh new file mode 100755 index 000000000..b4667681d --- /dev/null +++ b/scripts/postinstall.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +if [[ "$OSTYPE" == "darwin"* ]]; then + if !gem list bundler -i --version 2.1.4 > /dev/null 2>&1; then + gem install bundler --version 2.1.4 + fi + echo "Installing Gems" + npm run ios-gems &> /dev/null + echo "Getting Cocoapods dependencies" + npm run pod-install &> /dev/null +fi + +ASSETS=$(node scripts/generate-assets.js) +if [ -z "$ASSETS" ]; then + echo "Error Generating app assets" + exit 1 +else + echo "Generating app assets" +fi \ No newline at end of file diff --git a/test/setup.js b/test/setup.js index b718063a3..9462fa5d7 100644 --- a/test/setup.js +++ b/test/setup.js @@ -1,6 +1,8 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +/* eslint-disable react/no-multi-comp */ + import * as ReactNative from 'react-native'; import MockAsyncStorage from 'mock-async-storage'; import {configure} from 'enzyme'; @@ -110,8 +112,31 @@ jest.doMock('react-native', () => { }, ReactNative); }); +jest.mock('react-native-vector-icons/MaterialIcons', () => ({ + getImageSource: jest.fn().mockResolvedValue({}), +})); jest.mock('react-native-vector-icons/MaterialCommunityIcons'); jest.mock('react-native-vector-icons/FontAwesome5'); +jest.mock('react-native-vector-icons', () => { + const React = jest.requireActual('react'); + const PropTypes = jest.requireActual('prop-types'); + class CompassIcon extends React.PureComponent { + render() { + return React.createElement('Icon', this.props); + } + } + CompassIcon.propTypes = { + name: PropTypes.string, + size: PropTypes.number, + style: PropTypes.oneOfType([PropTypes.array, PropTypes.number, PropTypes.object]), + }; + CompassIcon.getImageSource = jest.fn().mockResolvedValue({}); + return { + createIconSet: () => CompassIcon, + + createIconSetFromFontello: () => CompassIcon, + }; +}); jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper'); jest.mock('../node_modules/react-native/Libraries/EventEmitter/NativeEventEmitter');