diff --git a/.azure-pipelines-canary.yml b/.azure-pipelines-canary.yml index fcd8692b..3ec65890 100644 --- a/.azure-pipelines-canary.yml +++ b/.azure-pipelines-canary.yml @@ -2,7 +2,7 @@ trigger: # Start a new run as soon as a "canaries/build/(...)" branch is created branches: include: - canaries/build/* - + variables: - template: build/variables.yml @@ -23,22 +23,18 @@ stages: removeHyperlinksFromReleaseNotes: true BannerVersionNameText: "CANARY" -- stage: AppCenter_TestFlight_Canary +- stage: Deploygate_TestFlight_Canary condition: and(succeeded(), eq(variables['IsCanary'], 'true')) dependsOn: Build_Canary jobs: - - template: build/stage-release-appcenter.yml + - template: build/stage-release-deploygate.yml parameters: applicationEnvironment: Staging - deploymentEnvironment: AppCenter - appCenterWindowsSlug: $(AppCenterWindowsSlug_Canary) - appCenteriOSSlug: $(AppCenteriOSSlug_Canary) - appCenterAndroidSlug: $(AppCenterAndroidSlug_Canary) + deploymentEnvironment: Deploygate androidKeyStoreFile: $(InternalKeystore) androidVariableGroup: 'ApplicationTemplate.Distribution.Internal.Android' - appCenterServiceConnectionName: $(AppCenterCanaryServiceConnection) - appCenterDistributionGroup: $(AppCenterCanaryDistributionGroup) + iosVariableGroup: 'ApplicationTemplate.Distribution.Internal.iOS' - template: build/stage-release-appstore.yml parameters: diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index 7453f813..2509f73e 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -76,21 +76,17 @@ stages: iosVariableGroup: 'ApplicationTemplate.Distribution.Internal.iOS' BannerVersionNameText: "STAGING" -- stage: AppCenter_TestFlight_Staging +- stage: Deploygate_TestFlight_Staging condition: and(succeeded(), eq(variables['IsLightBuild'], 'false')) dependsOn: Build_Staging jobs: - - template: build/stage-release-appcenter.yml + - template: build/stage-release-deploygate.yml parameters: applicationEnvironment: Staging - deploymentEnvironment: AppCenter - appCenterWindowsSlug: $(AppCenterWindowsSlug) - appCenteriOSSlug: $(AppCenteriOSSlug) - appCenterAndroidSlug: $(AppCenterAndroidSlug) + deploymentEnvironment: Deploygate androidKeyStoreFile: $(InternalKeystore) androidVariableGroup: 'ApplicationTemplate.Distribution.Internal.Android' - appCenterServiceConnectionName: $(AppCenterServiceConnection) - appCenterDistributionGroup: $(AppCenterDistributionGroup) + iosVariableGroup: 'ApplicationTemplate.Distribution.Internal.iOS' - template: build/stage-release-appstore.yml parameters: @@ -110,21 +106,17 @@ stages: iosCertificateFile: $(AppStoreCertificate) iosVariableGroup: 'ApplicationTemplate.Distribution.AppStore' -- stage: AppCenter_Production +- stage: Deploygate_Production condition: and(succeeded(), eq(variables['IsLightBuild'], 'false')) dependsOn: Build_Production jobs: - - template: build/stage-release-appcenter.yml + - template: build/stage-release-deploygate.yml parameters: applicationEnvironment: Production - deploymentEnvironment: 'AppCenter Prod' - appCenterWindowsSlug: $(AppCenterWindowsSlug_Production) - appCenteriOSSlug: $(AppCenteriOSSlug_Production) - appCenterAndroidSlug: $(AppCenterAndroidSlug_Production) + deploymentEnvironment: 'Deploygate Prod' androidKeyStoreFile: $(GooglePlayKeystore) androidVariableGroup: 'ApplicationTemplate.Distribution.GooglePlay' - appCenterServiceConnectionName: $(AppCenterServiceConnection) - appCenterDistributionGroup: $(AppCenterDistributionGroup) + iosVariableGroup: 'ApplicationTemplate.Distribution.AppStore' - stage: AppStore condition: and(succeeded(), eq(variables['IsLightBuild'], 'false')) diff --git a/build/fastlane/Fastfile b/build/fastlane/Fastfile new file mode 100644 index 00000000..353c807d --- /dev/null +++ b/build/fastlane/Fastfile @@ -0,0 +1,14 @@ +default_platform(:ios) + +platform :ios do + desc "Upload Application to Deploygate" + lane :upload_deploygate do + deploygate( + api_token: ENV['deploygateApiToken'], + user: ENV['deploygateUsername'], + ipa: ENV['ipaFile'], + apk: ENV['apkFile'], + message: ENV['releasesNotes'] + ) + end +end \ No newline at end of file diff --git a/build/fastlane/Gemfile b/build/fastlane/Gemfile new file mode 100644 index 00000000..ee1dd979 --- /dev/null +++ b/build/fastlane/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "fastlane", "~> 2.225" \ No newline at end of file diff --git a/build/stage-release-appcenter.yml b/build/stage-release-appcenter.yml deleted file mode 100644 index 69496323..00000000 --- a/build/stage-release-appcenter.yml +++ /dev/null @@ -1,110 +0,0 @@ -parameters: - applicationEnvironment: '' # e.g. "Staging", "Production" - deploymentEnvironment: '' # e.g. "GooglePlay", "AppStore", "AppCenter" - appCenterWindowsSlug: '' - appCenteriOSSlug: '' - appCenterAndroidSlug: '' - androidKeyStoreFile: '' - androidVariableGroup: '' - appCenterServiceConnectionName: '' - appCenterDistributionGroup: '' - -jobs: -- deployment: AppCenter_Android - pool: - vmImage: $(windowsHostedAgentImage) - variables: - - name: artifactName - value: $(AndroidArtifactName)_${{ parameters.applicationEnvironment }} - - name: releaseNotesArtifactName - value: ReleaseNotes_${{ parameters.applicationEnvironment }} - - group: ${{ parameters.androidVariableGroup }} - environment: ${{ parameters.deploymentEnvironment }} - strategy: - runOnce: - deploy: - steps: - - download: current - displayName: "Download Artifact" - artifact: $(artifactName) - - - download: current - displayName: "Download Release Notes" - artifact: $(releaseNotesArtifactName) - - - task: AppCenterDistribute@3 - displayName: Deploy Android to AppCenter - inputs: - serverEndpoint: ${{ parameters.appCenterServiceConnectionName }} - distributionGroupId: ${{ parameters.appCenterDistributionGroup }} - appSlug: ${{ parameters.appCenterAndroidSlug }} - appFile: '$(Pipeline.Workspace)/$(artifactName)/*Signed.aab' - releaseNotesOption: file - releaseNotesFile: "$(Pipeline.Workspace)/$(releaseNotesArtifactName)/ReleaseNotes-Excerpt.md" - isSilent: true - - - task: DeleteFiles@1 - displayName: "Remove Downloaded Artifacts (Build)" - condition: always() - inputs: - SourceFolder: $(Pipeline.Workspace)/$(artifactName) - RemoveSourceFolder: true - Contents: '**' - - - task: DeleteFiles@1 - displayName: "Remove Downloaded Artifacts (Release Notes)" - condition: always() - inputs: - SourceFolder: $(Pipeline.Workspace)/$(releaseNotesArtifactName) - RemoveSourceFolder: true - Contents: '**' - -- deployment: AppCenter_iOS - pool: - vmImage: $(macOSHostedAgentImage) - variables: - - name: artifactName - value: $(iOSArtifactName)_${{ parameters.applicationEnvironment }} - - name: releaseNotesArtifactName - value: ReleaseNotes_${{ parameters.applicationEnvironment }} - environment: ${{ parameters.deploymentEnvironment }} - strategy: - runOnce: - deploy: - steps: - - download: current - displayName: "Download Artifact" - artifact: $(artifactName) - - - download: current - displayName: "Download Release Notes" - artifact: $(releaseNotesArtifactName) - - - task: AppCenterDistribute@3 - displayName: Deploy iOS to AppCenter - inputs: - serverEndpoint: ${{ parameters.appCenterServiceConnectionName }} - appSlug: ${{ parameters.appCenteriOSSlug }} - appFile: $(Pipeline.Workspace)/$(artifactName)/*.ipa - symbolsDsymFiles: $(Pipeline.Workspace)/$(artifactName)/*.dSYM - symbolsIncludeParentDirectory: true - distributionGroupId: ${{ parameters.appCenterDistributionGroup }} - releaseNotesOption: file - releaseNotesFile: "$(Pipeline.Workspace)/$(releaseNotesArtifactName)/ReleaseNotes-Excerpt.md" - isSilent: true - - - task: DeleteFiles@1 - displayName: "Remove Downloaded Artifacts (Build)" - condition: always() - inputs: - SourceFolder: $(Pipeline.Workspace)/$(artifactName) - RemoveSourceFolder: true - Contents: '**' - - - task: DeleteFiles@1 - displayName: "Remove Downloaded Artifacts (Release Notes)" - condition: always() - inputs: - SourceFolder: $(Pipeline.Workspace)/$(releaseNotesArtifactName) - RemoveSourceFolder: true - Contents: '**' diff --git a/build/stage-release-appstore.yml b/build/stage-release-appstore.yml index d6c91a2b..f7de6ee3 100644 --- a/build/stage-release-appstore.yml +++ b/build/stage-release-appstore.yml @@ -1,6 +1,6 @@ parameters: applicationEnvironment: '' # e.g. "Staging", "Production" - deploymentEnvironment: '' # e.g. "GooglePlay", "AppStore", "AppCenter" + deploymentEnvironment: '' # e.g. "GooglePlay", "AppStore", "Deploygate" jobs: - deployment: AppStore_iOS_${{ parameters.deploymentEnvironment}} diff --git a/build/stage-release-deploygate.yml b/build/stage-release-deploygate.yml new file mode 100644 index 00000000..9024a624 --- /dev/null +++ b/build/stage-release-deploygate.yml @@ -0,0 +1,145 @@ +parameters: + applicationEnvironment: '' # e.g. "Staging", "Production" + deploymentEnvironment: '' # e.g. "GooglePlay", "AppStore", "Deploygate" + androidKeyStoreFile: '' + androidVariableGroup: '' + iosVariableGroup: '' + +jobs: +- deployment: Deploygate_Android + pool: + vmImage: $(macOSHostedAgentImage) + variables: + - name: artifactName + value: $(AndroidArtifactName)_${{ parameters.applicationEnvironment }} + - name: releaseNotesArtifactName + value: ReleaseNotes_${{ parameters.applicationEnvironment }} + - group: ${{ parameters.androidVariableGroup }} + environment: ${{ parameters.deploymentEnvironment }} + strategy: + runOnce: + deploy: + steps: + - checkout: self + + - download: current + displayName: "Download Artifact" + artifact: $(artifactName) + + - download: current + displayName: "Download Release Notes" + artifact: $(releaseNotesArtifactName) + + - script: | + fileContent=$(cat $(Pipeline.Workspace)/$(releaseNotesArtifactName)/ReleaseNotes-Excerpt.md) + modifiedContent=$(echo "$fileContent" | tr '\n' ' ') + echo "##vso[task.setvariable variable=releaseNotes]$modifiedContent" + displayName: Read Release Notes + + - script: | + gem install bundler + bundle install --path ~/.gem + bundle update --bundler + displayName: Run Bundle install + workingDirectory: '$(Build.SourcesDirectory)/build/fastlane' + condition: succeeded() + + - script: | + bundle exec fastlane upload_deploygate --verbose + displayName: Deploy Android to Deploygate + workingDirectory: '$(Build.SourcesDirectory)/build/fastlane' + condition: succeeded() + env: + deploygateApiToken: $(DeploygateApiToken) + deploygateUsername: $(DeploygateUsername) + releasesNotes: "$(releaseNotes)" + apkFile: '$(Pipeline.Workspace)/$(artifactName)/$(ApplicationIdentifier)-Signed.apk' + + - task: DeleteFiles@1 + displayName: "Remove Downloaded Artifacts (Build)" + condition: always() + inputs: + SourceFolder: $(Pipeline.Workspace)/$(artifactName) + RemoveSourceFolder: true + Contents: '**' + + - task: DeleteFiles@1 + displayName: "Remove Downloaded Artifacts (Release Notes)" + condition: always() + inputs: + SourceFolder: $(Pipeline.Workspace)/$(releaseNotesArtifactName) + RemoveSourceFolder: true + Contents: '**' + + - task: PostBuildCleanup@3 + displayName: 'Post-Build cleanup : Cleanup files to keep build server clean!' + condition: always() + +- deployment: Deploygate_iOS + pool: + vmImage: $(macOSHostedAgentImage) + variables: + - name: artifactName + value: $(iOSArtifactName)_${{ parameters.applicationEnvironment }} + - name: releaseNotesArtifactName + value: ReleaseNotes_${{ parameters.applicationEnvironment }} + - group: ${{ parameters.iosVariableGroup }} + environment: ${{ parameters.deploymentEnvironment }} + strategy: + runOnce: + deploy: + steps: + - checkout: self + + - download: current + displayName: "Download Artifact" + artifact: $(artifactName) + + - download: current + displayName: "Download Release Notes" + artifact: $(releaseNotesArtifactName) + + - script: | + fileContent=$(cat $(Pipeline.Workspace)/$(releaseNotesArtifactName)/ReleaseNotes-Excerpt.md) + modifiedContent=$(echo "$fileContent" | tr '\n' ' ') + echo "##vso[task.setvariable variable=releaseNotes]$modifiedContent" + displayName: Read Release Notes + + - script: | + gem install bundler + bundle install --path ~/.gem + bundle update --bundler + displayName: Run Bundle install + workingDirectory: '$(Build.SourcesDirectory)/build/fastlane' + condition: succeeded() + + - script: | + bundle exec fastlane upload_deploygate --verbose + displayName: Deploy iOS to Deploygate + workingDirectory: '$(Build.SourcesDirectory)/build/fastlane' + condition: succeeded() + env: + deploygateApiToken: $(DeploygateApiToken) + deploygateUsername: $(DeploygateUsername) + releasesNotes: "$(releaseNotes)" + ipaFile: '$(Pipeline.Workspace)/$(artifactName)/$(SolutionName).Mobile.ipa' + + - task: DeleteFiles@1 + displayName: "Remove Downloaded Artifacts (Build)" + condition: always() + inputs: + SourceFolder: $(Pipeline.Workspace)/$(artifactName) + RemoveSourceFolder: true + Contents: '**' + + - task: DeleteFiles@1 + displayName: "Remove Downloaded Artifacts (Release Notes)" + condition: always() + inputs: + SourceFolder: $(Pipeline.Workspace)/$(releaseNotesArtifactName) + RemoveSourceFolder: true + Contents: '**' + + - task: PostBuildCleanup@3 + displayName: 'Post-Build cleanup : Cleanup files to keep build server clean!' + condition: always() \ No newline at end of file diff --git a/build/stage-release-googleplay.yml b/build/stage-release-googleplay.yml index 2f8fd400..ae6f08fb 100644 --- a/build/stage-release-googleplay.yml +++ b/build/stage-release-googleplay.yml @@ -1,6 +1,6 @@ parameters: applicationEnvironment: '' # e.g. "Staging", "Production" - deploymentEnvironment: '' # e.g. "GooglePlay", "AppStore", "AppCenter" + deploymentEnvironment: '' # e.g. "GooglePlay", "AppStore", "Deploygate" jobs: - deployment: GooglePlay_Android diff --git a/build/variables.yml b/build/variables.yml index b71d01b4..ea16c9fc 100644 --- a/build/variables.yml +++ b/build/variables.yml @@ -3,10 +3,11 @@ # Make sure you have the following variable groups in your Azure pipeline library: # # ApplicationTemplate.Distribution.Internal.Android - # ApplicationIdentifier: This is the internal application id of the app for AppCenter releases. Note that this variable is used by Nimue to automatically change the package name. # AndroidSigningKeyAlias: This is the keystore alias. # AndroidSigningKeyPass: This is the keystore keypass (secured). # AndroidSigningStorePass: This is the keystore storepass (secured). + # DeploygateApiToken: This is the target username or organization name (secured). + # DeploygateUsername: This is the username or for TestFairy (secured). # # ApplicationTemplate.Distribution.GooglePlay # ApplicationIdentifier: This is the official application id of the app that should go on the store. Note that this variable is used by Nimue to automatically change the package name. @@ -15,8 +16,9 @@ # AndroidSigningStorePass: This is the keystore storepass (secured). # # ApplicationTemplate.Distribution.Internal.iOS - # ApplicationIdentifier: This is the internal application id of the app for AppCenter releases. Note that this variable is used by Nimue to automatically change the bundle id. # AppleCertificatePassword: The certificate password (secured). + # DeploygateApiToken: This is the target username or organization name (secured). + # DeploygateUsername: This is the username or for TestFairy (secured). # # ApplicationTemplate.Distribution.AppStore # ApplicationIdentifier: The official bundle id of the app that should go on the store; the app will be resigned with this id. Note that this variable is used by Nimue to automatically change the bundle id. @@ -31,7 +33,7 @@ InternalKeystore: com.nventive.internal.applicationtemplate.jks # This is the internal keystore used for internal builds. GooglePlayKeystore: com.nventive.applicationtemplate.jks # This is the official keystore used for Google Play. # iOS - InternalProvisioningProfile: com.nventive.applicationtemplate.mobileprovision # This is the internal provisioning profile for internal builds. + InternalProvisioningProfile: com.nventive.applicationtemplate-adhoc.mobileprovision # This is the internal provisioning profile for internal builds. InternalCertificate: nventive.p12 # This is the certificate from the nventive Apple account used to sign internal builds. AppStoreProvisioningProfile: com.nventive.applicationtemplate.mobileprovision # This is the client provisioning profile for the AppStore (Production distribution). AppStoreCertificate: nventive.p12 # This is the client production certificate used to sign AppStore builds. @@ -39,26 +41,7 @@ # Prerequisites - Service connections # Make sure you have the following service connections in your Azure pipeline library. GooglePlayServiceConnection: GooglePlay-nventive-ApplicationTemplate - AppCenterServiceConnection: AppCenter-nventive-framework AppStoreServiceConnection: AppStore-nventive - AppCenterCanaryServiceConnection: AppCenter-nventive-framework - - # AppCenter slugs - # The "app slug" corresponds to the identifiers of the app in AppCenter; to find it, navigate to the app in a browser and; - # the URL should look like this: https://appcenter.ms/orgs/{orgId}/apps/{appId}; the slug is simply: "{orgId}/{appId}". - AppCenterAndroidSlug: 'nventive-framework/Application-Template-1' - AppCenteriOSSlug: 'nventive-framework/Application-Template' - AppCenterWindowsSlug: 'nventive-framework/Application-Template-2' - AppCenterAndroidSlug_Production: 'nventive-framework/ApplicationTemplate-Production-1' - AppCenteriOSSlug_Production: 'nventive-framework/ApplicationTemplate-Production' - AppCenterWindowsSlug_Production: 'nventive-framework/ApplicationTemplate-Production-2' - AppCenterAndroidSlug_Canary: 'nventive-framework/Application-Template-Canary-1' - AppCenteriOSSlug_Canary: 'nventive-framework/Application-Template-Canary' - AppCenterWindowsSlug_Canary: 'nventive-framework/Application-Template-Canary-2' - - # AppCenter Distribution Groups - AppCenterDistributionGroup: '00000000-0000-0000-0000-000000000000' - AppCenterCanaryDistributionGroup: '00000000-0000-0000-0000-000000000000' # Azure subscription # AzureSubscriptionName: diff --git a/doc/AzurePipelines.md b/doc/AzurePipelines.md index 356efde3..c8b8b368 100644 --- a/doc/AzurePipelines.md +++ b/doc/AzurePipelines.md @@ -25,7 +25,7 @@ At high level, the CI/CD pipelines do the following: It also runs automated tests during the build steps. ### Pull request runs -Due to the length of mobile builds, pipelines are configured to behave a little differently when building in a context of **pull request (PR) build validation**. To reduce the build time, some runtime performance optimizations are disabled for PR builds. This requires a specific variable called `IsLightBuild` to be set, hence why it is appearing in the pipeline. +Due to the length of mobile builds, pipelines are configured to behave a little differently when building in a context of **pull request (PR) build validation**. To reduce the build time, some runtime performance optimizations are disabled for PR builds. This requires a specific variable called `IsLightBuild` to be set, hence why it is appearing in the pipeline. Also, all release stages are disabled in the context of PR build validation because, with the optimizations differences, the resulting application would not represent the real thing. @@ -68,7 +68,7 @@ This is where the exact build steps are defined. These vary depending on the pla The release stages are even more straigtforward than the build ones. One thing to note is that, for the same reason as it is done at the end of the build steps, a clean-up step is included in every stage. -### AppCenter Release Stage ([stage-release-appcenter.yml](../build/stage-release-appcenter.yml)) +### Deploygate Release Stage ([stage-release-deploygate.yml](../build/stage-release-deploygate.yml)) This stage is in charge of pushing the application to AppCenter. It's divided into 2 jobs, one for each platform. ### Apple AppStore Release Stage ([stage-release-appstore.yml](../build/stage-release-appstore.yml)) @@ -102,4 +102,4 @@ This pipelines creates a branch on which it commits a version of the latest code ### Canary Deployment This pipelines triggers automatically when a new branch is created and pushed by the previous pipeline. It takes the new code, builds it, and deploys so that it can be manually tested. -This pipeline uses the same build and release stages as the main CI/CD pipeline of the app. \ No newline at end of file +This pipeline uses the same build and release stages as the main CI/CD pipeline of the app. diff --git a/src/ApplicationTemplate.sln b/src/ApplicationTemplate.sln index fcc4a9db..cca8ff9a 100644 --- a/src/ApplicationTemplate.sln +++ b/src/ApplicationTemplate.sln @@ -64,7 +64,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6E8378CB ..\build\canary-merge.yml = ..\build\canary-merge.yml ..\build\gitversion-config.yml = ..\build\gitversion-config.yml ..\build\stage-build.yml = ..\build\stage-build.yml - ..\build\stage-release-appcenter.yml = ..\build\stage-release-appcenter.yml + ..\build\stage-release-deploygate.yml = ..\build\stage-release-deploygate.yml ..\build\stage-release-appstore.yml = ..\build\stage-release-appstore.yml ..\build\stage-release-googleplay.yml = ..\build\stage-release-googleplay.yml ..\build\steps-build-android.yml = ..\build\steps-build-android.yml