diff --git a/.github/workflows/build-release.yaml b/.github/workflows/build-release.yaml new file mode 100644 index 0000000..4526b7e --- /dev/null +++ b/.github/workflows/build-release.yaml @@ -0,0 +1,69 @@ +name: build-release +on: [push, pull_request] +jobs: + build: + name: Build default scheme + runs-on: macos-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Install tools + run: | + brew install sunshinejr/formulae/pouch + - name: Generate Secrets.swift + env: + AIRTABLE_API_KEY: ${{ secrets.AIRTABLE_API_KEY }} + AIRTABLE_BASE_ID: ${{ secrets.AIRTABLE_BASE_ID }} + AIRTABLE_TREES_TABLE_NAME: ${{ secrets.AIRTABLE_TREES_TABLE_NAME }} + AIRTABLE_SPECIES_TABLE_NAME: ${{ secrets.AIRTABLE_SPECIES_TABLE_NAME }} + AIRTABLE_SUPERVISORS_TABLE_NAME: ${{ secrets.AIRTABLE_SUPERVISORS_TABLE_NAME }} + AIRTABLE_SITES_TABLE_NAME: ${{ secrets.AIRTABLE_SITES_TABLE_NAME }} + CLOUDINARY_CLOUD_NAME: ${{ secrets.CLOUDINARY_CLOUD_NAME }} + CLOUDINARY_UPLOAD_PRESET_NAME: ${{ secrets.CLOUDINARY_UPLOAD_PRESET_NAME }} + run: pouch + - name: Set build number + run: agvtool new-version $GITHUB_RUN_NUMBER + - name: Build and test + env: + platform: ${{ 'iOS Simulator' }} + run: | + # xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959) + device=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{print $1" "$2}'` + set -o pipefail + xcodebuild build-for-testing test -scheme "Unit Tests" -project "Tree Tracker.xcodeproj" -destination "platform=$platform,name=$device" | xcpretty + - name: Configure Keychain + env: + PROVISIONING_PROFILE_BASE64: ${{ secrets.PROVISIONING_PROFILE_BASE64 }} + DISTRIBUTION_CERT_BASE64: ${{ secrets.DISTRIBUTION_CERT_BASE64 }} + APP_SPECIFIC_PWD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} + run: | + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + echo -n $PROVISIONING_PROFILE_BASE64 | base64 -D > ~/Library/MobileDevice/Provisioning\ Profiles/iOS_App_Store_Distribution_Profile_20220213.mobileprovision.mobileprovision + echo -n $DISTRIBUTION_CERT_BASE64 | base64 -D > ~/Certificates.p12 + + ls -lrt ~/Library/MobileDevice/Provisioning\ Profiles + ls -lrt ~/Certificates.p12 + + security create-keychain -p "" build.keychain + security import ~/Certificates.p12 -t agg -k ~/Library/Keychains/build.keychain -P "" -A + security list-keychains -s ~/Library/Keychains/build.keychain + security default-keychain -s ~/Library/Keychains/build.keychain + security unlock-keychain -p "" ~/Library/Keychains/build.keychain + security set-key-partition-list -S apple-tool:,apple: -s -k "" ~/Library/Keychains/build.keychain + - name: Build archive + run: | + set -o pipefail + mkdir -p ~/build + xcodebuild clean archive -scheme "Tree Tracker" -project "Tree Tracker.xcodeproj" -sdk iphoneos -configuration Release -archivePath ~/build/Tree\ Tracker.xcarchive | xcpretty + - name: Export .ipa + run: | + set -o pipefail + xcodebuild -archivePath ~/build/Tree\ Tracker.xcarchive -exportOptionsPlist $GITHUB_WORKSPACE/Tree\ Tracker/ExportOptions.plist -exportPath ~/build -allowProvisioningUpdates -exportArchive | xcpretty + - name: Publish + if: ${{ success() && github.ref_name == 'main' && github.event_name != 'pull_request' }} + env: + APPLEID_USERNAME: ${{ secrets.APPLE_APPLE_ID }} + APPLEID_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} + run: | + xcrun altool --upload-app -t ios -f ~/build/Tree\ Tracker.ipa -u "$APPLEID_USERNAME" -p "$APPLEID_PASSWORD" --verbose \ No newline at end of file diff --git a/CICD.md b/CICD.md new file mode 100644 index 0000000..23b0f97 --- /dev/null +++ b/CICD.md @@ -0,0 +1,46 @@ +# Tree Tracker CI/CD Setup +CI/CD for Tree Tracker has been configured with GitHub Actions to allow new builds to be tested and delivered via TestFlight with a single click or in response to specific repository events. + +## Workflow + +The GitHub Actions workflow is located in `.github/workflows/build-release.yaml` and will trigger on any push or pull request for the repository. If the triggering event is a merge to `main`, the resulting application will also be published to TestFlight. Unit tests are executed early on in the workflow, which will exit if these do not pass. + +## Pre-requisites + +In order for the workflow to run successfully, repository secrets must be configured to provide the various API credentials the application requires. + +To add these, navigate to _Settings > Security > Secrets > Actions_ and add the following as repository secrets with the appropriate values: + +``` +AIRTABLE_API_KEY +AIRTABLE_BASE_ID +AIRTABLE_TREES_TABLE_NAME +AIRTABLE_SPECIES_TABLE_NAME +AIRTABLE_SUPERVISORS_TABLE_NAME +AIRTABLE_SITES_TABLE_NAME +CLOUDINARY_CLOUD_NAME +CLOUDINARY_UPLOAD_PRESET_NAME +``` + +Finally, additional secrets must be configured to store the details required for signing and publishing the app to the AppStore. Add the following secrets in the same way as before, with the appropriate values: + +``` +PROVISIONING_PROFILE_BASE64 +DISTRIBUTION_CERT_BASE64 +APPLE_APPLE_ID +APPLE_APP_SPECIFIC_PASSWORD +``` + +The provisioning profile used is currently _iOS App Store Distribution Profile 20220213_, which may be downloaded from AppStore Connect. Both files should be encoded to base64 via the following command line: + +`cat | base64` + +The App Specific Password is essentially an additional password which you can use to authenticate your AppleID account with, and should only be used in specific cases. Generate a new password as follows: + +* Sign in with your AppleID at https://appleid.apple.com/ +* Navigate to _Sign-In and Security > App-Specific Password_ +* Add or generate a new App-Specific Password and add the password to the `APPLE_APP_SPECIFIC_PASSWORD` secret as listed above, along with your AppleID in `APPLE_APPLE_ID`. + +## TestFlight notes + +The build number of the app is set to the run number of the workflow using `agvtool`. Updates to the release number should be made manually via a PR. Once published, compliance requirements will need to be accepted manually in TestFlight, and the appropriate tester groups will need to be added in order to get access to the latest build. \ No newline at end of file diff --git a/Tree Tracker.xcodeproj/project.pbxproj b/Tree Tracker.xcodeproj/project.pbxproj index 3476647..7614b21 100644 --- a/Tree Tracker.xcodeproj/project.pbxproj +++ b/Tree Tracker.xcodeproj/project.pbxproj @@ -108,6 +108,7 @@ 85DC214625E0FCBF003F0721 /* GRDBImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85DC214525E0FCBF003F0721 /* GRDBImageCache.swift */; }; 85E0E05E25B33F8C009D8FC0 /* TappableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E0E05D25B33F8C009D8FC0 /* TappableButton.swift */; }; 85E0E06225B35744009D8FC0 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E0E06125B35744009D8FC0 /* UIView.swift */; }; + 9D5CDBD727BBC080007D4F0A /* ExportOptions.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9D5CDBD627BBC080007D4F0A /* ExportOptions.plist */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -225,6 +226,7 @@ 85DC214525E0FCBF003F0721 /* GRDBImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GRDBImageCache.swift; sourceTree = ""; }; 85E0E05D25B33F8C009D8FC0 /* TappableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TappableButton.swift; sourceTree = ""; }; 85E0E06125B35744009D8FC0 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; }; + 9D5CDBD627BBC080007D4F0A /* ExportOptions.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = ExportOptions.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -307,6 +309,7 @@ 853ABD7225961B4800144B0D /* IDETemplateMacros.plist */, 853ABD602596144A00144B0D /* LaunchScreen.storyboard */, 853ABD632596144A00144B0D /* Info.plist */, + 9D5CDBD627BBC080007D4F0A /* ExportOptions.plist */, ); path = "Tree Tracker"; sourceTree = ""; @@ -566,6 +569,7 @@ buildRules = ( ); dependencies = ( + 9D5CDBD527BBB7D2007D4F0A /* PBXTargetDependency */, ); name = "Tree Tracker"; packageProductDependencies = ( @@ -634,6 +638,7 @@ buildActionMask = 2147483647; files = ( 853ABD7A25961C6500144B0D /* License.md in Resources */, + 9D5CDBD727BBC080007D4F0A /* ExportOptions.plist in Resources */, 853ABD622596144A00144B0D /* LaunchScreen.storyboard in Resources */, 853ABD7325961B4800144B0D /* IDETemplateMacros.plist in Resources */, 853ABD5F2596144A00144B0D /* Assets.xcassets in Resources */, @@ -759,6 +764,10 @@ target = 853ABD512596144900144B0D /* Tree Tracker */; targetProxy = 851DAC2C262F2FA70087E1D4 /* PBXContainerItemProxy */; }; + 9D5CDBD527BBB7D2007D4F0A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = 9D5CDBD427BBB7D2007D4F0A /* Alamofire */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -939,7 +948,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Distribution: Protect Earth CIO (K5RUKV288P)"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = K5RUKV288P; INFOPLIST_FILE = "Tree Tracker/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -950,8 +961,10 @@ MARKETING_VERSION = 0.7.0; PRODUCT_BUNDLE_IDENTIFIER = "com.protect.earth.Tree-Tracker"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "iOS App Store Distribution Profile 2022-02-13"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; @@ -960,7 +973,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Distribution: Protect Earth CIO (K5RUKV288P)"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = K5RUKV288P; INFOPLIST_FILE = "Tree Tracker/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -971,8 +986,10 @@ MARKETING_VERSION = 0.7.0; PRODUCT_BUNDLE_IDENTIFIER = "com.protect.earth.Tree-Tracker"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "iOS App Store Distribution Profile 2022-02-13"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; @@ -1051,6 +1068,11 @@ package = 85B83A0B25B87C0D0008E167 /* XCRemoteSwiftPackageReference "BSImagePicker" */; productName = BSImagePicker; }; + 9D5CDBD427BBB7D2007D4F0A /* Alamofire */ = { + isa = XCSwiftPackageProductDependency; + package = 85A0EF8125A2271C003CE744 /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 853ABD4A2596144900144B0D /* Project object */; diff --git a/Tree Tracker/ExportOptions.plist b/Tree Tracker/ExportOptions.plist new file mode 100644 index 0000000..9e9bfac --- /dev/null +++ b/Tree Tracker/ExportOptions.plist @@ -0,0 +1,17 @@ + + + + + teamID + K5RUKV288P + signingCertificate + iPhone Distribution: Protect Earth CIO (K5RUKV288P) + provisioningProfiles + + com.protect.earth.Tree-Tracker + iOS App Store Distribution Profile 2022-02-13 + + method + app-store + + diff --git a/Tree Tracker/Info.plist b/Tree Tracker/Info.plist index 51ddea6..b37ef75 100644 --- a/Tree Tracker/Info.plist +++ b/Tree Tracker/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS NSCameraUsageDescription