diff --git a/README.md b/README.md index 5d54b5819..0ccdfb167 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ We use [fastlane](https://docs.fastlane.tools/) to help automate parts of the de ### Usage -The current expectation is that you we tag to freeze the code, bump the version, and describe the changes represented by the tag. Then we release to make builds and publish on Github. Later, presumably when some of the change logs have been translated, we push builds for internal testing. If that looks ok, we push to public testing, and later to production release. +The current expectation is that you we tag to freeze the code, bump the internal build number, and describe the changes represented by the tag. Then we release to make builds and publish on Github. Later, presumably when some of the change logs have been translated, we push builds for internal testing. If that looks ok, we push to public testing, and later to production release. ```zsh # Make a git tag. This will bump the build number and prompt you to describe diff --git a/env.example b/env.example index 505e63233..1f289ccb5 100644 --- a/env.example +++ b/env.example @@ -17,7 +17,11 @@ E2E_TEST_PASSWORD=test-password GMAPS_API_KEY=some-key -# Model file names for Android and iOS +# Model file names for Android and iOS. See the README for instructions on +# where to put these files. Note that in iOS, we have an additional build +# phase in xcode to hard link these files using file names xcode knows about, +# so we don't have to add these files to xcode in addition to declaring them +# here. ANDROID_MODEL_FILE_NAME=small_inception_tf1.tflite ANDROID_TAXONOMY_FILE_NAME=small_export_tax.csv IOS_MODEL_FILE_NAME=small_inception_tf1.mlmodel diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 1d71a7216..9b9e403af 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -14,9 +14,9 @@ PODS: - ReactCommon/turbomodule/core (= 0.71.12) - fmt (6.2.1) - glog (0.3.5) - - hermes-engine (0.71.12): - - hermes-engine/Pre-built (= 0.71.12) - - hermes-engine/Pre-built (0.71.12) + - hermes-engine (0.71.8): + - hermes-engine/Pre-built (= 0.71.8) + - hermes-engine/Pre-built (0.71.8) - libevent (2.1.12) - Permission-LocationWhenInUse (3.8.0): - RNPermissions @@ -706,7 +706,7 @@ SPEC CHECKSUMS: FBReactNativeSpec: b24809f97ae83c786928d56b732957195b4fa390 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b - hermes-engine: b60ebc812e0179a612d8146ac54730d533c804a2 + hermes-engine: 47986d26692ae75ee7a17ab049caee8864f855de libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 Permission-LocationWhenInUse: 1c80e4797ae587642a83125e80b86686cefeba53 Permission-PhotoLibrary: 31787bbe77d0d3ae6a5267b8435e4a2e9ef78f1d diff --git a/ios/iNaturalistReactNative.xcodeproj/project.pbxproj b/ios/iNaturalistReactNative.xcodeproj/project.pbxproj index a02c2139c..a593ca1c5 100644 --- a/ios/iNaturalistReactNative.xcodeproj/project.pbxproj +++ b/ios/iNaturalistReactNative.xcodeproj/project.pbxproj @@ -11,8 +11,9 @@ 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 19FA77402A787B8A00982F02 /* taxonomy.json in Resources */ = {isa = PBXBuildFile; fileRef = 19FA773E2A787B8A00982F02 /* taxonomy.json */; }; - 19FA77412A787B8A00982F02 /* cvmodel.mlmodel in Sources */ = {isa = PBXBuildFile; fileRef = 19FA773F2A787B8A00982F02 /* cvmodel.mlmodel */; }; + 1956B4912A7C3C1E00BBBDE4 /* link-inat-model-files.sh in Resources */ = {isa = PBXBuildFile; fileRef = 1956B4902A7C3C1E00BBBDE4 /* link-inat-model-files.sh */; }; + 197A169D2A7C2567001A03DC /* cvmodel.mlmodel in Sources */ = {isa = PBXBuildFile; fileRef = 197A169B2A7C2567001A03DC /* cvmodel.mlmodel */; }; + 197A169E2A7C2567001A03DC /* taxonomy.json in Resources */ = {isa = PBXBuildFile; fileRef = 197A169C2A7C2567001A03DC /* taxonomy.json */; }; 374CB22F29943E63005885ED /* Whitney-BookItalic-Pro.otf in Resources */ = {isa = PBXBuildFile; fileRef = 374CB22E29943E63005885ED /* Whitney-BookItalic-Pro.otf */; }; 4FB3B444D46A4115B867B9CC /* inaturalisticons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = EE004FD2EC174086A7AB2908 /* inaturalisticons.ttf */; }; 763319F15FC44DBF89C9DEB0 /* INatIcon.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A96A8C1FA45F4C8692AAE36F /* INatIcon.ttf */; }; @@ -70,9 +71,10 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = iNaturalistReactNative/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = iNaturalistReactNative/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = iNaturalistReactNative/main.m; sourceTree = ""; }; + 1956B4902A7C3C1E00BBBDE4 /* link-inat-model-files.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "link-inat-model-files.sh"; sourceTree = ""; }; + 197A169B2A7C2567001A03DC /* cvmodel.mlmodel */ = {isa = PBXFileReference; lastKnownFileType = file.mlmodel; path = cvmodel.mlmodel; sourceTree = ""; }; + 197A169C2A7C2567001A03DC /* taxonomy.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = taxonomy.json; sourceTree = ""; }; 19A5877328F8E3310016D128 /* iNaturalistReactNative-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "iNaturalistReactNative-Bridging-Header.h"; sourceTree = ""; }; - 19FA773E2A787B8A00982F02 /* taxonomy.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = taxonomy.json; sourceTree = ""; }; - 19FA773F2A787B8A00982F02 /* cvmodel.mlmodel */ = {isa = PBXFileReference; lastKnownFileType = file.mlmodel; path = cvmodel.mlmodel; sourceTree = ""; }; 374CB22E29943E63005885ED /* Whitney-BookItalic-Pro.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "Whitney-BookItalic-Pro.otf"; path = "../assets/fonts/Whitney-BookItalic-Pro.otf"; sourceTree = ""; }; 52305C33CFC262F17ADC692E /* Pods-iNaturalistReactNative.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iNaturalistReactNative.release.xcconfig"; path = "Target Support Files/Pods-iNaturalistReactNative/Pods-iNaturalistReactNative.release.xcconfig"; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = iNaturalistReactNative/LaunchScreen.storyboard; sourceTree = ""; }; @@ -143,8 +145,9 @@ 13B07FAE1A68108700A75B9A /* iNaturalistReactNative */ = { isa = PBXGroup; children = ( - 19FA773F2A787B8A00982F02 /* cvmodel.mlmodel */, - 19FA773E2A787B8A00982F02 /* taxonomy.json */, + 1956B4902A7C3C1E00BBBDE4 /* link-inat-model-files.sh */, + 197A169B2A7C2567001A03DC /* cvmodel.mlmodel */, + 197A169C2A7C2567001A03DC /* taxonomy.json */, 8FE03BBA2A5EFCB2001B35BA /* small_export_tax.json */, 8FE03BB92A5EFCB2001B35BA /* small_inception_tf1.mlmodel */, 8B8BAD0429F54EB300CE5C9F /* iNaturalistReactNative.entitlements */, @@ -264,6 +267,7 @@ buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "iNaturalistReactNative" */; buildPhases = ( F2ED1E9C8F0D0CEE2EFC8B3D /* [CP] Check Pods Manifest.lock */, + 197A169F2A7C2D90001A03DC /* Link iNat model files */, FD10A7F022414F080027D42C /* Start Packager */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, @@ -357,9 +361,10 @@ buildActionMask = 2147483647; files = ( 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, - 19FA77402A787B8A00982F02 /* taxonomy.json in Resources */, + 197A169E2A7C2567001A03DC /* taxonomy.json in Resources */, 8FE03BBC2A5EFCB2001B35BA /* small_export_tax.json in Resources */, 374CB22F29943E63005885ED /* Whitney-BookItalic-Pro.otf in Resources */, + 1956B4912A7C3C1E00BBBDE4 /* link-inat-model-files.sh in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, A252B2AEA64E47C9AC1D20E8 /* Whitney-Light-Pro.otf in Resources */, BA2479FA3D7B40A7BEF7B3CD /* Whitney-Medium-Pro.otf in Resources */, @@ -395,6 +400,24 @@ shellPath = /bin/sh; shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; + 197A169F2A7C2D90001A03DC /* Link iNat model files */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Link iNat model files"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n$SRCROOT/link-inat-model-files.sh\n"; + }; 49042D6B9285839976E1E986 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -525,7 +548,7 @@ buildActionMask = 2147483647; files = ( 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */, - 19FA77412A787B8A00982F02 /* cvmodel.mlmodel in Sources */, + 197A169D2A7C2567001A03DC /* cvmodel.mlmodel in Sources */, 8FE03BBB2A5EFCB2001B35BA /* small_inception_tf1.mlmodel in Sources */, 13B07FC11A68108700A75B9A /* main.m in Sources */, ); diff --git a/ios/link-inat-model-files.sh b/ios/link-inat-model-files.sh new file mode 100755 index 000000000..23dcb85d5 --- /dev/null +++ b/ios/link-inat-model-files.sh @@ -0,0 +1,23 @@ +# Hard links the model and taxonomy files specified in .env or .env.staging to the +# files declared in the xcode project +# https://developer.apple.com/documentation/xcode/running-custom-scripts-during-a-build +if [ $CONFIGURATION = "Debug" ]; then + source $SRCROOT/../.env.staging +else + source $SRCROOT/../.env +fi + +if ! [ -f $SRCROOT/$IOS_MODEL_FILE_NAME ]; then + echo "CV model file does not exist at $SRCROOT/$IOS_MODEL_FILE_NAME" + exit 1 +fi + +if ! [ -f $SRCROOT/$IOS_TAXONOMY_FILE_NAME ]; then + echo "CV taxonomy file does not exist at $SRCROOT/$IOS_TAXONOMY_FILE_NAME" + exit 1 +fi + +echo "Linking $IOS_MODEL_FILE_NAME to cvmodel.mlmodel..." +ln -f $SRCROOT/$IOS_MODEL_FILE_NAME $SRCROOT/cvmodel.mlmodel +echo "Linking $IOS_TAXONOMY_FILE_NAME to taxonomy.json..." +ln -f $SRCROOT/$IOS_TAXONOMY_FILE_NAME $SRCROOT/taxonomy.json diff --git a/src/sharedHelpers/cvModel.js b/src/sharedHelpers/cvModel.js index 2a4fb1b4f..e216227c1 100644 --- a/src/sharedHelpers/cvModel.js +++ b/src/sharedHelpers/cvModel.js @@ -9,8 +9,13 @@ import { log } from "../../react-native-logs.config"; const logger = log.extend( "cvModel" ); const modelFiles = { - IOSMODEL: `${Config.IOS_MODEL_FILE_NAME}c`, - IOSTAXONOMY: Config.IOS_TAXONOMY_FILE_NAME, + // The iOS model and taxonomy files always have to be referenced in the + // xcode project. To avoid constantly having to add new files every time we + // change the model, we are keeping the files referenced in the xcode + // project the same but linking them to the files specified in .env in a + // build phase script. See ios/link-inat-model-files.sh + IOSMODEL: "cvmodel.mlmodelc", + IOSTAXONOMY: "taxonomy.json", ANDROIDMODEL: Config.ANDROID_MODEL_FILE_NAME, ANDROIDTAXONOMY: Config.ANDROID_TAXONOMY_FILE_NAME }; @@ -61,17 +66,20 @@ const addCameraFilesAndroid = () => { }; const addCameraFilesiOS = () => { - const copyFilesiOS = ( source, destination ) => { - RNFS.copyFile( source, destination ) - .then( () => { - console.log( `moved file from ${source} to ${destination}` ); - } ) - .catch( error => { - console.log( - error, - `error moving file from ${source} to ${destination}` - ); - } ); + const copyFilesiOS = async ( source, destination ) => { + try { + await RNFS.unlink( destination ); + } catch ( unlinkError ) { + console.error( "Error deleting file at ", destination, ": ", unlinkError ); + } + try { + await RNFS.copyFile( source, destination ); + } catch ( copyError ) { + console.error( + `Error moving file from ${source} to ${destination}: `, + copyError + ); + } }; RNFS.readDir( RNFS.MainBundlePath ).then( results => {