diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 00000000..e086d3f9 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,209 @@ +name: Android Builds + +on: + schedule: + - cron: "0 9 * * *" # 9am UTC = 1am PST / 2am PDT. for all testapps except firestore + pull_request: + types: [opened, reopened, synchronize] + workflow_dispatch: + inputs: + apis: + description: 'CSV of apis to build and test' + default: 'analytics,auth,database,dynamic_links,firestore,functions,gma,messaging,remote_config,storage' + required: true + +env: + CCACHE_DIR: ${{ github.workspace }}/ccache_dir + GITHUB_TOKEN: ${{ github.token }} + xcodeVersion: "13.3.1" # Only affects Mac runners, and only for prerequisites. + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + check_and_prepare: + runs-on: ubuntu-latest + outputs: + apis: ${{ steps.set_outputs.outputs.apis }} + steps: + - id: set_outputs + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + apis="${{ github.event.inputs.apis }}" + else + apis="analytics,auth,database,dynamic_links,firestore,functions,gma,messaging,remote_config,storage" + fi + echo apis: ${apis} + echo "::set-output name=apis::${apis}" + + build: + name: android-${{ matrix.os }}-${{ matrix.architecture }}-${{ matrix.python_version }} + runs-on: ${{ matrix.os }} + needs: [check_and_prepare] + strategy: + fail-fast: false + matrix: + os: [macos-12, ubuntu-latest, windows-latest] + architecture: [x64] + python_version: [3.7] + steps: + - name: setup Xcode version (macos) + if: runner.os == 'macOS' + run: sudo xcode-select -s /Applications/Xcode_${{ env.xcodeVersion }}.app/Contents/Developer + + - name: Store git credentials for all git commands + # Forces all git commands to use authenticated https, to prevent throttling. + shell: bash + run: | + git config --global credential.helper 'store --file /tmp/git-credentials' + echo 'https://${{ github.token }}@github.com' > /tmp/git-credentials + + - name: Enable Git Long-paths Support + if: runner.os == 'Windows' + run: git config --system core.longpaths true + + - uses: actions/checkout@v2 + with: + submodules: true + + - name: Set env variables for subsequent steps (all) + shell: bash + run: | + echo "MATRIX_UNIQUE_NAME=${{ matrix.os }}-${{ matrix.architecture }}" >> $GITHUB_ENV + echo "GHA_INSTALL_CCACHE=1" >> $GITHUB_ENV + + - name: Setup python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python_version }} + architecture: ${{ matrix.architecture }} + + - name: Add msbuild to PATH + if: startsWith(matrix.os, 'windows') + uses: microsoft/setup-msbuild@v1.0.2 + + - name: Cache NDK + id: cache_ndk + uses: actions/cache@v2 + with: + path: /tmp/android-ndk-r21e + key: android-ndk-${{ matrix.os }}-r21e + + - name: Check cached NDK + shell: bash + if: steps.cache_ndk.outputs.cache-hit != 'true' + run: | + # If the NDK failed to download from the cache, but there is a + # /tmp/android-ndk-r21e directory, it's incomplete, so remove it. + if [[ -d "/tmp/android-ndk-r21e" ]]; then + echo "Removing incomplete download of NDK" + rm -rf /tmp/android-ndk-r21e + fi + + - name: Update homebrew (avoid bintray errors) + uses: nick-invision/retry@v2 + if: startsWith(matrix.os, 'macos') + with: + timeout_minutes: 10 + max_attempts: 3 + command: | + # Temporarily here until Github runners have updated their version of + # homebrew. This prevents errors arising from the shut down of + # binutils, used by older version of homebrew for hosting packages. + brew update + + - name: Install prerequisites + uses: nick-invision/retry@v2 + with: + timeout_minutes: 10 + max_attempts: 3 + shell: bash + command: | + scripts/build_scripts/android/install_prereqs.sh + echo "NDK_ROOT=/tmp/android-ndk-r21e" >> $GITHUB_ENV + echo "ANDROID_NDK_HOME=/tmp/android-ndk-r21e" >> $GITHUB_ENV + pip install -r scripts/build_scripts/python_requirements.txt + python scripts/restore_secrets.py --passphrase "${{ secrets.TEST_SECRET }}" + + - name: Download C++ SDK + shell: bash + run: | + set +e + # Retry up to 10 times because Curl has a tendency to timeout on + # Github runners. + for retry in {1..10} error; do + if [[ $retry == "error" ]]; then exit 5; fi + curl -LSs \ + "https://firebase.google.com/download/cpp" \ + --output firebase_cpp_sdk.zip && break + sleep 300 + done + set -e + mkdir /tmp/downloaded_sdk + unzip -q firebase_cpp_sdk.zip -d /tmp/downloaded_sdk/ + rm -f firebase_cpp_sdk.zip + + - name: Cache ccache files + id: cache_ccache + uses: actions/cache@v2 + with: + path: ccache_dir + key: dev-test-ccache-${{ env.MATRIX_UNIQUE_NAME }} + + - name: Build testapp + shell: bash + run: | + set -x + python scripts/build_scripts/build_testapps.py --p Android \ + --t ${{ needs.check_and_prepare.outputs.apis }} \ + --output_directory "${{ github.workspace }}" \ + --artifact_name "android-${{ matrix.os }}" \ + --noadd_timestamp \ + --short_output_paths \ + --gha_build \ + --packaged_sdk /tmp/downloaded_sdk/firebase_cpp_sdk + + - name: Stats for ccache (mac and linux) + if: startsWith(matrix.os, 'ubuntu') || startsWith(matrix.os, 'macos') + run: ccache -s + + - name: Prepare results summary artifact + if: ${{ !cancelled() }} + shell: bash + run: | + if [ ! -f build-results-android-${{ matrix.os }}.log.json ]; then + echo "__SUMMARY_MISSING__" > build-results-android-${{ matrix.os }}.log.json + fi + + - name: Upload Android integration tests artifact + uses: actions/upload-artifact@v3 + if: ${{ !cancelled() }} + with: + name: testapps-android-${{ matrix.os }} + path: testapps-android-${{ matrix.os }} + retention-days: ${{ env.artifactRetentionDays }} + + - name: Upload Android build results artifact + uses: actions/upload-artifact@v3 + if: ${{ !cancelled() }} + with: + name: log-artifact + path: build-results-android-${{ matrix.os }}* + retention-days: ${{ env.artifactRetentionDays }} + + - name: Download log artifacts + if: ${{ needs.check_and_prepare.outputs.pr_number && failure() && !cancelled() }} + uses: actions/download-artifact@v3 + with: + path: test_results + name: log-artifact + + - name: Summarize build results + if: ${{ !cancelled() }} + shell: bash + run: | + cat build-results-android-${{ matrix.os }}.log + if [[ "${{ job.status }}" != "success" ]]; then + exit 1 + fi diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml new file mode 100644 index 00000000..72c85f64 --- /dev/null +++ b/.github/workflows/ios.yml @@ -0,0 +1,14 @@ +name: iOS builds + +on: + workflow_dispatch: + inputs: + apis: + description: 'CSV of apis whose quickstart examples we should build' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: noop + run: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c4f78aab --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +GoogleService-Info.plist +google-services.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 02f251e9..c02eddc2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -109,7 +109,7 @@ Before you submit your pull request consider the following guidelines: * Make your changes in a new git branch: ```shell - git checkout -b my-fix-branch master + git checkout -b my-fix-branch main ``` * Create your patch, **including appropriate test cases**. @@ -133,14 +133,14 @@ Before you submit your pull request consider the following guidelines: git push origin my-fix-branch ``` -* In GitHub, send a pull request to `firebase/quickstart-cpp:master`. +* In GitHub, send a pull request to `firebase/quickstart-cpp:main`. * If we suggest changes then: * Make the required updates. * Rebase your branch and force push to your GitHub repository (this will update your Pull Request): ```shell - git rebase master -i + git rebase main -i git push origin my-fix-branch -f ``` @@ -158,10 +158,10 @@ the changes from the main (upstream) repository: git push origin --delete my-fix-branch ``` -* Check out the master branch: +* Check out the main branch: ```shell - git checkout master -f + git checkout main -f ``` * Delete the local branch: @@ -170,10 +170,10 @@ the changes from the main (upstream) repository: git branch -D my-fix-branch ``` -* Update your master with the latest upstream version: +* Update your main with the latest upstream version: ```shell - git pull --ff upstream master + git pull --ff upstream main ``` ## Coding Rules @@ -186,7 +186,7 @@ Please sign our [Contributor License Agreement][google-cla] (CLA) before sending pull requests. For any code changes to be accepted, the CLA must be signed. It's a quick process, we promise! -*This guide was inspired by the [AngularJS contribution guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).* +*This guide was inspired by the [AngularJS contribution guidelines](https://github.com/angular/angular.js/blob/main/CONTRIBUTING.md).* [github]: https://github.com/firebase/quickstart-cpp [google-cla]: https://cla.developers.google.com diff --git a/admob/testapp/Podfile b/admob/testapp/Podfile deleted file mode 100644 index 9759f528..00000000 --- a/admob/testapp/Podfile +++ /dev/null @@ -1,6 +0,0 @@ -source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' -# AdMob test application. -target 'testapp' do - pod 'Firebase/AdMob', '7.0.0' -end diff --git a/admob/testapp/src/common_main.cc b/admob/testapp/src/common_main.cc deleted file mode 100644 index eae4f5c4..00000000 --- a/admob/testapp/src/common_main.cc +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright 2016 Google Inc. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "firebase/admob.h" -#include "firebase/admob/banner_view.h" -#include "firebase/admob/interstitial_ad.h" -#include "firebase/admob/rewarded_video.h" -#include "firebase/admob/types.h" -#include "firebase/app.h" -#include "firebase/future.h" - -// Thin OS abstraction layer. -#include "main.h" // NOLINT - -// A simple listener that logs changes to a BannerView. -class LoggingBannerViewListener : public firebase::admob::BannerView::Listener { - public: - LoggingBannerViewListener() {} - void OnPresentationStateChanged( - firebase::admob::BannerView* banner_view, - firebase::admob::BannerView::PresentationState state) override { - ::LogMessage("BannerView PresentationState has changed to %d.", state); - } - void OnBoundingBoxChanged(firebase::admob::BannerView* banner_view, - firebase::admob::BoundingBox box) override { - ::LogMessage( - "BannerView BoundingBox has changed to (x: %d, y: %d, width: %d, " - "height %d).", - box.x, box.y, box.width, box.height); - } -}; - -// A simple listener that logs changes to an InterstitialAd. -class LoggingInterstitialAdListener - : public firebase::admob::InterstitialAd::Listener { - public: - LoggingInterstitialAdListener() {} - void OnPresentationStateChanged( - firebase::admob::InterstitialAd* interstitial_ad, - firebase::admob::InterstitialAd::PresentationState state) override { - ::LogMessage("InterstitialAd PresentationState has changed to %d.", state); - } -}; - -// A simple listener that logs changes to rewarded video state. -class LoggingRewardedVideoListener - : public firebase::admob::rewarded_video::Listener { - public: - LoggingRewardedVideoListener() {} - void OnRewarded(firebase::admob::rewarded_video::RewardItem reward) override { - ::LogMessage("Rewarding user with %f %s.", reward.amount, - reward.reward_type.c_str()); - } - void OnPresentationStateChanged( - firebase::admob::rewarded_video::PresentationState state) override { - ::LogMessage("Rewarded video PresentationState has changed to %d.", state); - } -}; - -// The AdMob app IDs for the test app. -#if defined(__ANDROID__) -// If you change the AdMob app ID for your Android app, make sure to change it -// in AndroidManifest.xml as well. -const char* kAdMobAppID = "YOUR_ANDROID_ADMOB_APP_ID"; -#else -// If you change the AdMob app ID for your iOS app, make sure to change the -// value for "GADApplicationIdentifier" in your Info.plist as well. -const char* kAdMobAppID = "YOUR_IOS_ADMOB_APP_ID"; -#endif - -// These ad units IDs have been created specifically for testing, and will -// always return test ads. -#if defined(__ANDROID__) -const char* kBannerAdUnit = "ca-app-pub-3940256099942544/6300978111"; -const char* kInterstitialAdUnit = "ca-app-pub-3940256099942544/1033173712"; -const char* kRewardedVideoAdUnit = "ca-app-pub-3940256099942544/5224354917"; -#else -const char* kBannerAdUnit = "ca-app-pub-3940256099942544/2934735716"; -const char* kInterstitialAdUnit = "ca-app-pub-3940256099942544/4411468910"; -const char* kRewardedVideoAdUnit = "ca-app-pub-3940256099942544/1712485313"; -#endif - -// Standard mobile banner size is 320x50. -static const int kBannerWidth = 320; -static const int kBannerHeight = 50; - -// Sample keywords to use in making the request. -static const char* kKeywords[] = {"AdMob", "C++", "Fun"}; - -// Sample test device IDs to use in making the request. -static const char* kTestDeviceIDs[] = {"2077ef9a63d2b398840261c8221a0c9b", - "098fe087d987c9a878965454a65654d7"}; - -// Sample birthday value to use in making the request. -static const int kBirthdayDay = 10; -static const int kBirthdayMonth = 11; -static const int kBirthdayYear = 1976; - -static void WaitForFutureCompletion(firebase::FutureBase future) { - while (!ProcessEvents(1000)) { - if (future.status() != firebase::kFutureStatusPending) { - break; - } - } - - if (future.error() != firebase::admob::kAdMobErrorNone) { - LogMessage("ERROR: Action failed with error code %d and message \"%s\".", - future.error(), future.error_message()); - } -} - -// Execute all methods of the C++ admob API. -extern "C" int common_main(int argc, const char* argv[]) { - firebase::App* app; - LogMessage("Initializing the AdMob library."); - -#if defined(__ANDROID__) - app = ::firebase::App::Create(GetJniEnv(), GetActivity()); -#else - app = ::firebase::App::Create(); -#endif // defined(__ANDROID__) - - LogMessage("Created the Firebase App %x.", - static_cast(reinterpret_cast(app))); - - LogMessage("Initializing the AdMob with Firebase API."); - firebase::admob::Initialize(*app, kAdMobAppID); - - firebase::admob::AdRequest request; - // If the app is aware of the user's gender, it can be added to the targeting - // information. Otherwise, "unknown" should be used. - request.gender = firebase::admob::kGenderUnknown; - - // This value allows publishers to specify whether they would like the request - // to be treated as child-directed for purposes of the Children’s Online - // Privacy Protection Act (COPPA). - // See http://business.ftc.gov/privacy-and-security/childrens-privacy. - request.tagged_for_child_directed_treatment = - firebase::admob::kChildDirectedTreatmentStateTagged; - - // The user's birthday, if known. Note that months are indexed from one. - request.birthday_day = kBirthdayDay; - request.birthday_month = kBirthdayMonth; - request.birthday_year = kBirthdayYear; - - // Additional keywords to be used in targeting. - request.keyword_count = sizeof(kKeywords) / sizeof(kKeywords[0]); - request.keywords = kKeywords; - - // "Extra" key value pairs can be added to the request as well. Typically - // these are used when testing new features. - static const firebase::admob::KeyValuePair kRequestExtras[] = { - {"the_name_of_an_extra", "the_value_for_that_extra"}}; - request.extras_count = sizeof(kRequestExtras) / sizeof(kRequestExtras[0]); - request.extras = kRequestExtras; - - // This example uses ad units that are specially configured to return test ads - // for every request. When using your own ad unit IDs, however, it's important - // to register the device IDs associated with any devices that will be used to - // test the app. This ensures that regardless of the ad unit ID, those - // devices will always receive test ads in compliance with AdMob policy. - // - // Device IDs can be obtained by checking the logcat or the Xcode log while - // debugging. They appear as a long string of hex characters. - request.test_device_id_count = - sizeof(kTestDeviceIDs) / sizeof(kTestDeviceIDs[0]); - request.test_device_ids = kTestDeviceIDs; - - // Create an ad size for the BannerView. - firebase::admob::AdSize banner_ad_size; - banner_ad_size.ad_size_type = firebase::admob::kAdSizeStandard; - banner_ad_size.width = kBannerWidth; - banner_ad_size.height = kBannerHeight; - - LogMessage("Creating the BannerView."); - firebase::admob::BannerView* banner = new firebase::admob::BannerView(); - banner->Initialize(GetWindowContext(), kBannerAdUnit, banner_ad_size); - - WaitForFutureCompletion(banner->InitializeLastResult()); - - // Set the listener. - LoggingBannerViewListener banner_listener; - banner->SetListener(&banner_listener); - - // Load the banner ad. - LogMessage("Loading a banner ad."); - banner->LoadAd(request); - - WaitForFutureCompletion(banner->LoadAdLastResult()); - - // Make the BannerView visible. - LogMessage("Showing the banner ad."); - banner->Show(); - - WaitForFutureCompletion(banner->ShowLastResult()); - - // Move to each of the six pre-defined positions. - LogMessage("Moving the banner ad to top-center."); - banner->MoveTo(firebase::admob::BannerView::kPositionTop); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to top-left."); - banner->MoveTo(firebase::admob::BannerView::kPositionTopLeft); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to top-right."); - banner->MoveTo(firebase::admob::BannerView::kPositionTopRight); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to bottom-center."); - banner->MoveTo(firebase::admob::BannerView::kPositionBottom); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to bottom-left."); - banner->MoveTo(firebase::admob::BannerView::kPositionBottomLeft); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to bottom-right."); - banner->MoveTo(firebase::admob::BannerView::kPositionBottomRight); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - // Try some coordinate moves. - LogMessage("Moving the banner ad to (100, 300)."); - banner->MoveTo(100, 300); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to (100, 400)."); - banner->MoveTo(100, 400); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - // Try hiding and showing the BannerView. - LogMessage("Hiding the banner ad."); - banner->Hide(); - - WaitForFutureCompletion(banner->HideLastResult()); - - LogMessage("Showing the banner ad."); - banner->Show(); - - WaitForFutureCompletion(banner->ShowLastResult()); - - // A few last moves after showing it again. - LogMessage("Moving the banner ad to (100, 300)."); - banner->MoveTo(100, 300); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to (100, 400)."); - banner->MoveTo(100, 400); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Hiding the banner ad now that we're done with it."); - banner->Hide(); - - WaitForFutureCompletion(banner->HideLastResult()); - - // Create and test InterstitialAd. - LogMessage("Creating the InterstitialAd."); - firebase::admob::InterstitialAd* interstitial = - new firebase::admob::InterstitialAd(); - interstitial->Initialize(GetWindowContext(), kInterstitialAdUnit); - - WaitForFutureCompletion(interstitial->InitializeLastResult()); - - // Set the listener. - LoggingInterstitialAdListener interstitial_listener; - interstitial->SetListener(&interstitial_listener); - - // When the InterstitialAd is initialized, load an ad. - LogMessage("Loading an interstitial ad."); - interstitial->LoadAd(request); - - WaitForFutureCompletion(interstitial->LoadAdLastResult()); - - // When the InterstitialAd has loaded an ad, show it. - LogMessage("Showing the interstitial ad."); - interstitial->Show(); - - WaitForFutureCompletion(interstitial->ShowLastResult()); - - // Wait for the user to close the interstitial. - while (interstitial->presentation_state() != - firebase::admob::InterstitialAd::PresentationState:: - kPresentationStateHidden) { - ProcessEvents(1000); - } - - // Start up rewarded video ads and associated mediation adapters. - LogMessage("Initializing rewarded video."); - namespace rewarded_video = firebase::admob::rewarded_video; - rewarded_video::Initialize(); - - WaitForFutureCompletion(rewarded_video::InitializeLastResult()); - - LogMessage("Setting rewarded video listener."); - LoggingRewardedVideoListener rewarded_listener; - rewarded_video::SetListener(&rewarded_listener); - - LogMessage("Loading a rewarded video ad."); - rewarded_video::LoadAd(kRewardedVideoAdUnit, request); - - WaitForFutureCompletion(rewarded_video::LoadAdLastResult()); - - // If an ad has loaded, show it. If the user watches all the way through, the - // LoggingRewardedVideoListener will log a reward! - if (rewarded_video::LoadAdLastResult().error() == - firebase::admob::kAdMobErrorNone) { - LogMessage("Showing a rewarded video ad."); - rewarded_video::Show(GetWindowContext()); - - WaitForFutureCompletion(rewarded_video::ShowLastResult()); - - // Normally Pause and Resume would be called in response to the app pausing - // or losing focus. This is just a test. - LogMessage("Pausing."); - rewarded_video::Pause(); - - WaitForFutureCompletion(rewarded_video::PauseLastResult()); - - LogMessage("Resuming."); - rewarded_video::Resume(); - - WaitForFutureCompletion(rewarded_video::ResumeLastResult()); - } - - LogMessage("Done!"); - - // Wait until the user kills the app. - while (!ProcessEvents(1000)) { - } - - delete banner; - delete interstitial; - rewarded_video::Destroy(); - firebase::admob::Terminate(); - delete app; - - return 0; -} diff --git a/analytics/testapp/AndroidManifest.xml b/analytics/testapp/AndroidManifest.xml index 02165262..b7038a15 100644 --- a/analytics/testapp/AndroidManifest.xml +++ b/analytics/testapp/AndroidManifest.xml @@ -6,9 +6,10 @@ - + - + sign_in_future, const char* fn, +static bool WaitForSignInFuture(Future sign_in_future, const char* fn, AuthError expected_error, Auth* auth) { if (WaitForFuture(sign_in_future, fn, expected_error)) return true; - const User* const* sign_in_user_ptr = sign_in_future.result(); - const User* sign_in_user = - sign_in_user_ptr == nullptr ? nullptr : *sign_in_user_ptr; - const User* auth_user = auth->current_user(); + const User sign_in_user = sign_in_future.result() ? *sign_in_future.result() : + User(); + const User auth_user = auth->current_user(); if (expected_error == ::firebase::auth::kAuthErrorNone && sign_in_user != auth_user) { - LogMessage("ERROR: future's user (%x) and current_user (%x) don't match", - static_cast(reinterpret_cast(sign_in_user)), - static_cast(reinterpret_cast(auth_user))); + LogMessage("ERROR: future's user (%s) and current_user (%s) don't match", + sign_in_user.uid().c_str(), + auth_user.uid().c_str()); } return false; } -static bool WaitForSignInFuture(const Future& sign_in_future, +static bool WaitForSignInFuture(const Future& sign_in_future, const char* fn, AuthError expected_error, Auth* auth) { if (WaitForFuture(sign_in_future, fn, expected_error)) return true; - const SignInResult* sign_in_result = sign_in_future.result(); - const User* sign_in_user = sign_in_result ? sign_in_result->user : nullptr; - const User* auth_user = auth->current_user(); + const AuthResult* auth_result = sign_in_future.result(); + const User sign_in_user = auth_result ? auth_result->user : User(); + const User auth_user = auth->current_user(); if (expected_error == ::firebase::auth::kAuthErrorNone && sign_in_user != auth_user) { - LogMessage("ERROR: future's user (%x) and current_user (%x) don't match", - static_cast(reinterpret_cast(sign_in_user)), - static_cast(reinterpret_cast(auth_user))); + LogMessage("ERROR: future's user (%s) and current_user (%s) don't match", + sign_in_user.uid().c_str(), + auth_user.uid().c_str()); } return false; @@ -159,7 +160,7 @@ static bool WaitForSignInFuture(const Future& sign_in_future, // Wait for the current user to sign out. Typically you should use the // state listener to determine whether the user has signed out. static bool WaitForSignOut(firebase::auth::Auth* auth) { - while (auth->current_user() != nullptr) { + while (!auth->current_user().is_valid()) { if (ProcessEvents(100)) return true; } // Wait - hopefully - long enough for listeners to be signalled. @@ -251,17 +252,17 @@ static void LogVariantMap(const std::map& variant_map, } // Display the sign-in result. -static void LogSignInResult(const SignInResult& result) { - if (!result.user) { +static void LogAuthResult(const AuthResult result) { + if (!result.user.is_valid()) { LogMessage("ERROR: User not signed in"); return; } - LogMessage("* User ID %s", result.user->uid().c_str()); - const AdditionalUserInfo& info = result.info; + LogMessage("* User ID %s", result.user.uid().c_str()); + const AdditionalUserInfo& info = result.additional_user_info; LogMessage("* Provider ID %s", info.provider_id.c_str()); LogMessage("* User Name %s", info.user_name.c_str()); LogVariantMap(info.profile, 0); - const UserMetadata& metadata = result.meta; + UserMetadata metadata = result.user.metadata(); LogMessage("* Sign in timestamp %d", static_cast(metadata.last_sign_in_timestamp)); LogMessage("* Creation timestamp %d", @@ -274,8 +275,8 @@ class AuthStateChangeCounter : public firebase::auth::AuthStateListener { virtual void OnAuthStateChanged(Auth* auth) { // NOLINT num_state_changes_++; - LogMessage("OnAuthStateChanged User %p (state changes %d)", - auth->current_user(), num_state_changes_); + LogMessage("OnAuthStateChanged User %s (state changes %d)", + auth->current_user().uid().c_str(), num_state_changes_); } void CompleteTest(const char* test_name, int expected_state_changes) { @@ -302,8 +303,8 @@ class IdTokenChangeCounter : public firebase::auth::IdTokenListener { virtual void OnIdTokenChanged(Auth* auth) { // NOLINT num_token_changes_++; - LogMessage("OnIdTokenChanged User %p (token changes %d)", - auth->current_user(), num_token_changes_); + LogMessage("OnIdTokenChanged User %s (token changes %d)", + auth->current_user().uid().c_str(), num_token_changes_); } void CompleteTest(const char* test_name, int token_changes) { @@ -331,7 +332,6 @@ class UserLogin { : auth_(auth), email_(email), password_(password), - user_(nullptr), log_errors_(true) {} explicit UserLogin(Auth* auth) : auth_(auth) { @@ -340,48 +340,48 @@ class UserLogin { } ~UserLogin() { - if (user_ != nullptr) { + if (user_.is_valid()) { log_errors_ = false; Delete(); } } void Register() { - Future register_test_account = + Future register_test_account = auth_->CreateUserWithEmailAndPassword(email(), password()); WaitForSignInFuture(register_test_account, "CreateUserWithEmailAndPassword() to create temp user", kAuthErrorNone, auth_); - user_ = register_test_account.result() ? *register_test_account.result() - : nullptr; + user_ = register_test_account.result() ? register_test_account.result()->user + : User(); } void Login() { Credential email_cred = EmailAuthProvider::GetCredential(email(), password()); - Future sign_in_cred = auth_->SignInWithCredential(email_cred); + Future sign_in_cred = auth_->SignInWithCredential(email_cred); WaitForSignInFuture(sign_in_cred, "Auth::SignInWithCredential() for UserLogin", kAuthErrorNone, auth_); } void Delete() { - if (user_ != nullptr) { - Future delete_future = user_->Delete(); + if (user_.is_valid()) { + Future delete_future = user_.Delete(); if (delete_future.status() == ::firebase::kFutureStatusInvalid) { Login(); - delete_future = user_->Delete(); + delete_future = user_.Delete(); } WaitForFuture(delete_future, "User::Delete()", kAuthErrorNone, log_errors_); } - user_ = nullptr; + user_ = User(); } const char* email() const { return email_.c_str(); } const char* password() const { return password_.c_str(); } - User* user() const { return user_; } + User user() const { return user_; } void set_email(const char* email) { email_ = email; } void set_password(const char* password) { password_ = password; } @@ -389,7 +389,7 @@ class UserLogin { Auth* auth_; std::string email_; std::string password_; - User* user_; + User user_; bool log_errors_; }; @@ -401,7 +401,7 @@ class PhoneListener : public PhoneAuthProvider::Listener { num_calls_on_code_sent_(0), num_calls_on_code_auto_retrieval_time_out_(0) {} - void OnVerificationCompleted(Credential /*credential*/) override { + void OnVerificationCompleted(PhoneAuthCredential /*credential*/) override { LogMessage("PhoneListener: successful automatic verification."); num_calls_on_verification_complete_++; } @@ -493,13 +493,13 @@ extern "C" int common_main(int argc, const char* argv[]) { // It's possible for current_user() to be non-null if the previous run // left us in a signed-in state. - if (auth->current_user() == nullptr) { + if (!auth->current_user().is_valid()) { LogMessage("No user signed in at creation time."); } else { LogMessage( "Current user uid(%s) name(%s) already signed in, so signing them out.", - auth->current_user()->uid().c_str(), - auth->current_user()->display_name().c_str()); + auth->current_user().uid().c_str(), + auth->current_user().display_name().c_str()); auth->SignOut(); } @@ -523,7 +523,7 @@ extern "C" int common_main(int argc, const char* argv[]) { if (kTestCustomEmail) { // Test Auth::SignInWithEmailAndPassword(). // Sign in with email and password that have already been registered. - Future sign_in_future = + Future sign_in_future = auth->SignInWithEmailAndPassword(kCustomEmail, kCustomPassword); WaitForSignInFuture(sign_in_future, "Auth::SignInWithEmailAndPassword() existing " @@ -532,11 +532,11 @@ extern "C" int common_main(int argc, const char* argv[]) { // Test SignOut() after signed in with email and password. if (sign_in_future.status() == ::firebase::kFutureStatusComplete) { auth->SignOut(); - if (auth->current_user() != nullptr) { + if (auth->current_user().is_valid()) { LogMessage( - "ERROR: current_user() returning %x instead of nullptr after " + "ERROR: current_user() returning %s instead of nullptr after " "SignOut()", - auth->current_user()); + auth->current_user().uid().c_str()); } } } @@ -560,7 +560,7 @@ extern "C" int common_main(int argc, const char* argv[]) { token_counter.CompleteTest("SignOut() when already signed-out", 0); // Test notification on SignIn(). - Future sign_in_future = auth->SignInAnonymously(); + Future sign_in_future = auth->SignInAnonymously(); WaitForSignInFuture(sign_in_future, "Auth::SignInAnonymously()", kAuthErrorNone, auth); // Notified when the user is about to change and after the user has @@ -569,21 +569,21 @@ extern "C" int common_main(int argc, const char* argv[]) { token_counter.CompleteTest("SignInAnonymously()", 1, 5); // Refresh the token. - if (auth->current_user() != nullptr) { - Future token_future = auth->current_user()->GetToken(true); + if (auth->current_user().is_valid()) { + Future token_future = auth->current_user().GetToken(true); WaitForFuture(token_future, "GetToken()", kAuthErrorNone); counter.CompleteTest("GetToken()", 0); token_counter.CompleteTest("GetToken()", 1); } // Test notification on SignOut(), when signed-in. - LogMessage("Current user %p", auth->current_user()); // DEBUG + LogMessage("Current user %s", auth->current_user().uid().c_str()); // DEBUG auth->SignOut(); // Wait for the sign out to complete. WaitForSignOut(auth); counter.CompleteTest("SignOut()", 1); token_counter.CompleteTest("SignOut()", 1); - LogMessage("Current user %p", auth->current_user()); // DEBUG + LogMessage("Current user %s", auth->current_user().uid().c_str()); // DEBUG auth->RemoveAuthStateListener(&counter); auth->RemoveIdTokenListener(&token_counter); @@ -602,8 +602,10 @@ extern "C" int common_main(int argc, const char* argv[]) { "Phone Number", "Please enter your phone number", "+12345678900"); PhoneListener listener; PhoneAuthProvider& phone_provider = PhoneAuthProvider::GetInstance(auth); - phone_provider.VerifyPhoneNumber(phone_number.c_str(), kPhoneAuthTimeoutMs, - nullptr, &listener); + PhoneAuthOptions options; + options.phone_number = phone_number; + options.timeout_milliseconds = kPhoneAuthTimeoutMs; + phone_provider.VerifyPhoneNumber(options, &listener); // Wait for OnCodeSent() callback. int wait_ms = 0; @@ -635,21 +637,21 @@ extern "C" int common_main(int argc, const char* argv[]) { LogMessage("."); } if (listener.num_calls_on_code_auto_retrieval_time_out() > 0) { - const Credential phone_credential = phone_provider.GetCredential( + const PhoneAuthCredential phone_credential = phone_provider.GetCredential( listener.verification_id().c_str(), verification_code.c_str()); - Future phone_future = + Futurephone_future = auth->SignInWithCredential(phone_credential); WaitForSignInFuture(phone_future, "Auth::SignInWithCredential() phone credential", kAuthErrorNone, auth); if (phone_future.error() == kAuthErrorNone) { - User* user = *phone_future.result(); - Future update_future = - user->UpdatePhoneNumberCredential(phone_credential); + User user = *phone_future.result(); + Future update_future = + user.UpdatePhoneNumberCredential(phone_credential); WaitForSignInFuture( update_future, - "user->UpdatePhoneNumberCredential(phone_credential)", + "user.UpdatePhoneNumberCredential(phone_credential)", kAuthErrorNone, auth); } @@ -664,12 +666,12 @@ extern "C" int common_main(int argc, const char* argv[]) { { UserLogin user_login(auth); // Generate a random name/password user_login.Register(); - if (!user_login.user()) { + if (!user_login.user().is_valid()) { LogMessage("ERROR: Could not register new user."); } else { // Test Auth::SignInAnonymously(). { - Future sign_in_future = auth->SignInAnonymously(); + Future sign_in_future = auth->SignInAnonymously(); WaitForSignInFuture(sign_in_future, "Auth::SignInAnonymously()", kAuthErrorNone, auth); ExpectTrue("SignInAnonymouslyLastResult matches returned Future", @@ -678,11 +680,11 @@ extern "C" int common_main(int argc, const char* argv[]) { // Test SignOut() after signed in anonymously. if (sign_in_future.status() == ::firebase::kFutureStatusComplete) { auth->SignOut(); - if (auth->current_user() != nullptr) { + if (auth->current_user().is_valid()) { LogMessage( - "ERROR: current_user() returning %x instead of nullptr after " + "ERROR: current_user() returning valid user %s instead of invalid user after " "SignOut()", - auth->current_user()); + auth->current_user().uid().c_str()); } } } @@ -711,7 +713,7 @@ extern "C" int common_main(int argc, const char* argv[]) { // Test Auth::SignInWithEmailAndPassword(). // Sign in with email and password that have already been registered. { - Future sign_in_future = auth->SignInWithEmailAndPassword( + Future sign_in_future = auth->SignInWithEmailAndPassword( user_login.email(), user_login.password()); WaitForSignInFuture( sign_in_future, @@ -724,39 +726,38 @@ extern "C" int common_main(int argc, const char* argv[]) { // Test SignOut() after signed in with email and password. if (sign_in_future.status() == ::firebase::kFutureStatusComplete) { auth->SignOut(); - if (auth->current_user() != nullptr) { + if (auth->current_user().is_valid()) { LogMessage( - "ERROR: current_user() returning %x instead of nullptr after " - "SignOut()", - auth->current_user()); + "ERROR: current_user() returning valid user %s instead of invalid user after " + "SignOut()", auth->current_user().uid().c_str()); } } } // Test User::UpdateUserProfile { - Future sign_in_future = auth->SignInWithEmailAndPassword( + Future sign_in_future = auth->SignInWithEmailAndPassword( user_login.email(), user_login.password()); WaitForSignInFuture( sign_in_future, "Auth::SignInWithEmailAndPassword() existing email and password", kAuthErrorNone, auth); if (sign_in_future.error() == kAuthErrorNone) { - User* user = *sign_in_future.result(); + User user = sign_in_future.result()->user; const char* kDisplayName = "Hello World"; const char* kPhotoUrl = "http://test.com/image.jpg"; User::UserProfile user_profile; user_profile.display_name = kDisplayName; user_profile.photo_url = kPhotoUrl; Future update_profile_future = - user->UpdateUserProfile(user_profile); + user.UpdateUserProfile(user_profile); WaitForFuture(update_profile_future, "User::UpdateUserProfile", kAuthErrorNone); if (update_profile_future.error() == kAuthErrorNone) { ExpectStringsEqual("User::display_name", kDisplayName, - user->display_name().c_str()); + user.display_name().c_str()); ExpectStringsEqual("User::photo_url", kPhotoUrl, - user->photo_url().c_str()); + user.photo_url().c_str()); } } } @@ -764,33 +765,33 @@ extern "C" int common_main(int argc, const char* argv[]) { // Sign in anonymously, link an email credential, reauthenticate with the // credential, unlink the credential and finally sign out. { - Future sign_in_anonymously_future = auth->SignInAnonymously(); + Future sign_in_anonymously_future = auth->SignInAnonymously(); WaitForSignInFuture(sign_in_anonymously_future, "Auth::SignInAnonymously", kAuthErrorNone, auth); if (sign_in_anonymously_future.error() == kAuthErrorNone) { - User* user = *sign_in_anonymously_future.result(); + User user = sign_in_anonymously_future.result()->user; std::string email = CreateNewEmail(); Credential credential = EmailAuthProvider::GetCredential(email.c_str(), kTestPassword); // Link with an email / password credential. - Future link_future = - user->LinkAndRetrieveDataWithCredential(credential); + Future link_future = + user.LinkWithCredential(credential); WaitForSignInFuture(link_future, "User::LinkAndRetrieveDataWithCredential", kAuthErrorNone, auth); if (link_future.error() == kAuthErrorNone) { - LogSignInResult(*link_future.result()); - Future reauth_future = - user->ReauthenticateAndRetrieveData(credential); + LogAuthResult(*link_future.result()); + Future reauth_future = + user.ReauthenticateAndRetrieveData(credential); WaitForSignInFuture(reauth_future, "User::ReauthenticateAndRetrieveData", kAuthErrorNone, auth); if (reauth_future.error() == kAuthErrorNone) { - LogSignInResult(*reauth_future.result()); + LogAuthResult(*reauth_future.result()); } // Unlink email / password from credential. - Future unlink_future = - user->Unlink(credential.provider().c_str()); + Future unlink_future = + user.Unlink(credential.provider().c_str()); WaitForSignInFuture(unlink_future, "User::Unlink", kAuthErrorNone, auth); } @@ -800,7 +801,7 @@ extern "C" int common_main(int argc, const char* argv[]) { // Sign in user with bad email. Should fail. { - Future sign_in_future_bad_email = + Future sign_in_future_bad_email = auth->SignInWithEmailAndPassword(kTestEmailBad, kTestPassword); WaitForSignInFuture(sign_in_future_bad_email, "Auth::SignInWithEmailAndPassword() bad email", @@ -809,7 +810,7 @@ extern "C" int common_main(int argc, const char* argv[]) { // Sign in user with correct email but bad password. Should fail. { - Future sign_in_future_bad_password = + Future sign_in_future_bad_password = auth->SignInWithEmailAndPassword(user_login.email(), kTestPasswordBad); WaitForSignInFuture(sign_in_future_bad_password, @@ -819,7 +820,7 @@ extern "C" int common_main(int argc, const char* argv[]) { // Try to create with existing email. Should fail. { - Future create_future_bad = auth->CreateUserWithEmailAndPassword( + Future create_future_bad = auth->CreateUserWithEmailAndPassword( user_login.email(), user_login.password()); WaitForSignInFuture( create_future_bad, @@ -836,13 +837,11 @@ extern "C" int common_main(int argc, const char* argv[]) { { Credential email_cred_ok = EmailAuthProvider::GetCredential( user_login.email(), user_login.password()); - Future sign_in_cred_ok = + Futuresign_in_cred_ok = auth->SignInWithCredential(email_cred_ok); WaitForSignInFuture(sign_in_cred_ok, "Auth::SignInWithCredential() existing email", kAuthErrorNone, auth); - ExpectTrue("SignInWithCredentialLastResult matches returned Future", - sign_in_cred_ok == auth->SignInWithCredentialLastResult()); } // Test Auth::SignInAndRetrieveDataWithCredential using email & password. @@ -850,7 +849,7 @@ extern "C" int common_main(int argc, const char* argv[]) { { Credential email_cred = EmailAuthProvider::GetCredential( user_login.email(), user_login.password()); - Future sign_in_future = + Future sign_in_future = auth->SignInAndRetrieveDataWithCredential(email_cred); WaitForSignInFuture(sign_in_future, "Auth::SignInAndRetrieveDataWithCredential " @@ -862,10 +861,10 @@ extern "C" int common_main(int argc, const char* argv[]) { sign_in_future == auth->SignInAndRetrieveDataWithCredentialLastResult()); if (sign_in_future.error() == kAuthErrorNone) { - const SignInResult* sign_in_result = sign_in_future.result(); - if (sign_in_result != nullptr && sign_in_result->user) { + const AuthResult* sign_in_result = sign_in_future.result(); + if (sign_in_result != nullptr && sign_in_result->user.is_valid()) { LogMessage("SignInAndRetrieveDataWithCredential"); - LogSignInResult(*sign_in_result); + LogAuthResult(*sign_in_result); } else { LogMessage( "ERROR: SignInAndRetrieveDataWithCredential returned no " @@ -878,7 +877,7 @@ extern "C" int common_main(int argc, const char* argv[]) { { Credential facebook_cred_bad = FacebookAuthProvider::GetCredential(kTestAccessTokenBad); - Future facebook_bad = + Futurefacebook_bad = auth->SignInWithCredential(facebook_cred_bad); WaitForSignInFuture( facebook_bad, @@ -890,7 +889,7 @@ extern "C" int common_main(int argc, const char* argv[]) { { Credential git_hub_cred_bad = GitHubAuthProvider::GetCredential(kTestAccessTokenBad); - Future git_hub_bad = + Futuregit_hub_bad = auth->SignInWithCredential(git_hub_cred_bad); WaitForSignInFuture( git_hub_bad, "Auth::SignInWithCredential() bad GitHub credentials", @@ -901,7 +900,7 @@ extern "C" int common_main(int argc, const char* argv[]) { { Credential google_cred_bad = GoogleAuthProvider::GetCredential( kTestIdTokenBad, kTestAccessTokenBad); - Future google_bad = auth->SignInWithCredential(google_cred_bad); + Futuregoogle_bad = auth->SignInWithCredential(google_cred_bad); WaitForSignInFuture( google_bad, "Auth::SignInWithCredential() bad Google credentials", kAuthErrorInvalidCredential, auth); @@ -911,7 +910,7 @@ extern "C" int common_main(int argc, const char* argv[]) { { Credential google_cred_bad = GoogleAuthProvider::GetCredential(kTestIdTokenBad, nullptr); - Future google_bad = auth->SignInWithCredential(google_cred_bad); + Futuregoogle_bad = auth->SignInWithCredential(google_cred_bad); WaitForSignInFuture( google_bad, "Auth::SignInWithCredential() bad Google credentials", kAuthErrorInvalidCredential, auth); @@ -922,7 +921,7 @@ extern "C" int common_main(int argc, const char* argv[]) { { Credential play_games_cred_bad = PlayGamesAuthProvider::GetCredential(kTestServerAuthCodeBad); - Future play_games_bad = + Futureplay_games_bad = auth->SignInWithCredential(play_games_cred_bad); WaitForSignInFuture( play_games_bad, @@ -959,7 +958,7 @@ extern "C" int common_main(int argc, const char* argv[]) { if (gc_credential_ptr == nullptr) { LogMessage("Failed to retrieve Game Center credential."); } else { - Future game_center_user = + Futuregame_center_user = auth->SignInWithCredential(*gc_credential_ptr); WaitForFuture(game_center_user, "Auth::SignInWithCredential() test Game Center " @@ -974,7 +973,7 @@ extern "C" int common_main(int argc, const char* argv[]) { { Credential twitter_cred_bad = TwitterAuthProvider::GetCredential( kTestIdTokenBad, kTestAccessTokenBad); - Future twitter_bad = + Futuretwitter_bad = auth->SignInWithCredential(twitter_cred_bad); WaitForSignInFuture( twitter_bad, "Auth::SignInWithCredential() bad Twitter credentials", @@ -999,7 +998,7 @@ extern "C" int common_main(int argc, const char* argv[]) { { Credential oauth_cred_bad = OAuthProvider::GetCredential( kTestIdProviderIdBad, kTestIdTokenBad, kTestAccessTokenBad); - Future oauth_bad = auth->SignInWithCredential(oauth_cred_bad); + Futureoauth_bad = auth->SignInWithCredential(oauth_cred_bad); WaitForSignInFuture( oauth_bad, "Auth::SignInWithCredential() bad OAuth credentials", kAuthErrorFailure, auth); @@ -1010,7 +1009,7 @@ extern "C" int common_main(int argc, const char* argv[]) { Credential oauth_cred_bad = OAuthProvider::GetCredential(kTestIdProviderIdBad, kTestIdTokenBad, kTestNonceBad, kTestAccessTokenBad); - Future oauth_bad = auth->SignInWithCredential(oauth_cred_bad); + Futureoauth_bad = auth->SignInWithCredential(oauth_cred_bad); WaitForSignInFuture( oauth_bad, "Auth::SignInWithCredential() bad OAuth credentials", kAuthErrorFailure, auth); @@ -1042,48 +1041,48 @@ extern "C" int common_main(int argc, const char* argv[]) { // --- User tests ------------------------------------------------------------ // Test anonymous user info strings. { - Future anon_sign_in_for_user = auth->SignInAnonymously(); + Future anon_sign_in_for_user = auth->SignInAnonymously(); WaitForSignInFuture(anon_sign_in_for_user, "Auth::SignInAnonymously() for User", kAuthErrorNone, auth); if (anon_sign_in_for_user.status() == ::firebase::kFutureStatusComplete) { - User* anonymous_user = anon_sign_in_for_user.result() - ? *anon_sign_in_for_user.result() - : nullptr; - if (anonymous_user != nullptr) { - LogMessage("Anonymous uid is %s", anonymous_user->uid().c_str()); + User anonymous_user = anon_sign_in_for_user.result() + ? anon_sign_in_for_user.result()->user + : User(); + if (anonymous_user.is_valid()) { + LogMessage("Anonymous uid is %s", anonymous_user.uid().c_str()); ExpectStringsEqual("Anonymous user email", "", - anonymous_user->email().c_str()); + anonymous_user.email().c_str()); ExpectStringsEqual("Anonymous user display_name", "", - anonymous_user->display_name().c_str()); + anonymous_user.display_name().c_str()); ExpectStringsEqual("Anonymous user photo_url", "", - anonymous_user->photo_url().c_str()); + anonymous_user.photo_url().c_str()); ExpectStringsEqual("Anonymous user provider_id", kFirebaseProviderId, - anonymous_user->provider_id().c_str()); + anonymous_user.provider_id().c_str()); ExpectTrue("Anonymous user is_anonymous()", - anonymous_user->is_anonymous()); + anonymous_user.is_anonymous()); ExpectFalse("Anonymous user is_email_verified()", - anonymous_user->is_email_verified()); + anonymous_user.is_email_verified()); ExpectTrue("Anonymous user metadata().last_sign_in_timestamp != 0", - anonymous_user->metadata().last_sign_in_timestamp != 0); + anonymous_user.metadata().last_sign_in_timestamp != 0); ExpectTrue("Anonymous user metadata().creation_timestamp != 0", - anonymous_user->metadata().creation_timestamp != 0); + anonymous_user.metadata().creation_timestamp != 0); // Test User::LinkWithCredential(), linking with email & password. const std::string newer_email = CreateNewEmail(); Credential user_cred = EmailAuthProvider::GetCredential( newer_email.c_str(), kTestPassword); { - Future link_future = - anonymous_user->LinkWithCredential(user_cred); + Future link_future = + anonymous_user.LinkWithCredential(user_cred); WaitForSignInFuture(link_future, "User::LinkWithCredential()", kAuthErrorNone, auth); } // Test User::LinkWithCredential(), linking with same email & password. { - Future link_future = - anonymous_user->LinkWithCredential(user_cred); + Future link_future = + anonymous_user.LinkWithCredential(user_cred); WaitForSignInFuture(link_future, "User::LinkWithCredential() again", ::firebase::auth::kAuthErrorProviderAlreadyLinked, auth); @@ -1092,14 +1091,14 @@ extern "C" int common_main(int argc, const char* argv[]) { // Test User::LinkWithCredential(), linking with bad credential. // Call should fail and Auth's current user should be maintained. { - const User* pre_link_user = auth->current_user(); + const User pre_link_user = auth->current_user(); ExpectTrue("Test precondition requires active user", - pre_link_user != nullptr); + pre_link_user.is_valid()); Credential twitter_cred_bad = TwitterAuthProvider::GetCredential( kTestIdTokenBad, kTestAccessTokenBad); - Future link_bad_future = - anonymous_user->LinkWithCredential(twitter_cred_bad); + Future link_bad_future = + anonymous_user.LinkWithCredential(twitter_cred_bad); WaitForFuture(link_bad_future, "User::LinkWithCredential() with bad credential", kAuthErrorInvalidCredential); @@ -1110,12 +1109,12 @@ extern "C" int common_main(int argc, const char* argv[]) { // Test Auth::SignInWithCredential(), signing in with bad credential. // Call should fail, and Auth's current user should be maintained. { - const User* pre_signin_user = auth->current_user(); + const User pre_signin_user = auth->current_user(); ExpectTrue("Test precondition requires active user", - pre_signin_user != nullptr); + pre_signin_user.is_valid()); Credential twitter_cred_bad = TwitterAuthProvider::GetCredential( kTestIdTokenBad, kTestAccessTokenBad); - Future signin_bad_future = + Futuresignin_bad_future = auth->SignInWithCredential(twitter_cred_bad); WaitForFuture(signin_bad_future, "Auth::SignInWithCredential() with bad credential", @@ -1127,41 +1126,41 @@ extern "C" int common_main(int argc, const char* argv[]) { UserLogin user_login(auth); user_login.Register(); - if (!user_login.user()) { + if (!user_login.user().is_valid()) { LogMessage("Error - Could not create new user."); } else { // Test email user info strings. - Future email_sign_in_for_user = + Future email_sign_in_for_user = auth->SignInWithEmailAndPassword(user_login.email(), user_login.password()); WaitForSignInFuture(email_sign_in_for_user, "Auth::SignInWithEmailAndPassword() for User", kAuthErrorNone, auth); - User* email_user = email_sign_in_for_user.result() - ? *email_sign_in_for_user.result() - : nullptr; - if (email_user != nullptr) { - LogMessage("Email uid is %s", email_user->uid().c_str()); + User email_user = email_sign_in_for_user.result() + ? email_sign_in_for_user.result()->user + : User(); + if (email_user.is_valid()) { + LogMessage("Email uid is %s", email_user.uid().c_str()); ExpectStringsEqual("Email user email", user_login.email(), - email_user->email().c_str()); + email_user.email().c_str()); ExpectStringsEqual("Email user display_name", "", - email_user->display_name().c_str()); + email_user.display_name().c_str()); ExpectStringsEqual("Email user photo_url", "", - email_user->photo_url().c_str()); + email_user.photo_url().c_str()); ExpectStringsEqual("Email user provider_id", kFirebaseProviderId, - email_user->provider_id().c_str()); + email_user.provider_id().c_str()); ExpectFalse("Email user is_anonymous()", - email_user->is_anonymous()); + email_user.is_anonymous()); ExpectFalse("Email user is_email_verified()", - email_user->is_email_verified()); + email_user.is_email_verified()); ExpectTrue("Email user metadata().last_sign_in_timestamp != 0", - email_user->metadata().last_sign_in_timestamp != 0); + email_user.metadata().last_sign_in_timestamp != 0); ExpectTrue("Email user metadata().creation_timestamp != 0", - email_user->metadata().creation_timestamp != 0); + email_user.metadata().creation_timestamp != 0); // Test User::GetToken(). // with force_refresh = false. - Future token_no_refresh = email_user->GetToken(false); + Future token_no_refresh = email_user.GetToken(false); WaitForFuture(token_no_refresh, "User::GetToken(false)", kAuthErrorNone); LogMessage("User::GetToken(false) = %s", @@ -1171,7 +1170,7 @@ extern "C" int common_main(int argc, const char* argv[]) { // with force_refresh = true. Future token_force_refresh = - email_user->GetToken(true); + email_user.GetToken(true); WaitForFuture(token_force_refresh, "User::GetToken(true)", kAuthErrorNone); LogMessage("User::GetToken(true) = %s", @@ -1180,59 +1179,59 @@ extern "C" int common_main(int argc, const char* argv[]) { : ""); // Test Reload(). - Future reload_future = email_user->Reload(); + Future reload_future = email_user.Reload(); WaitForFuture(reload_future, "User::Reload()", kAuthErrorNone); // Test User::Unlink(). - Future unlink_future = email_user->Unlink("firebase"); + Future unlink_future = email_user.Unlink("firebase"); WaitForSignInFuture(unlink_future, "User::Unlink()", ::firebase::auth::kAuthErrorNoSuchProvider, auth); // Sign in again if user is now invalid. - if (auth->current_user() == nullptr) { - Future email_sign_in_again = + if (!auth->current_user().is_valid()) { + Future email_sign_in_again = auth->SignInWithEmailAndPassword(user_login.email(), user_login.password()); WaitForSignInFuture(email_sign_in_again, "Auth::SignInWithEmailAndPassword() again", kAuthErrorNone, auth); email_user = email_sign_in_again.result() - ? *email_sign_in_again.result() - : nullptr; + ? email_sign_in_again.result()->user + : User(); } } - if (email_user != nullptr) { + if (email_user.is_valid()) { // Test User::provider_data(). - const std::vector& provider_data = - email_user->provider_data(); + const std::vector provider_data = + email_user.provider_data(); LogMessage("User::provider_data() returned %d interface%s", provider_data.size(), provider_data.size() == 1 ? "" : "s"); for (size_t i = 0; i < provider_data.size(); ++i) { - const UserInfoInterface* user_info = provider_data[i]; + const UserInfoInterface user_info = provider_data[i]; LogMessage( " UID() = %s\n" " Email() = %s\n" " DisplayName() = %s\n" " PhotoUrl() = %s\n" " ProviderId() = %s", - user_info->uid().c_str(), user_info->email().c_str(), - user_info->display_name().c_str(), - user_info->photo_url().c_str(), - user_info->provider_id().c_str()); + user_info.uid().c_str(), user_info.email().c_str(), + user_info.display_name().c_str(), + user_info.photo_url().c_str(), + user_info.provider_id().c_str()); } // Test User::UpdateEmail(). const std::string newest_email = CreateNewEmail(); Future update_email_future = - email_user->UpdateEmail(newest_email.c_str()); + email_user.UpdateEmail(newest_email.c_str()); WaitForFuture(update_email_future, "User::UpdateEmail()", kAuthErrorNone); // Test User::UpdatePassword(). Future update_password_future = - email_user->UpdatePassword(kTestPasswordUpdated); + email_user.UpdatePassword(kTestPasswordUpdated); WaitForFuture(update_password_future, "User::UpdatePassword()", kAuthErrorNone); @@ -1240,13 +1239,13 @@ extern "C" int common_main(int argc, const char* argv[]) { Credential email_cred_reauth = EmailAuthProvider::GetCredential( newest_email.c_str(), kTestPasswordUpdated); Future reauthenticate_future = - email_user->Reauthenticate(email_cred_reauth); + email_user.Reauthenticate(email_cred_reauth); WaitForFuture(reauthenticate_future, "User::Reauthenticate()", kAuthErrorNone); // Test User::SendEmailVerification(). Future send_email_verification_future = - email_user->SendEmailVerification(); + email_user.SendEmailVerification(); WaitForFuture(send_email_verification_future, "User::SendEmailVerification()", kAuthErrorNone); } @@ -1256,29 +1255,29 @@ extern "C" int common_main(int argc, const char* argv[]) { // Test User::Delete(). const std::string new_email_for_delete = CreateNewEmail(); - Future create_future_for_delete = + Future create_future_for_delete = auth->CreateUserWithEmailAndPassword(new_email_for_delete.c_str(), kTestPassword); WaitForSignInFuture( create_future_for_delete, "Auth::CreateUserWithEmailAndPassword() new email for delete", kAuthErrorNone, auth); - User* email_user_for_delete = create_future_for_delete.result() - ? *create_future_for_delete.result() - : nullptr; - if (email_user_for_delete != nullptr) { - Future delete_future = email_user_for_delete->Delete(); + User email_user_for_delete = create_future_for_delete.result() + ? create_future_for_delete.result()->user + : User(); + if (email_user_for_delete.is_valid()) { + Future delete_future = email_user_for_delete.Delete(); WaitForFuture(delete_future, "User::Delete()", kAuthErrorNone); } } { // We end with a login so that we can test if a second run will detect // that we're already logged-in. - Future sign_in_future = auth->SignInAnonymously(); + Future sign_in_future = auth->SignInAnonymously(); WaitForSignInFuture(sign_in_future, "Auth::SignInAnonymously() at end", kAuthErrorNone, auth); - LogMessage("Anonymous uid(%s)", auth->current_user()->uid().c_str()); + LogMessage("Anonymous uid(%s)", auth->current_user().uid().c_str()); } #ifdef INTERNAL_EXPERIMENTAL @@ -1305,13 +1304,13 @@ extern "C" int common_main(int argc, const char* argv[]) { firebase::auth::FederatedOAuthProvider provider; provider.SetProviderData(provider_data); LogMessage("invoking linkwithprovider"); - Future sign_in_future = - user_login.user()->LinkWithProvider(&provider); + Future sign_in_future = + user_login.user().LinkWithProvider(&provider); WaitForSignInFuture(sign_in_future, "LinkWithProvider", kAuthErrorNone, auth); if (sign_in_future.error() == kAuthErrorNone) { - const SignInResult* result_ptr = sign_in_future.result(); - LogMessage("user email %s", result_ptr->user->email().c_str()); + const AuthResult* result_ptr = sign_in_future.result(); + LogMessage("user email %s", result_ptr->user.email().c_str()); LogMessage("Additonal user info provider_id: %s", result_ptr->info.provider_id.c_str()); LogMessage("LinkWithProviderDone"); @@ -1330,12 +1329,12 @@ extern "C" int common_main(int argc, const char* argv[]) { firebase::auth::FederatedOAuthProvider provider; provider.SetProviderData(provider_data); LogMessage("SignInWithProvider SETUP COMPLETE"); - Future sign_in_future = auth->SignInWithProvider(&provider); + Future sign_in_future = auth->SignInWithProvider(&provider); WaitForSignInFuture(sign_in_future, "SignInWithProvider", kAuthErrorNone, auth); if (sign_in_future.error() == kAuthErrorNone && sign_in_future.result() != nullptr) { - LogSignInResult(*sign_in_future.result()); + LogAuthResult(*sign_in_future.result()); } } @@ -1351,13 +1350,13 @@ extern "C" int common_main(int argc, const char* argv[]) { firebase::auth::FederatedOAuthProvider provider; provider.SetProviderData(provider_data); - Future sign_in_future = - auth->current_user()->ReauthenticateWithProvider(&provider); + Future sign_in_future = + auth->current_user().ReauthenticateWithProvider(&provider); WaitForSignInFuture(sign_in_future, "ReauthenticateWithProvider", kAuthErrorNone, auth); if (sign_in_future.error() == kAuthErrorNone && sign_in_future.result() != nullptr) { - LogSignInResult(*sign_in_future.result()); + LogAuthResult(*sign_in_future.result()); } } } @@ -1365,7 +1364,7 @@ extern "C" int common_main(int argc, const char* argv[]) { // Clean up provider-linked user so we can run the test app again // and not get "user with that email already exists" errors. if (auth->current_user()) { - WaitForFuture(auth->current_user()->Delete(), "Delete User", + WaitForFuture(auth->current_user().Delete(), "Delete User", kAuthErrorNone, /*log_error=*/true); } diff --git a/auth/testapp/testapp.xcodeproj/project.pbxproj b/auth/testapp/testapp.xcodeproj/project.pbxproj index 6c325725..dbad8094 100644 --- a/auth/testapp/testapp.xcodeproj/project.pbxproj +++ b/auth/testapp/testapp.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 529227241C85FB7600C89379 /* ios_main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 529227221C85FB7600C89379 /* ios_main.mm */; }; 52B71EBB1C8600B600398745 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 52B71EBA1C8600B600398745 /* Images.xcassets */; }; D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; + D6FC32B32C06595E00E3E028 /* GameKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6FC32B22C06595E00E3E028 /* GameKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -30,6 +31,7 @@ 52B71EBA1C8600B600398745 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = testapp/Images.xcassets; sourceTree = ""; }; 52FD1FF81C85FFA000BC68E3 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = testapp/Info.plist; sourceTree = ""; }; D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + D6FC32B22C06595E00E3E028 /* GameKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameKit.framework; path = System/Library/Frameworks/GameKit.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -38,6 +40,7 @@ buildActionMask = 2147483647; files = ( 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + D6FC32B32C06595E00E3E028 /* GameKit.framework in Frameworks */, 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, ); @@ -46,6 +49,13 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 3E2FC86EB58D8B5977042124 /* Pods */ = { + isa = PBXGroup; + children = ( + ); + path = Pods; + sourceTree = ""; + }; 529226C91C85F68000C89379 = { isa = PBXGroup; children = ( @@ -56,6 +66,7 @@ 5292271D1C85FB5500C89379 /* src */, 529226D41C85F68000C89379 /* Frameworks */, 529226D31C85F68000C89379 /* Products */, + 3E2FC86EB58D8B5977042124 /* Pods */, ); sourceTree = ""; }; @@ -70,6 +81,7 @@ 529226D41C85F68000C89379 /* Frameworks */ = { isa = PBXGroup; children = ( + D6FC32B22C06595E00E3E028 /* GameKit.framework */, 529226D51C85F68000C89379 /* Foundation.framework */, 529226D71C85F68000C89379 /* CoreGraphics.framework */, 529226D91C85F68000C89379 /* UIKit.framework */, @@ -135,6 +147,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = 529226C91C85F68000C89379; @@ -208,7 +221,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -245,7 +258,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/database/testapp/AndroidManifest.xml b/database/testapp/AndroidManifest.xml index a087cd78..f94a8ca5 100644 --- a/database/testapp/AndroidManifest.xml +++ b/database/testapp/AndroidManifest.xml @@ -6,9 +6,10 @@ - + sign_in_future = + firebase::Future sign_in_future = auth->SignInAnonymously(); WaitForCompletion(sign_in_future, "SignInAnonymously"); if (sign_in_future.error() == firebase::auth::kAuthErrorNone) { diff --git a/database/testapp/testapp.xcodeproj/project.pbxproj b/database/testapp/testapp.xcodeproj/project.pbxproj index 6c325725..5769362c 100644 --- a/database/testapp/testapp.xcodeproj/project.pbxproj +++ b/database/testapp/testapp.xcodeproj/project.pbxproj @@ -208,7 +208,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -245,7 +245,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/dynamic_links/testapp/AndroidManifest.xml b/dynamic_links/testapp/AndroidManifest.xml index 62212dc1..2aa60f2d 100644 --- a/dynamic_links/testapp/AndroidManifest.xml +++ b/dynamic_links/testapp/AndroidManifest.xml @@ -6,9 +6,10 @@ - + [!IMPORTANT] +> Firebase Dynamic Links is **deprecated** and should not be used in new projects. The service will shut down on August 25, 2025. +> +> Please see our [Dynamic Links Deprecation FAQ documentation](https://firebase.google.com/support/dynamic-links-faq) for more guidance. + The Firebase Dynamic Links Quickstart demonstrates logging a range of different events using the Firebase Dynamic Links C++ SDK. The application has no user interface and simply logs actions it's performing to the console. diff --git a/dynamic_links/testapp/testapp.xcodeproj/project.pbxproj b/dynamic_links/testapp/testapp.xcodeproj/project.pbxproj index 6c325725..5769362c 100644 --- a/dynamic_links/testapp/testapp.xcodeproj/project.pbxproj +++ b/dynamic_links/testapp/testapp.xcodeproj/project.pbxproj @@ -208,7 +208,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -245,7 +245,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/firestore/testapp/AndroidManifest.xml b/firestore/testapp/AndroidManifest.xml index 9b97c8d6..b4f84579 100644 --- a/firestore/testapp/AndroidManifest.xml +++ b/firestore/testapp/AndroidManifest.xml @@ -6,10 +6,11 @@ - + SignInAnonymously(); Await(login_future, "Auth sign-in"); auto* login_result = login_future.result(); - if (login_result && *login_result) { - const firebase::auth::User* user = *login_result; + if (login_result) { + const firebase::auth::User user = login_result->user; LogMessage("Signed in as %s user, uid: %s, email: %s.\n", - user->is_anonymous() ? "an anonymous" : "a non-anonymous", - user->uid().c_str(), user->email().c_str()); + user.is_anonymous() ? "an anonymous" : "a non-anonymous", + user.uid().c_str(), user.email().c_str()); } else { LogMessage("ERROR: could not sign in"); } diff --git a/firestore/testapp/testapp.xcodeproj/project.pbxproj b/firestore/testapp/testapp.xcodeproj/project.pbxproj index 81ed36ca..ed7634b5 100644 --- a/firestore/testapp/testapp.xcodeproj/project.pbxproj +++ b/firestore/testapp/testapp.xcodeproj/project.pbxproj @@ -284,7 +284,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -321,7 +321,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/functions/testapp/AndroidManifest.xml b/functions/testapp/AndroidManifest.xml index 98dc4cef..0b38246d 100644 --- a/functions/testapp/AndroidManifest.xml +++ b/functions/testapp/AndroidManifest.xml @@ -6,9 +6,10 @@ - + { diff --git a/functions/testapp/gradle.properties b/functions/testapp/gradle.properties new file mode 100644 index 00000000..d7ba8f42 --- /dev/null +++ b/functions/testapp/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX = true diff --git a/functions/testapp/gradle/wrapper/gradle-wrapper.properties b/functions/testapp/gradle/wrapper/gradle-wrapper.properties index 35732b09..65340c1b 100644 --- a/functions/testapp/gradle/wrapper/gradle-wrapper.properties +++ b/functions/testapp/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/functions/testapp/src/common_main.cc b/functions/testapp/src/common_main.cc index 98277ccc..5e5e0019 100644 --- a/functions/testapp/src/common_main.cc +++ b/functions/testapp/src/common_main.cc @@ -97,7 +97,7 @@ extern "C" int common_main(int argc, const char* argv[]) { // Optionally, sign in using Auth before accessing Functions. { - firebase::Future sign_in_future = + firebase::Future sign_in_future = auth->SignInAnonymously(); WaitForCompletion(sign_in_future, "SignInAnonymously"); if (sign_in_future.error() == firebase::auth::kAuthErrorNone) { diff --git a/functions/testapp/testapp.xcodeproj/project.pbxproj b/functions/testapp/testapp.xcodeproj/project.pbxproj index 6c325725..5769362c 100644 --- a/functions/testapp/testapp.xcodeproj/project.pbxproj +++ b/functions/testapp/testapp.xcodeproj/project.pbxproj @@ -208,7 +208,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -245,7 +245,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/admob/testapp/AndroidManifest.xml b/gma/testapp/AndroidManifest.xml similarity index 80% rename from admob/testapp/AndroidManifest.xml rename to gma/testapp/AndroidManifest.xml index e62e6385..f443dd14 100644 --- a/admob/testapp/AndroidManifest.xml +++ b/gma/testapp/AndroidManifest.xml @@ -6,15 +6,16 @@ - + + android:value="ca-app-pub-3940256099942544~3347511713"/> + android:exported = "true" + android:screenOrientation="portrait" + android:configChanges="orientation|screenSize"> diff --git a/admob/testapp/CMakeLists.txt b/gma/testapp/CMakeLists.txt similarity index 98% rename from admob/testapp/CMakeLists.txt rename to gma/testapp/CMakeLists.txt index 3a7d02dd..2132660c 100644 --- a/admob/testapp/CMakeLists.txt +++ b/gma/testapp/CMakeLists.txt @@ -107,5 +107,5 @@ endif() # Add the Firebase libraries to the target using the function from the SDK. add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) # Note that firebase_app needs to be last in the list. -set(firebase_libs firebase_admob firebase_app) +set(firebase_libs firebase_gma firebase_app) target_link_libraries(${target_name} "${firebase_libs}" ${ADDITIONAL_LIBS}) diff --git a/admob/testapp/LICENSE b/gma/testapp/LICENSE similarity index 100% rename from admob/testapp/LICENSE rename to gma/testapp/LICENSE diff --git a/admob/testapp/LaunchScreen.storyboard b/gma/testapp/LaunchScreen.storyboard similarity index 100% rename from admob/testapp/LaunchScreen.storyboard rename to gma/testapp/LaunchScreen.storyboard diff --git a/gma/testapp/Podfile b/gma/testapp/Podfile new file mode 100644 index 00000000..4683028b --- /dev/null +++ b/gma/testapp/Podfile @@ -0,0 +1,8 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '13.0' +use_frameworks! +# GMA test application. +target 'testapp' do + pod 'Google-Mobile-Ads-SDK', '11.2.0' + pod 'Firebase/Analytics', '10.25.0' +end diff --git a/admob/testapp/build.gradle b/gma/testapp/build.gradle similarity index 69% rename from admob/testapp/build.gradle rename to gma/testapp/build.gradle index 4562ea56..4cf159a9 100644 --- a/admob/testapp/build.gradle +++ b/gma/testapp/build.gradle @@ -1,4 +1,5 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. + buildscript { repositories { mavenLocal() @@ -6,9 +7,8 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' - classpath 'com.google.gms:google-services:4.0.1' - } + classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.google.gms:google-services:4.0.1' } } allprojects { @@ -22,8 +22,14 @@ allprojects { apply plugin: 'com.android.application' android { - compileSdkVersion 28 - buildToolsVersion '28.0.3' + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + + compileSdkVersion 34 + ndkPath System.getenv('ANDROID_NDK_HOME') + buildToolsVersion '30.0.2' sourceSets { main { @@ -36,7 +42,7 @@ android { defaultConfig { applicationId 'com.google.android.admob.testapp' - minSdkVersion 16 + minSdkVersion 23 targetSdkVersion 28 versionCode 1 versionName '1.0' @@ -54,11 +60,20 @@ android { proguardFile file('proguard.pro') } } + packagingOptions { + pickFirst 'META-INF/**/coroutines.pro' + } + lintOptions { + abortOnError false + checkReleaseBuilds false + } } apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" firebaseCpp.dependencies { - admob + gma } apply plugin: 'com.google.gms.google-services' + +// com.google.gms.googleservices.GoogleServicesPlugin.config.disableVersionCheck = true diff --git a/gma/testapp/gradle.properties b/gma/testapp/gradle.properties new file mode 100644 index 00000000..d7ba8f42 --- /dev/null +++ b/gma/testapp/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX = true diff --git a/admob/testapp/gradle/wrapper/gradle-wrapper.jar b/gma/testapp/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from admob/testapp/gradle/wrapper/gradle-wrapper.jar rename to gma/testapp/gradle/wrapper/gradle-wrapper.jar diff --git a/admob/testapp/gradle/wrapper/gradle-wrapper.properties b/gma/testapp/gradle/wrapper/gradle-wrapper.properties similarity index 65% rename from admob/testapp/gradle/wrapper/gradle-wrapper.properties rename to gma/testapp/gradle/wrapper/gradle-wrapper.properties index 35732b09..65340c1b 100644 --- a/admob/testapp/gradle/wrapper/gradle-wrapper.properties +++ b/gma/testapp/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/admob/testapp/gradlew b/gma/testapp/gradlew similarity index 100% rename from admob/testapp/gradlew rename to gma/testapp/gradlew diff --git a/admob/testapp/gradlew.bat b/gma/testapp/gradlew.bat similarity index 100% rename from admob/testapp/gradlew.bat rename to gma/testapp/gradlew.bat diff --git a/admob/testapp/proguard.pro b/gma/testapp/proguard.pro similarity index 100% rename from admob/testapp/proguard.pro rename to gma/testapp/proguard.pro diff --git a/admob/testapp/readme.md b/gma/testapp/readme.md similarity index 85% rename from admob/testapp/readme.md rename to gma/testapp/readme.md index e8f45f80..4143cb7e 100644 --- a/admob/testapp/readme.md +++ b/gma/testapp/readme.md @@ -1,15 +1,15 @@ -Firebase AdMob Quickstart +Firebase GMA Quickstart ============================== -The Firebase AdMob Test Application (testapp) demonstrates loading and showing -banners and interstitials using the Firebase AdMob C++ SDK. The application -has no user interface and simply logs actions it's performing to the console -while displaying the ads. +The Firebase Google Mobile Ads Test Application (testapp) demonstrates +loading and showing AdMob-served banners, interstitials and rewarded ads +using the Firebase GMA C++ SDK. The application has no user interface and +simply logs actions it's performing to the console while displaying the ads. Introduction ------------ -- [Read more about Firebase AdMob](https://firebase.google.com/docs/admob) +- [Read more about Firebase GMA](https://firebase.google.com/docs/gma) Getting Started --------------- @@ -41,7 +41,7 @@ Getting Started and unzip it to a directory of your choice. - Add the following frameworks from the Firebase C++ SDK to the project: - frameworks/ios/universal/firebase.framework - - frameworks/ios/universal/firebase_admob.framework + - frameworks/ios/universal/firebase_gma.framework - You will need to either, 1. Check "Copy items if needed" when adding the frameworks, or 2. Add the framework path in "Framework Search Paths" @@ -55,16 +55,13 @@ Getting Started the build settings. Scroll down to "Search Paths", and add your path to "Framework Search Paths". - Update the AdMob App ID: - - In the `src/common_main.cc`, update `kAdMobAppID` with the app ID for - your iOS app, replacing 'YOUR_IOS_ADMOB_APP_ID'. - In the `testapp/Info.plist`, update `GADApplicationIdentifier` with the - same app ID, replacing 'YOUR_IOS_ADMOB_APP_ID'. + app ID for your iOS app, replacing 'YOUR_IOS_ADMOB_APP_ID'. - For more information, see [Update your Info.plist](https://developers.google.com/admob/ios/quick-start#manual_download) - In Xcode, build & run the sample on an iOS device or simulator. - - The testapp displays a banner ad and an interstitial ad. You can dismiss - the interstitial ad to see the banner ad. - - Afterwards, the testapp will display a Rewarded Video test ad. + - The testapp displays a banner ad, an interstitial ad and a rewarded ad. You must + dismiss each ad to see the next. - The output of the app can be viewed onscreen or via the console. To view the console in Xcode, select "View --> Debug Area --> Activate Console" from the menu. @@ -112,25 +109,26 @@ Getting Started - From the Android Studio launch menu, "Open an existing Android Studio project", and select `build.gradle`. - Install the SDK Platforms that Android Studio reports missing. - - Update the AdMob App ID: - - In the `src/common_main.cc`, update `kAdMobAppID` with the app ID for - your Android app, replacing 'YOUR_ANDROID_ADMOB_APP_ID'. + - Update the GMA App ID: - In the `AndroidManifest.xml`, update `com.google.android.gms.ads.APPLICATION_ID` with the same app ID, replacing 'YOUR_ANDROID_ADMOB_APP_ID'. - For more information, see [Update your AndroidManifest.xml](https://developers.google.com/admob/android/quick-start#update_your_androidmanifestxml) - Build the testapp and run it on an Android device or emulator. - - The testapp will initialize AdMob, then load and display a test banner and - a test interstitial. + - The testapp will initialize the GMA SDK, then load and display a test + banner ad, interstitial ad and rewarded ad. - Tapping on an ad to verify the clickthrough process is possible, and the - interstitial will wait to be closed by the user. - - Afterwards, the testapp will display a Rewarded Video test ad. + test app will wait for each ad to be dismissed. - While this is happening, information from the device log will be written to an onscreen TextView. - Logcat can also be used as normal. ### Desktop + - Note: the testapp has no user interface, but the output can be viewed via + the console. The GMA SDK uses a stubbed implementation on desktop, so + functionality is not expected, and the app will end waiting for the interstitial + ad to be dismissed. - Register your app with Firebase. - Create a new app on the [Firebase console](https://firebase.google.com/console/), following the above instructions for Android or iOS. @@ -170,9 +168,6 @@ Getting Started ./desktop_testapp ``` Note that the executable might be under another directory, such as Debug. - - The testapp has no user interface, but the output can be viewed via the - console. Note that Admob uses a stubbed implementation on desktop, - so functionality is not expected. Support ------- @@ -182,7 +177,7 @@ Support License ------- -Copyright 2016 Google, Inc. +Copyright 2022 Google, Inc. Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for diff --git a/admob/testapp/res/values/strings.xml b/gma/testapp/res/values/strings.xml similarity index 53% rename from admob/testapp/res/values/strings.xml rename to gma/testapp/res/values/strings.xml index 8589bd2c..a9a313a3 100644 --- a/admob/testapp/res/values/strings.xml +++ b/gma/testapp/res/values/strings.xml @@ -1,4 +1,4 @@ - Firebase AdMob Test + Firebase GMA Test diff --git a/admob/testapp/settings.gradle b/gma/testapp/settings.gradle similarity index 100% rename from admob/testapp/settings.gradle rename to gma/testapp/settings.gradle diff --git a/admob/testapp/src/android/android_main.cc b/gma/testapp/src/android/android_main.cc similarity index 86% rename from admob/testapp/src/android/android_main.cc rename to gma/testapp/src/android/android_main.cc index 73cb30e7..7a6de455 100644 --- a/admob/testapp/src/android/android_main.cc +++ b/gma/testapp/src/android/android_main.cc @@ -17,33 +17,33 @@ #include #include -#include #include +#include -#include "main.h" // NOLINT +#include "main.h" // NOLINT // This implementation is derived from http://github.com/google/fplutil -extern "C" int common_main(int argc, const char* argv[]); +extern "C" int common_main(int argc, const char *argv[]); -static struct android_app* g_app_state = nullptr; +static struct android_app *g_app_state = nullptr; static bool g_destroy_requested = false; static bool g_started = false; static bool g_restarted = false; static pthread_mutex_t g_started_mutex; // Handle state changes from via native app glue. -static void OnAppCmd(struct android_app* app, int32_t cmd) { +static void OnAppCmd(struct android_app *app, int32_t cmd) { g_destroy_requested |= cmd == APP_CMD_DESTROY; } // Process events pending on the main thread. // Returns true when the app receives an event requesting exit. bool ProcessEvents(int msec) { - struct android_poll_source* source = nullptr; + struct android_poll_source *source = nullptr; int events; int looperId = ALooper_pollAll(msec, nullptr, &events, - reinterpret_cast(&source)); + reinterpret_cast(&source)); if (looperId >= 0 && source) { source->process(g_app_state, source); } @@ -57,7 +57,7 @@ jobject GetActivity() { return g_app_state->activity->clazz; } jobject GetWindowContext() { return g_app_state->activity->clazz; } // Find a class, attempting to load the class if it's not found. -jclass FindClass(JNIEnv* env, jobject activity_object, const char* class_name) { +jclass FindClass(JNIEnv *env, jobject activity_object, const char *class_name) { jclass class_object = env->FindClass(class_name); if (env->ExceptionCheck()) { env->ExceptionClear(); @@ -93,14 +93,13 @@ jclass FindClass(JNIEnv* env, jobject activity_object, const char* class_name) { // Vars that we need available for appending text to the log window: class LoggingUtilsData { - public: +public: LoggingUtilsData() - : logging_utils_class_(nullptr), - logging_utils_add_log_text_(0), + : logging_utils_class_(nullptr), logging_utils_add_log_text_(0), logging_utils_init_log_window_(0) {} ~LoggingUtilsData() { - JNIEnv* env = GetJniEnv(); + JNIEnv *env = GetJniEnv(); assert(env); if (logging_utils_class_) { env->DeleteGlobalRef(logging_utils_class_); @@ -108,7 +107,7 @@ class LoggingUtilsData { } void Init() { - JNIEnv* env = GetJniEnv(); + JNIEnv *env = GetJniEnv(); assert(env); jclass logging_utils_class = FindClass( @@ -130,9 +129,10 @@ class LoggingUtilsData { logging_utils_init_log_window_, GetActivity()); } - void AppendText(const char* text) { - if (logging_utils_class_ == 0) return; // haven't been initted yet - JNIEnv* env = GetJniEnv(); + void AppendText(const char *text) { + if (logging_utils_class_ == 0) + return; // haven't been initted yet + JNIEnv *env = GetJniEnv(); assert(env); jstring text_string = env->NewStringUTF(text); env->CallStaticVoidMethod(logging_utils_class_, logging_utils_add_log_text_, @@ -140,17 +140,17 @@ class LoggingUtilsData { env->DeleteLocalRef(text_string); } - private: +private: jclass logging_utils_class_; jmethodID logging_utils_add_log_text_; jmethodID logging_utils_init_log_window_; }; -LoggingUtilsData* g_logging_utils_data; +LoggingUtilsData *g_logging_utils_data; // Checks if a JNI exception has happened, and if so, logs it to the console. void CheckJNIException() { - JNIEnv* env = GetJniEnv(); + JNIEnv *env = GetJniEnv(); if (env->ExceptionCheck()) { // Get the exception text. jthrowable exception = env->ExceptionOccurred(); @@ -161,7 +161,7 @@ void CheckJNIException() { jmethodID toString = env->GetMethodID(object_class, "toString", "()Ljava/lang/String;"); jstring s = (jstring)env->CallObjectMethod(exception, toString); - const char* exception_text = env->GetStringUTFChars(s, nullptr); + const char *exception_text = env->GetStringUTFChars(s, nullptr); // Log the exception text. __android_log_print(ANDROID_LOG_INFO, FIREBASE_TESTAPP_NAME, @@ -182,7 +182,7 @@ void CheckJNIException() { } // Log a message that can be viewed in "adb logcat". -void LogMessage(const char* format, ...) { +void LogMessage(const char *format, ...) { static const int kLineBufferSize = 100; char buffer[kLineBufferSize + 2]; @@ -201,15 +201,15 @@ void LogMessage(const char* format, ...) { } // Get the JNI environment. -JNIEnv* GetJniEnv() { - JavaVM* vm = g_app_state->activity->vm; - JNIEnv* env; +JNIEnv *GetJniEnv() { + JavaVM *vm = g_app_state->activity->vm; + JNIEnv *env; jint result = vm->AttachCurrentThread(&env, nullptr); return result == JNI_OK ? env : nullptr; } // Execute common_main(), flush pending events and finish the activity. -extern "C" void android_main(struct android_app* state) { +extern "C" void android_main(struct android_app *state) { // native_app_glue spawns a new thread, calling android_main() when the // activity onStart() or onRestart() methods are called. This code handles // the case where we're re-entering this method on a different thread by @@ -236,9 +236,9 @@ extern "C" void android_main(struct android_app* state) { g_logging_utils_data->Init(); // Execute cross platform entry point. - static const char* argv[] = {FIREBASE_TESTAPP_NAME}; + static const char *argv[] = {FIREBASE_TESTAPP_NAME}; int return_value = common_main(1, argv); - (void)return_value; // Ignore the return value. + (void)return_value; // Ignore the return value. ProcessEvents(10); // Clean up logging display. @@ -246,7 +246,8 @@ extern "C" void android_main(struct android_app* state) { g_logging_utils_data = nullptr; // Finish the activity. - if (!g_restarted) ANativeActivity_finish(state->activity); + if (!g_restarted) + ANativeActivity_finish(state->activity); g_app_state->activity->vm->DetachCurrentThread(); g_started = false; diff --git a/admob/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java b/gma/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java similarity index 92% rename from admob/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java rename to gma/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java index 11d67c5b..3cb37ecf 100644 --- a/admob/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java +++ b/gma/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java @@ -44,12 +44,12 @@ public static void initLogWindow(Activity activity) { public static void addLogText(final String text) { new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - if (sTextView != null) { - sTextView.append(text); - } + @Override + public void run() { + if (sTextView != null) { + sTextView.append(text); } - }); + } + }); } } diff --git a/gma/testapp/src/common_main.cc b/gma/testapp/src/common_main.cc new file mode 100644 index 00000000..10a0a181 --- /dev/null +++ b/gma/testapp/src/common_main.cc @@ -0,0 +1,473 @@ +// Copyright 2022 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "firebase/app.h" +#include "firebase/future.h" +#include "firebase/gma.h" +#include "firebase/gma/ad_view.h" +#include "firebase/gma/interstitial_ad.h" +#include "firebase/gma/rewarded_ad.h" +#include "firebase/gma/types.h" + +// Thin OS abstraction layer. +#include "main.h" // NOLINT + +// A simple listener that logs changes to an AdView. +class LoggingAdViewListener : public firebase::gma::AdListener { +public: + LoggingAdViewListener() {} + + void OnAdClicked() override { ::LogMessage("AdView ad clicked."); } + + void OnAdClosed() override { ::LogMessage("AdView ad closed."); } + + void OnAdImpression() override { ::LogMessage("AdView ad impression."); } + + void OnAdOpened() override { ::LogMessage("AdView ad opened."); } +}; + +// A simple listener that logs changes to an AdView's bounding box. +class LoggingAdViewBoundedBoxListener + : public firebase::gma::AdViewBoundingBoxListener { +public: + void OnBoundingBoxChanged(firebase::gma::AdView *ad_view, + firebase::gma::BoundingBox box) override { + ::LogMessage("AdView bounding box update x: %d y: %d " + "width: %d height: %d", + box.x, box.y, box.width, box.height); + } +}; + +// A simple listener track FullScreen content changes. +class LoggingFullScreenContentListener + : public firebase::gma::FullScreenContentListener { +public: + LoggingFullScreenContentListener() : num_ad_dismissed_(0) {} + + void OnAdClicked() override { ::LogMessage("FullScreenContent ad clicked."); } + + void OnAdDismissedFullScreenContent() override { + ::LogMessage("FullScreenContent ad dismissed."); + num_ad_dismissed_++; + } + + void OnAdFailedToShowFullScreenContent( + const firebase::gma::AdError &ad_error) override { + ::LogMessage("FullScreenContent ad failed to show full screen content," + " AdErrorCode: %d", + ad_error.code()); + } + + void OnAdImpression() override { + ::LogMessage("FullScreenContent ad impression."); + } + + void OnAdShowedFullScreenContent() override { + ::LogMessage("FullScreenContent ad showed content."); + } + + uint32_t num_ad_dismissed() const { return num_ad_dismissed_; } + +private: + uint32_t num_ad_dismissed_; +}; + +// A simple listener track UserEarnedReward events. +class LoggingUserEarnedRewardListener + : public firebase::gma::UserEarnedRewardListener { +public: + LoggingUserEarnedRewardListener() {} + + void OnUserEarnedReward(const firebase::gma::AdReward &reward) override { + ::LogMessage("User earned reward amount: %d type: %s", reward.amount(), + reward.type().c_str()); + } +}; + +// A simple listener track ad pay events. +class LoggingPaidEventListener : public firebase::gma::PaidEventListener { +public: + LoggingPaidEventListener() {} + + void OnPaidEvent(const firebase::gma::AdValue &value) override { + ::LogMessage("PaidEvent value: %lld currency_code: %s", + value.value_micros(), value.currency_code().c_str()); + } +}; + +void LoadAndShowAdView(const firebase::gma::AdRequest &ad_request); +void LoadAndShowInterstitialAd(const firebase::gma::AdRequest &ad_request); +void LoadAndShowRewardedAd(const firebase::gma::AdRequest &ad_request); + +// These ad units IDs have been created specifically for testing, and will +// always return test ads. +#if defined(__ANDROID__) +const char *kBannerAdUnit = "ca-app-pub-3940256099942544/6300978111"; +const char *kInterstitialAdUnit = "ca-app-pub-3940256099942544/1033173712"; +const char *kRewardedAdUnit = "ca-app-pub-3940256099942544/5224354917"; +#else +const char *kBannerAdUnit = "ca-app-pub-3940256099942544/2934735716"; +const char *kInterstitialAdUnit = "ca-app-pub-3940256099942544/4411468910"; +const char *kRewardedAdUnit = "ca-app-pub-3940256099942544/1712485313"; +#endif + +// Sample keywords to use in making the request. +static const std::vector kKeywords({"GMA", "C++", "Fun"}); + +// Sample test device IDs to use in making the request. Add your own here. +const std::vector kTestDeviceIDs = { + "2077ef9a63d2b398840261c8221a0c9b", "098fe087d987c9a878965454a65654d7"}; + +#if defined(ANDROID) +static const char *kAdNetworkExtrasClassName = + "com/google/ads/mediation/admob/AdMobAdapter"; +#else +static const char *kAdNetworkExtrasClassName = "GADExtras"; +#endif + +// Function to wait for the completion of a future, and log the error +// if one is encountered. +static void WaitForFutureCompletion(firebase::FutureBase future) { + while (!ProcessEvents(1000)) { + if (future.status() != firebase::kFutureStatusPending) { + break; + } + } + + if (future.error() != firebase::gma::kAdErrorCodeNone) { + LogMessage("ERROR: Action failed with error code %d and message \"%s\".", + future.error(), future.error_message()); + } +} + +// Inittialize GMA, load a Banner, Interstitial and Rewarded Ad. +extern "C" int common_main(int argc, const char *argv[]) { + firebase::App *app; + LogMessage("Initializing Firebase App."); + +#if defined(__ANDROID__) + app = ::firebase::App::Create(GetJniEnv(), GetActivity()); +#else + app = ::firebase::App::Create(); +#endif // defined(__ANDROID__) + + LogMessage("Created the Firebase App %x.", + static_cast(reinterpret_cast(app))); + + LogMessage("Initializing the GMA with Firebase API."); + firebase::gma::Initialize(*app); + + WaitForFutureCompletion(firebase::gma::InitializeLastResult()); + if (firebase::gma::InitializeLastResult().error() != + firebase::gma::kAdErrorCodeNone) { + // Initialization Failure. The error was already logged in + // WaitForFutureCompletion, so simply exit here. + return -1; + } + + // Log mediation adapter initialization status. + for (auto adapter_status : + firebase::gma::GetInitializationStatus().GetAdapterStatusMap()) { + LogMessage( + "GMA Mediation Adapter '%s' %s (latency %d ms): %s", + adapter_status.first.c_str(), + (adapter_status.second.is_initialized() ? "loaded" : "NOT loaded"), + adapter_status.second.latency(), + adapter_status.second.description().c_str()); + } + + // Configure test device ids before loading ads. + // + // This example uses ad units that are specially configured to return test ads + // for every request. When using your own ad unit IDs, however, it's important + // to register the device IDs associated with any devices that will be used to + // test the app. This ensures that regardless of the ad unit ID, those + // devices will always receive test ads in compliance with AdMob policy. + // + // Device IDs can be obtained by checking the logcat or the Xcode log while + // debugging. They appear as a long string of hex characters. + firebase::gma::RequestConfiguration request_configuration; + request_configuration.test_device_ids = kTestDeviceIDs; + firebase::gma::SetRequestConfiguration(request_configuration); + + // + // Load and Display a Banner Ad using AdView. + // + + // Create an AdRequest. + firebase::gma::AdRequest ad_request; + + // Configure additional keywords to be used in targeting. + for (auto keyword_iter = kKeywords.begin(); keyword_iter != kKeywords.end(); + ++keyword_iter) { + ad_request.add_keyword((*keyword_iter).c_str()); + } + + // "Extra" key value pairs can be added to the request as well. Typically + // these are used when testing new features. + ad_request.add_extra(kAdNetworkExtrasClassName, "the_name_of_an_extra", + "the_value_for_that_extra"); + + LoadAndShowAdView(ad_request); + LoadAndShowInterstitialAd(ad_request); + LoadAndShowRewardedAd(ad_request); + + LogMessage("\nAll ad operations complete, terminating GMA"); + + firebase::gma::Terminate(); + delete app; + + // Wait until the user kills the app. + while (!ProcessEvents(1000)) { + } + + return 0; +} + +void LoadAndShowAdView(const firebase::gma::AdRequest &ad_request) { + LogMessage("\nLoad and show a banner ad in an AdView:"); + LogMessage("==="); + // Initialize an AdView. + firebase::gma::AdView *ad_view = new firebase::gma::AdView(); + const firebase::gma::AdSize banner_ad_size = firebase::gma::AdSize::kBanner; + ad_view->Initialize(GetWindowContext(), kBannerAdUnit, banner_ad_size); + + // Block until the ad view completes initialization. + WaitForFutureCompletion(ad_view->InitializeLastResult()); + + // Check for errors. + if (ad_view->InitializeLastResult().error() != + firebase::gma::kAdErrorCodeNone) { + LogMessage("AdView initalization failed, error code: %d", + ad_view->InitializeLastResult().error()); + delete ad_view; + ad_view = nullptr; + return; + } + + // Setup the AdView's listeners. + LoggingAdViewListener ad_view_listener; + ad_view->SetAdListener(&ad_view_listener); + LoggingPaidEventListener paid_event_listener; + ad_view->SetPaidEventListener(&paid_event_listener); + LoggingAdViewBoundedBoxListener bounding_box_listener; + ad_view->SetBoundingBoxListener(&bounding_box_listener); + + // Load an ad. + ad_view->LoadAd(ad_request); + WaitForFutureCompletion(ad_view->LoadAdLastResult()); + + // Check for errors. + if (ad_view->LoadAdLastResult().error() != firebase::gma::kAdErrorCodeNone) { + // Log information as to why the loadAd request failed. + const firebase::gma::AdResult *result_ptr = + ad_view->LoadAdLastResult().result(); + if (result_ptr != nullptr) { + LogMessage("AdView::loadAd Failure - Code: %d Message: %s Domain: %s", + result_ptr->ad_error().code(), + result_ptr->ad_error().message().c_str(), + result_ptr->ad_error().domain().c_str()); + } + WaitForFutureCompletion(ad_view->Destroy()); + delete ad_view; + ad_view = nullptr; + return; + } + + // Log the loaded ad's dimensions. + const firebase::gma::AdSize ad_size = ad_view->ad_size(); + LogMessage("AdView loaded ad width: %d height: %d", ad_size.width(), + ad_size.height()); + + // Show the ad. + LogMessage("Showing the banner ad."); + WaitForFutureCompletion(ad_view->Show()); + + // Move to each of the six pre-defined positions. + LogMessage("Moving the banner ad to top-center."); + ad_view->SetPosition(firebase::gma::AdView::kPositionTop); + WaitForFutureCompletion(ad_view->SetPositionLastResult()); + + LogMessage("Moving the banner ad to top-left."); + ad_view->SetPosition(firebase::gma::AdView::kPositionTopLeft); + WaitForFutureCompletion(ad_view->SetPositionLastResult()); + + LogMessage("Moving the banner ad to top-right."); + ad_view->SetPosition(firebase::gma::AdView::kPositionTopRight); + WaitForFutureCompletion(ad_view->SetPositionLastResult()); + + LogMessage("Moving the banner ad to bottom-center."); + ad_view->SetPosition(firebase::gma::AdView::kPositionBottom); + WaitForFutureCompletion(ad_view->SetPositionLastResult()); + + LogMessage("Moving the banner ad to bottom-left."); + ad_view->SetPosition(firebase::gma::AdView::kPositionBottomLeft); + WaitForFutureCompletion(ad_view->SetPositionLastResult()); + + LogMessage("Moving the banner ad to bottom-right."); + ad_view->SetPosition(firebase::gma::AdView::kPositionBottomRight); + WaitForFutureCompletion(ad_view->SetPositionLastResult()); + + // Try some coordinate moves. + LogMessage("Moving the banner ad to (100, 300)."); + ad_view->SetPosition(100, 300); + WaitForFutureCompletion(ad_view->SetPositionLastResult()); + + LogMessage("Moving the banner ad to (100, 400)."); + ad_view->SetPosition(100, 400); + WaitForFutureCompletion(ad_view->SetPositionLastResult()); + + // Try hiding and showing the BannerView. + LogMessage("Hiding the banner ad."); + ad_view->Hide(); + WaitForFutureCompletion(ad_view->HideLastResult()); + + LogMessage("Showing the banner ad."); + ad_view->Show(); + WaitForFutureCompletion(ad_view->ShowLastResult()); + + LogMessage("Hiding the banner ad again now that we're done with it."); + ad_view->Hide(); + WaitForFutureCompletion(ad_view->HideLastResult()); + + // Clean up the ad view. + ad_view->Destroy(); + WaitForFutureCompletion(ad_view->DestroyLastResult()); + delete ad_view; + ad_view = nullptr; +} + +void LoadAndShowInterstitialAd(const firebase::gma::AdRequest &ad_request) { + LogMessage("\nLoad and show an interstitial ad:"); + LogMessage("==="); + // Initialize an InterstitialAd. + firebase::gma::InterstitialAd *interstitial_ad = + new firebase::gma::InterstitialAd(); + interstitial_ad->Initialize(GetWindowContext()); + + // Block until the interstitial ad completes initialization. + WaitForFutureCompletion(interstitial_ad->InitializeLastResult()); + + // Check for errors. + if (interstitial_ad->InitializeLastResult().error() != + firebase::gma::kAdErrorCodeNone) { + delete interstitial_ad; + interstitial_ad = nullptr; + return; + } + + // Setup the interstitial ad's listeners. + LoggingFullScreenContentListener fullscreen_content_listener; + interstitial_ad->SetFullScreenContentListener(&fullscreen_content_listener); + LoggingPaidEventListener paid_event_listener; + interstitial_ad->SetPaidEventListener(&paid_event_listener); + + // Load an ad. + interstitial_ad->LoadAd(kInterstitialAdUnit, ad_request); + WaitForFutureCompletion(interstitial_ad->LoadAdLastResult()); + + // Check for errors. + if (interstitial_ad->LoadAdLastResult().error() != + firebase::gma::kAdErrorCodeNone) { + // Log information as to why the loadAd request failed. + const firebase::gma::AdResult *result_ptr = + interstitial_ad->LoadAdLastResult().result(); + if (result_ptr != nullptr) { + LogMessage( + "InterstitialAd::loadAd Failure - Code: %d Message: %s Domain: %s", + result_ptr->ad_error().code(), + result_ptr->ad_error().message().c_str(), + result_ptr->ad_error().domain().c_str()); + } + delete interstitial_ad; + interstitial_ad = nullptr; + return; + } + + // Show the ad. + LogMessage("Showing the interstitial ad."); + interstitial_ad->Show(); + WaitForFutureCompletion(interstitial_ad->ShowLastResult()); + + // Wait for the user to close the interstitial. + while (fullscreen_content_listener.num_ad_dismissed() == 0) { + ProcessEvents(1000); + } + + // Clean up the interstitial ad. + delete interstitial_ad; + interstitial_ad = nullptr; +} + +// WIP +void LoadAndShowRewardedAd(const firebase::gma::AdRequest &ad_request) { + LogMessage("\nLoad and show a rewarded ad:"); + LogMessage("==="); + // Initialize a RewardedAd. + firebase::gma::RewardedAd *rewarded_ad = new firebase::gma::RewardedAd(); + rewarded_ad->Initialize(GetWindowContext()); + + // Block until the interstitial ad completes initialization. + WaitForFutureCompletion(rewarded_ad->InitializeLastResult()); + + // Check for errors. + if (rewarded_ad->InitializeLastResult().error() != + firebase::gma::kAdErrorCodeNone) { + delete rewarded_ad; + rewarded_ad = nullptr; + return; + } + + // Setup the rewarded ad's lifecycle listeners. + LoggingFullScreenContentListener fullscreen_content_listener; + rewarded_ad->SetFullScreenContentListener(&fullscreen_content_listener); + LoggingPaidEventListener paid_event_listener; + rewarded_ad->SetPaidEventListener(&paid_event_listener); + + // Load an ad. + rewarded_ad->LoadAd(kRewardedAdUnit, ad_request); + WaitForFutureCompletion(rewarded_ad->LoadAdLastResult()); + + // Check for errors. + if (rewarded_ad->LoadAdLastResult().error() != + firebase::gma::kAdErrorCodeNone) { + // Log information as to why the loadAd request failed. + const firebase::gma::AdResult *result_ptr = + rewarded_ad->LoadAdLastResult().result(); + if (result_ptr != nullptr) { + LogMessage("RewardedAd::loadAd Failure - Code: %d Message: %s Domain: %s", + result_ptr->ad_error().code(), + result_ptr->ad_error().message().c_str(), + result_ptr->ad_error().domain().c_str()); + } + delete rewarded_ad; + rewarded_ad = nullptr; + return; + } + + // Show the ad. + LogMessage("Showing the rewarded ad."); + LoggingUserEarnedRewardListener user_earned_reward_listener; + rewarded_ad->Show(&user_earned_reward_listener); + WaitForFutureCompletion(rewarded_ad->ShowLastResult()); + + // Wait for the user to close the interstitial. + while (fullscreen_content_listener.num_ad_dismissed() == 0) { + ProcessEvents(1000); + } + + // Clean up the interstitial ad. + delete rewarded_ad; + rewarded_ad = nullptr; +} diff --git a/admob/testapp/src/desktop/desktop_main.cc b/gma/testapp/src/desktop/desktop_main.cc similarity index 81% rename from admob/testapp/src/desktop/desktop_main.cc rename to gma/testapp/src/desktop/desktop_main.cc index 0220c688..0a318786 100644 --- a/admob/testapp/src/desktop/desktop_main.cc +++ b/gma/testapp/src/desktop/desktop_main.cc @@ -21,16 +21,16 @@ #define chdir _chdir #else #include -#endif // _WIN32 +#endif // _WIN32 #ifdef _WIN32 #include -#endif // _WIN32 +#endif // _WIN32 #include #include -#include "main.h" // NOLINT +#include "main.h" // NOLINT // The TO_STRING macro is useful for command line defined strings as the quotes // get stripped. @@ -42,9 +42,9 @@ #define FIREBASE_CONFIG_STRING TO_STRING(FIREBASE_CONFIG) #else #define FIREBASE_CONFIG_STRING "" -#endif // FIREBASE_CONFIG +#endif // FIREBASE_CONFIG -extern "C" int common_main(int argc, const char* argv[]); +extern "C" int common_main(int argc, const char *argv[]); static bool quit = false; @@ -58,22 +58,20 @@ static BOOL WINAPI SignalHandler(DWORD event) { } #else static void SignalHandler(int /* ignored */) { quit = true; } -#endif // _WIN32 +#endif // _WIN32 bool ProcessEvents(int msec) { #ifdef _WIN32 Sleep(msec); #else usleep(msec * 1000); -#endif // _WIN32 +#endif // _WIN32 return quit; } -std::string PathForResource() { - return std::string(); -} +std::string PathForResource() { return std::string(); } -void LogMessage(const char* format, ...) { +void LogMessage(const char *format, ...) { va_list list; va_start(list, format); vprintf(format, list); @@ -86,25 +84,26 @@ WindowContext GetWindowContext() { return nullptr; } // Change the current working directory to the directory containing the // specified file. -void ChangeToFileDirectory(const char* file_path) { +void ChangeToFileDirectory(const char *file_path) { std::string path(file_path); std::replace(path.begin(), path.end(), '\\', '/'); auto slash = path.rfind('/'); if (slash != std::string::npos) { std::string directory = path.substr(0, slash); - if (!directory.empty()) chdir(directory.c_str()); + if (!directory.empty()) + chdir(directory.c_str()); } } -int main(int argc, const char* argv[]) { - ChangeToFileDirectory( - FIREBASE_CONFIG_STRING[0] != '\0' ? - FIREBASE_CONFIG_STRING : argv[0]); // NOLINT +int main(int argc, const char *argv[]) { + ChangeToFileDirectory(FIREBASE_CONFIG_STRING[0] != '\0' + ? FIREBASE_CONFIG_STRING + : argv[0]); // NOLINT #ifdef _WIN32 SetConsoleCtrlHandler((PHANDLER_ROUTINE)SignalHandler, TRUE); #else signal(SIGINT, SignalHandler); -#endif // _WIN32 +#endif // _WIN32 return common_main(argc, argv); } diff --git a/admob/testapp/src/ios/ios_main.mm b/gma/testapp/src/ios/ios_main.mm similarity index 74% rename from admob/testapp/src/ios/ios_main.mm rename to gma/testapp/src/ios/ios_main.mm index 6ccb2de5..4c226d5e 100644 --- a/admob/testapp/src/ios/ios_main.mm +++ b/gma/testapp/src/ios/ios_main.mm @@ -18,9 +18,9 @@ #include "main.h" -extern "C" int common_main(int argc, const char* argv[]); +extern "C" int common_main(int argc, const char *argv[]); -@interface AppDelegate : UIResponder +@interface AppDelegate : UIResponder @property(nonatomic, strong) UIWindow *window; @@ -42,33 +42,35 @@ @implementation FTAViewController - (void)viewDidLoad { [super viewDidLoad]; g_parent_view = self.view; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - const char *argv[] = {FIREBASE_TESTAPP_NAME}; - [g_shutdown_signal lock]; - g_exit_status = common_main(1, argv); - [g_shutdown_complete signal]; - }); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + ^{ + const char *argv[] = {FIREBASE_TESTAPP_NAME}; + [g_shutdown_signal lock]; + g_exit_status = common_main(1, argv); + [g_shutdown_complete signal]; + }); } @end bool ProcessEvents(int msec) { [g_shutdown_signal - waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:static_cast(msec) / 1000.0f]]; + waitUntilDate:[NSDate + dateWithTimeIntervalSinceNow:static_cast(msec) / + 1000.0f]]; return g_shutdown; } -WindowContext GetWindowContext() { - return g_parent_view; -} +WindowContext GetWindowContext() { return g_parent_view; } // Log a message that can be viewed in the console. -void LogMessage(const char* format, ...) { +void LogMessage(const char *format, ...) { va_list args; NSString *formatString = @(format); va_start(args, format); - NSString *message = [[NSString alloc] initWithFormat:formatString arguments:args]; + NSString *message = [[NSString alloc] initWithFormat:formatString + arguments:args]; va_end(args); NSLog(@"%@", message); @@ -79,7 +81,7 @@ void LogMessage(const char* format, ...) { }); } -int main(int argc, char* argv[]) { +int main(int argc, char *argv[]) { @autoreleasepool { UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } @@ -88,8 +90,8 @@ int main(int argc, char* argv[]) { @implementation AppDelegate -- (BOOL)application:(UIApplication*)application - didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { g_shutdown_complete = [[NSCondition alloc] init]; g_shutdown_signal = [[NSCondition alloc] init]; [g_shutdown_complete lock]; diff --git a/admob/testapp/src/main.h b/gma/testapp/src/main.h similarity index 76% rename from admob/testapp/src/main.h rename to gma/testapp/src/main.h index 2eda2c10..d41017a7 100644 --- a/admob/testapp/src/main.h +++ b/gma/testapp/src/main.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef FIREBASE_TESTAPP_MAIN_H_ // NOLINT -#define FIREBASE_TESTAPP_MAIN_H_ // NOLINT +#ifndef FIREBASE_TESTAPP_MAIN_H_ // NOLINT +#define FIREBASE_TESTAPP_MAIN_H_ // NOLINT #if defined(__ANDROID__) #include @@ -21,18 +21,18 @@ #elif defined(__APPLE__) extern "C" { #include -} // extern "C" -#endif // __ANDROID__ +} // extern "C" +#endif // __ANDROID__ // Defined using -DANDROID_MAIN_APP_NAME=some_app_name when compiling this // file. #ifndef FIREBASE_TESTAPP_NAME #define FIREBASE_TESTAPP_NAME "android_main" -#endif // FIREBASE_TESTAPP_NAME +#endif // FIREBASE_TESTAPP_NAME // Cross platform logging method. // Implemented by android/android_main.cc or ios/ios_main.mm. -extern "C" void LogMessage(const char* format, ...); +extern "C" void LogMessage(const char *format, ...); // Platform-independent method to flush pending events for the main thread. // Returns true when an event requesting program-exit is received. @@ -41,23 +41,23 @@ bool ProcessEvents(int msec); // WindowContext represents the handle to the parent window. It's type // (and usage) vary based on the OS. #if defined(__ANDROID__) -typedef jobject WindowContext; // A jobject to the Java Activity. +typedef jobject WindowContext; // A jobject to the Java Activity. #elif defined(__APPLE__) -typedef id WindowContext; // A pointer to an iOS UIView. +typedef id WindowContext; // A pointer to an iOS UIView. #else -typedef void* WindowContext; // A void* for any other environments. +typedef void *WindowContext; // A void* for any other environments. #endif #if defined(__ANDROID__) // Get the JNI environment. -JNIEnv* GetJniEnv(); +JNIEnv *GetJniEnv(); // Get the activity. jobject GetActivity(); -#endif // defined(__ANDROID__) +#endif // defined(__ANDROID__) // Returns a variable that describes the window context for the app. On Android // this will be a jobject pointing to the Activity. On iOS, it's an id pointing // to the root view of the view controller. WindowContext GetWindowContext(); -#endif // FIREBASE_TESTAPP_MAIN_H_ // NOLINT +#endif // FIREBASE_TESTAPP_MAIN_H_ // NOLINT diff --git a/admob/testapp/testapp.xcodeproj/project.pbxproj b/gma/testapp/testapp.xcodeproj/project.pbxproj similarity index 99% rename from admob/testapp/testapp.xcodeproj/project.pbxproj rename to gma/testapp/testapp.xcodeproj/project.pbxproj index 14a0af7f..3afd2dea 100644 --- a/admob/testapp/testapp.xcodeproj/project.pbxproj +++ b/gma/testapp/testapp.xcodeproj/project.pbxproj @@ -208,7 +208,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -245,7 +245,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/admob/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json b/gma/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from admob/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json rename to gma/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json diff --git a/admob/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json b/gma/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json similarity index 100% rename from admob/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json rename to gma/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json diff --git a/admob/testapp/testapp/Info.plist b/gma/testapp/testapp/Info.plist similarity index 100% rename from admob/testapp/testapp/Info.plist rename to gma/testapp/testapp/Info.plist diff --git a/messaging/testapp/AndroidManifest.xml b/messaging/testapp/AndroidManifest.xml index f0f9b9c8..c0357769 100644 --- a/messaging/testapp/AndroidManifest.xml +++ b/messaging/testapp/AndroidManifest.xml @@ -3,7 +3,7 @@ package="com.google.android.messaging.testapp" android:versionCode="1" android:versionName="1.0"> - + @@ -19,6 +19,7 @@ work around a known issue when receiving notification data payloads in the background. --> diff --git a/messaging/testapp/Podfile b/messaging/testapp/Podfile index e6ffc8dc..b022880f 100644 --- a/messaging/testapp/Podfile +++ b/messaging/testapp/Podfile @@ -1,6 +1,7 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '13.0' +use_frameworks! # FCM test application. target 'testapp' do - pod 'Firebase/Messaging', '7.0.0' + pod 'Firebase/Messaging', '10.25.0' end diff --git a/messaging/testapp/build.gradle b/messaging/testapp/build.gradle index 0971c57b..4f607fe3 100644 --- a/messaging/testapp/build.gradle +++ b/messaging/testapp/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.android.tools.build:gradle:4.2.1' classpath 'com.google.gms:google-services:4.0.1' } } @@ -22,15 +22,13 @@ allprojects { apply plugin: 'com.android.application' android { - compileSdkVersion 28 - buildToolsVersion '28.0.3' - - android { - compileOptions { - sourceCompatibility 1.8 - targetCompatibility 1.8 - } + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 } + compileSdkVersion 34 + ndkPath System.getenv('ANDROID_NDK_HOME') + buildToolsVersion '30.0.2' sourceSets { main { @@ -43,7 +41,7 @@ android { defaultConfig { applicationId 'com.google.android.messaging.testapp' - minSdkVersion 16 + minSdkVersion 23 targetSdkVersion 28 versionCode 1 versionName '1.0' @@ -61,6 +59,13 @@ android { proguardFile file('proguard.pro') } } + packagingOptions { + pickFirst 'META-INF/**/coroutines.pro' + } + lintOptions { + abortOnError false + checkReleaseBuilds false + } } apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" diff --git a/messaging/testapp/gradle.properties b/messaging/testapp/gradle.properties new file mode 100644 index 00000000..d7ba8f42 --- /dev/null +++ b/messaging/testapp/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX = true diff --git a/messaging/testapp/gradle/wrapper/gradle-wrapper.properties b/messaging/testapp/gradle/wrapper/gradle-wrapper.properties index 35732b09..65340c1b 100644 --- a/messaging/testapp/gradle/wrapper/gradle-wrapper.properties +++ b/messaging/testapp/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/messaging/testapp/testapp.xcodeproj/project.pbxproj b/messaging/testapp/testapp.xcodeproj/project.pbxproj index 096e3825..c5f566b4 100644 --- a/messaging/testapp/testapp.xcodeproj/project.pbxproj +++ b/messaging/testapp/testapp.xcodeproj/project.pbxproj @@ -212,7 +212,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -249,7 +249,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/remote_config/testapp/AndroidManifest.xml b/remote_config/testapp/AndroidManifest.xml index e449f7ee..a7a904df 100644 --- a/remote_config/testapp/AndroidManifest.xml +++ b/remote_config/testapp/AndroidManifest.xml @@ -6,9 +6,10 @@ - + (reinterpret_cast(app))); ::firebase::ModuleInitializer initializer; - initializer.Initialize(app, nullptr, [](::firebase::App* app, void*) { - LogMessage("Try to initialize Remote Config"); - return ::firebase::remote_config::Initialize(*app); + + void *ptr = nullptr; + ptr = &rc_; + initializer.Initialize(app, ptr, [](::firebase::App *app, void *target) { + LogMessage("Try to initialize Firebase RemoteConfig"); + RemoteConfig **rc_ptr = reinterpret_cast(target); + *rc_ptr = RemoteConfig::GetInstance(app); + return firebase::kInitResultSuccess; }); + while (initializer.InitializeLastResult().status() != firebase::kFutureStatusComplete) { - if (ProcessEvents(100)) return 1; // exit if requested + if (ProcessEvents(100)) + return 1; // exit if requested } + if (initializer.InitializeLastResult().error() != 0) { LogMessage("Failed to initialize Firebase Remote Config: %s", initializer.InitializeLastResult().error_message()); @@ -79,47 +91,45 @@ extern "C" int common_main(int argc, const char* argv[]) { sizeof(kBinaryDefaults))}, {"TestDefaultOnly", "Default value that won't be overridden"}}; size_t default_count = sizeof(defaults) / sizeof(defaults[0]); - remote_config::SetDefaults(defaults, default_count); + rc_->SetDefaults(defaults, default_count); // The return values may not be the set defaults, if a fetch was previously // completed for the app that set them. remote_config::ValueInfo value_info; { - bool result = remote_config::GetBoolean("TestBoolean", &value_info); + bool result = rc_->GetBoolean("TestBoolean", &value_info); LogMessage("Get TestBoolean %d %s", result ? 1 : 0, ValueSourceToString(value_info.source)); } { - int64_t result = remote_config::GetLong("TestLong", &value_info); + int64_t result = rc_->GetLong("TestLong", &value_info); LogMessage("Get TestLong %lld %s", result, ValueSourceToString(value_info.source)); } { - double result = remote_config::GetDouble("TestDouble", &value_info); + double result = rc_->GetDouble("TestDouble", &value_info); LogMessage("Get TestDouble %f %s", result, ValueSourceToString(value_info.source)); } { - std::string result = remote_config::GetString("TestString", &value_info); + std::string result = rc_->GetString("TestString", &value_info); LogMessage("Get TestString \"%s\" %s", result.c_str(), ValueSourceToString(value_info.source)); } { - std::vector result = remote_config::GetData("TestData"); + std::vector result = rc_->GetData("TestData"); for (size_t i = 0; i < result.size(); ++i) { const unsigned char value = result[i]; LogMessage("TestData[%d] = 0x%02x", i, value); } } { - std::string result = remote_config::GetString("TestDefaultOnly", - &value_info); + std::string result = rc_->GetString("TestDefaultOnly", &value_info); LogMessage("Get TestDefaultOnly \"%s\" %s", result.c_str(), ValueSourceToString(value_info.source)); } { - std::string result = remote_config::GetString("TestNotSet", - &value_info); + std::string result = rc_->GetString("TestNotSet", &value_info); LogMessage("Get TestNotSet \"%s\" %s", result.c_str(), ValueSourceToString(value_info.source)); } @@ -127,30 +137,21 @@ extern "C" int common_main(int argc, const char* argv[]) { // Test the existence of the keys by name. { // Print out the keys with default values. - std::vector keys = remote_config::GetKeys(); + std::vector keys = rc_->GetKeys(); LogMessage("GetKeys:"); for (auto s = keys.begin(); s != keys.end(); ++s) { LogMessage(" %s", s->c_str()); } - keys = remote_config::GetKeysByPrefix("TestD"); + keys = rc_->GetKeysByPrefix("TestD"); LogMessage("GetKeysByPrefix(\"TestD\"):"); for (auto s = keys.begin(); s != keys.end(); ++s) { LogMessage(" %s", s->c_str()); } } - // Enable developer mode and verified it's enabled. - // NOTE: Developer mode should not be enabled in production applications. - remote_config::SetConfigSetting(remote_config::kConfigSettingDeveloperMode, - "1"); - if ((*remote_config::GetConfigSetting( - remote_config::kConfigSettingDeveloperMode) - .c_str()) != '1') { - LogMessage("Failed to enable developer mode"); - } // Test Fetch... LogMessage("Fetch..."); - auto future_result = remote_config::Fetch(0); + auto future_result = rc_->Fetch(0); while (future_result.status() == firebase::kFutureStatusPending) { if (ProcessEvents(1000)) { break; @@ -159,67 +160,71 @@ extern "C" int common_main(int argc, const char* argv[]) { if (future_result.status() == firebase::kFutureStatusComplete) { LogMessage("Fetch Complete"); - bool activate_result = remote_config::ActivateFetched(); - LogMessage("ActivateFetched %s", activate_result ? "succeeded" : "failed"); - - const remote_config::ConfigInfo& info = remote_config::GetInfo(); - LogMessage( - "Info last_fetch_time_ms=%d (year=%.2f) fetch_status=%d " - "failure_reason=%d throttled_end_time=%d", - static_cast(info.fetch_time), - 1970.0f + static_cast(info.fetch_time) / - (1000.0f * 60.0f * 60.0f * 24.0f * 365.0f), - info.last_fetch_status, info.last_fetch_failure_reason, - info.throttled_end_time); + auto activate_future_result = rc_->Activate(); + while (future_result.status() == firebase::kFutureStatusPending) { + if (ProcessEvents(1000)) { + break; + } + } + + bool activate_result = activate_future_result.result(); + LogMessage("Activate %s", activate_result ? "succeeded" : "failed"); + + const remote_config::ConfigInfo &info = rc_->GetInfo(); + LogMessage("Info last_fetch_time_ms=%d (year=%.2f) fetch_status=%d " + "failure_reason=%d throttled_end_time=%d", + static_cast(info.fetch_time), + 1970.0f + static_cast(info.fetch_time) / + (1000.0f * 60.0f * 60.0f * 24.0f * 365.0f), + info.last_fetch_status, info.last_fetch_failure_reason, + info.throttled_end_time); // Print out the new values, which may be updated from the Fetch. { - bool result = remote_config::GetBoolean("TestBoolean", &value_info); + bool result = rc_->GetBoolean("TestBoolean", &value_info); LogMessage("Updated TestBoolean %d %s", result ? 1 : 0, ValueSourceToString(value_info.source)); } { - int64_t result = remote_config::GetLong("TestLong", &value_info); + int64_t result = rc_->GetLong("TestLong", &value_info); LogMessage("Updated TestLong %lld %s", result, ValueSourceToString(value_info.source)); } { - double result = remote_config::GetDouble("TestDouble", &value_info); + double result = rc_->GetDouble("TestDouble", &value_info); LogMessage("Updated TestDouble %f %s", result, ValueSourceToString(value_info.source)); } { - std::string result = remote_config::GetString("TestString", &value_info); + std::string result = rc_->GetString("TestString", &value_info); LogMessage("Updated TestString \"%s\" %s", result.c_str(), ValueSourceToString(value_info.source)); } { - std::vector result = remote_config::GetData("TestData"); + std::vector result = rc_->GetData("TestData"); for (size_t i = 0; i < result.size(); ++i) { const unsigned char value = result[i]; LogMessage("TestData[%d] = 0x%02x", i, value); } } { - std::string result = remote_config::GetString("TestDefaultOnly", - &value_info); + std::string result = rc_->GetString("TestDefaultOnly", &value_info); LogMessage("Get TestDefaultOnly \"%s\" %s", result.c_str(), ValueSourceToString(value_info.source)); } { - std::string result = remote_config::GetString("TestNotSet", - &value_info); + std::string result = rc_->GetString("TestNotSet", &value_info); LogMessage("Get TestNotSet \"%s\" %s", result.c_str(), ValueSourceToString(value_info.source)); } { // Print out the keys that are now tied to data - std::vector keys = remote_config::GetKeys(); + std::vector keys = rc_->GetKeys(); LogMessage("GetKeys:"); for (auto s = keys.begin(); s != keys.end(); ++s) { LogMessage(" %s", s->c_str()); } - keys = remote_config::GetKeysByPrefix("TestD"); + keys = rc_->GetKeysByPrefix("TestD"); LogMessage("GetKeysByPrefix(\"TestD\"):"); for (auto s = keys.begin(); s != keys.end(); ++s) { LogMessage(" %s", s->c_str()); @@ -237,7 +242,8 @@ extern "C" int common_main(int argc, const char* argv[]) { while (!ProcessEvents(1000)) { } - remote_config::Terminate(); + delete rc_; + rc_ = nullptr; delete app; return 0; diff --git a/remote_config/testapp/testapp.xcodeproj/project.pbxproj b/remote_config/testapp/testapp.xcodeproj/project.pbxproj index 6c325725..5769362c 100644 --- a/remote_config/testapp/testapp.xcodeproj/project.pbxproj +++ b/remote_config/testapp/testapp.xcodeproj/project.pbxproj @@ -208,7 +208,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -245,7 +245,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/scripts/build_scripts/android/install_prereqs.sh b/scripts/build_scripts/android/install_prereqs.sh new file mode 100755 index 00000000..f5d6efe9 --- /dev/null +++ b/scripts/build_scripts/android/install_prereqs.sh @@ -0,0 +1,75 @@ +#!/bin/bash -e + +# Copyright 2022 Google LLC + +if [[ $(uname) == "Darwin" ]]; then + platform=darwin + if [[ ! -z "${GHA_INSTALL_CCACHE}" ]]; then + brew install ccache + echo "CCACHE_INSTALLED=1" >> $GITHUB_ENV + fi +elif [[ $(uname) == "Linux" ]]; then + platform=linux + if [[ ! -z "${GHA_INSTALL_CCACHE}" ]]; then + sudo apt install ccache + echo "CCACHE_INSTALLED=1" >> $GITHUB_ENV + fi +else + platform=windows +fi + +if [[ -z $(which cmake) ]]; then + echo "Error, cmake is not installed or is not in the PATH." + exit 1 +fi + +if [[ -z $(which python) ]]; then + echo "Error, python is not installed or is not in the PATH." + exit 1 +else + updated_pip=0 + if ! $(echo "import absl"$'\n' | python - 2> /dev/null); then + echo "Installing python packages." + set -x + # On Windows bash shell, sudo doesn't exist + if [[ $(uname) == "Linux" ]] || [[ $(uname) == "Darwin" ]]; then + sudo python -m pip install --upgrade pip + else + python -m pip install --upgrade pip + fi + pip install absl-py + set +x + fi +fi + +if [[ -z "${ANDROID_HOME}" ]]; then + echo "Error, ANDROID_HOME environment variable is not set." + exit 1 +fi + +if [[ -z "${NDK_ROOT}" || -z $(grep "Pkg\.Revision = 21\." "${NDK_ROOT}/source.properties") ]]; then + if [[ -d /tmp/android-ndk-r21e && \ + -n $(grep "Pkg\.Revision = 21\." "/tmp/android-ndk-r21e/source.properties") ]]; then + echo "Using NDK r21e in /tmp/android-ndk-r21e". + else + echo "NDK_ROOT environment variable is not set, or NDK version is incorrect." + echo "This build recommends NDK r21e, downloading..." + if [[ -z $(which curl) ]]; then + echo "Error, could not run 'curl' to download NDK. Is it in your PATH?" + exit 1 + fi + set +e + # Retry up to 10 times because Curl has a tendency to timeout on + # Github runners. + for retry in {1..10} error; do + if [[ $retry == "error" ]]; then exit 5; fi + curl --http1.1 -LSs \ + "https://dl.google.com/android/repository/android-ndk-r21e-${platform}-x86_64.zip" \ + --output /tmp/android-ndk-r21e.zip && break + sleep 300 + done + set -e + (cd /tmp && unzip -oq android-ndk-r21e.zip && rm -f android-ndk-r21e.zip) + echo "NDK r21e has been downloaded into /tmp/android-ndk-r21e" + fi +fi diff --git a/scripts/build_scripts/build_testapps.json b/scripts/build_scripts/build_testapps.json new file mode 100644 index 00000000..5b5ef2d1 --- /dev/null +++ b/scripts/build_scripts/build_testapps.json @@ -0,0 +1,170 @@ +{ + "apis": [ + { + "name": "analytics", + "full_name": "FirebaseAnalytics", + "bundle_id": "com.google.ios.analytics.testapp", + "ios_target": "testapp", + "tvos_target": "", + "testapp_path": "analytics/testapp", + "frameworks": [ + "firebase_analytics.xcframework", + "firebase.xcframework" + ], + "provision": "Google_Development.mobileprovision" + }, + { + "name": "auth", + "full_name": "FirebaseAuth", + "bundle_id": "com.google.FirebaseCppAuthTestApp.dev", + "ios_target": "testapp", + "tvos_target": "", + "testapp_path": "auth/testapp", + "frameworks": [ + "firebase_auth.xcframework", + "firebase.xcframework" + ], + "provision": "Firebase_Cpp_Auth_Test_App_Dev.mobileprovision" + }, + { + "name": "database", + "full_name": "FirebaseDatabase", + "bundle_id": "com.google.firebase.cpp.database.testapp", + "ios_target": "testapp", + "tvos_target": "", + "testapp_path": "database/testapp", + "frameworks": [ + "firebase_auth.xcframework", + "firebase_database.xcframework", + "firebase.xcframework" + ], + "provision": "Firebase_Dev_Wildcard.mobileprovision" + }, + { + "name": "dynamic_links", + "full_name": "FirebaseDynamicLinks", + "bundle_id": "com.google.FirebaseCppDynamicLinksTestApp.dev", + "ios_target": "testapp", + "tvos_target": "", + "testapp_path": "dynamic_links/testapp", + "frameworks": [ + "firebase_dynamic_links.xcframework", + "firebase.xcframework" + ], + "provision": "Firebase_Cpp_Dynamic_Links_Test_App_Dev.mobileprovision" + }, + { + "name": "functions", + "full_name": "FirebaseFunctions", + "bundle_id": "com.google.firebase.cpp.functions.testapp", + "ios_target": "testapp", + "tvos_target": "", + "testapp_path": "functions/testapp", + "frameworks": [ + "firebase_auth.xcframework", + "firebase_functions.xcframework", + "firebase.xcframework" + ], + "provision": "Firebase_Dev_Wildcard.mobileprovision" + }, + { + "name": "gma", + "full_name": "FirebaseGma", + "bundle_id": "com.google.ios.admob.testapp", + "ios_target": "testapp", + "tvos_target": "", + "testapp_path": "gma/testapp", + "frameworks": [ + "firebase_gma.xcframework", + "firebase.xcframework" + ], + "provision": "Google_Development.mobileprovision" + }, + { + "name": "messaging", + "full_name": "FirebaseMessaging", + "bundle_id": "com.google.FirebaseCppMessagingTestApp.dev", + "ios_target": "testapp", + "tvos_target": "", + "testapp_path": "messaging/testapp", + "frameworks": [ + "firebase_messaging.xcframework", + "firebase.xcframework" + ], + "provision": "Firebase_Cpp_Messaging_Test_App_Dev.mobileprovision" + }, + { + "name": "remote_config", + "full_name": "FirebaseRemoteConfig", + "bundle_id": "com.google.ios.remoteconfig.testapp", + "ios_target": "testapp", + "tvos_target": "", + "testapp_path": "remote_config/testapp", + "frameworks": [ + "firebase_remote_config.xcframework", + "firebase.xcframework" + ], + "provision": "Google_Development.mobileprovision" + }, + { + "name": "storage", + "full_name": "FirebaseStorage", + "bundle_id": "com.google.firebase.cpp.storage.testapp", + "ios_target": "testapp", + "tvos_target": "", + "testapp_path": "storage/testapp", + "frameworks": [ + "firebase_storage.xcframework", + "firebase_auth.xcframework", + "firebase.xcframework" + ], + "provision": "Firebase_Dev_Wildcard.mobileprovision" + }, + { + "name": "firestore", + "full_name": "FirebaseFirestore", + "bundle_id": "com.google.firebase.cpp.firestore.testapp", + "ios_target": "testapp", + "tvos_target": "", + "testapp_path": "firestore/testapp", + "frameworks": [ + "firebase_firestore.xcframework", + "firebase_auth.xcframework", + "firebase.xcframework" + ], + "provision": "Firebase_Dev_Wildcard.mobileprovision", + "minify": "proguard" + } + ], + "apple_team_id": "REPLACE_ME_TEMP_INVALID_ID", + "compiler_dict": { + "gcc-4.8": [ + "-DCMAKE_C_COMPILER=gcc-4.8", + "-DCMAKE_CXX_COMPILER=g++-4.8" + ], + "gcc-7": [ + "-DCMAKE_C_COMPILER=gcc-7", + "-DCMAKE_CXX_COMPILER=g++-7" + ], + "gcc-9": [ + "-DCMAKE_C_COMPILER=gcc-9", + "-DCMAKE_CXX_COMPILER=g++-9" + ], + "clang-5.0": [ + "-DCMAKE_C_COMPILER=clang-5.0", + "-DCMAKE_CXX_COMPILER=clang++-5.0" + ], + "VisualStudio2015": [ + "-G", + "Visual Studio 14 2015 Win64" + ], + "VisualStudio2017": [ + "-G", + "Visual Studio 15 2017 Win64" + ], + "VisualStudio2019": [ + "-G", + "Visual Studio 16 2019" + ] + } + } diff --git a/scripts/build_scripts/build_testapps.py b/scripts/build_scripts/build_testapps.py new file mode 100644 index 00000000..3caf85f0 --- /dev/null +++ b/scripts/build_scripts/build_testapps.py @@ -0,0 +1,718 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +r"""Build automation tool for Firebase C++ testapps for desktop and mobile. + +USAGE: + +This tool has a number of dependencies (listed below). Once those are taken +care of, here is an example of an execution of the tool (on MacOS): + +python build_testapps.py --t auth,messaging --p iOS --s /tmp/firebase-cpp-sdk + +Critical flags: +--t (full name: testapps, default: None) +--p (full name: platforms, default: None) +--s (full name: packaged_sdk, default: None) + +By default, this tool will build integration tests from source, which involves + +Under most circumstances the other flags don't need to be set, but can be +seen by running --help. Note that all path flags will forcefully expand +the user ~. + + +DEPENDENCIES: + +----Firebase Repo---- +The Firebase C++ Quickstart repo must be locally present. +Path specified by the flag: + + --repo_dir (default: current working directory) + +----Python Dependencies---- +The requirements.txt file has the required dependencies for this Python tool. + + pip install -r requirements.txt + +----CMake (Desktop only)---- +CMake must be installed and on the system path. + +----Environment Variables (Android only)---- +If building for Android, gradle requires several environment variables. +The following lists expected variables, and examples of what +a configured value may look like on MacOS: + + JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-8-latest/Contents/Home + ANDROID_HOME=/Users/user_name/Library/Android/sdk + ANDROID_SDK_HOME=/Users/user_name/Library/Android/sdk + ANDROID_NDK_HOME=/Users/user_name/Library/Android/sdk/ndk-bundle + +Or on Linux: + JAVA_HOME=/usr/local/buildtools/java/jdk/ + ANDROID_HOME=~/Android/Sdk + ANDROID_SDK_HOME=~/Android/Sdk + ANDROID_NDK_HOME=~/Android/Sdk/ndk + +If using this tool frequently, you will likely find it convenient to +modify your bashrc file to automatically set these variables. + +""" + +import attr +import datetime +import json +import os +import platform +import shutil +import stat +import subprocess +import sys +import tempfile + +from absl import app +from absl import flags +from absl import logging +from distutils import dir_util + +import utils +import config_reader +import xcodebuild + +# Environment variables +_JAVA_HOME = "JAVA_HOME" +_ANDROID_HOME = "ANDROID_HOME" +_ANDROID_SDK_HOME = "ANDROID_SDK_HOME" +_NDK_ROOT = "NDK_ROOT" +_ANDROID_NDK_HOME = "ANDROID_NDK_HOME" + +# Platforms +_ANDROID = "Android" +_IOS = "iOS" +_TVOS = "tvOS" +_DESKTOP = "Desktop" +_SUPPORTED_PLATFORMS = (_ANDROID, _IOS, _TVOS, _DESKTOP) + +# Architecture +_SUPPORTED_ARCHITECTURES = ("x64", "x86", "arm64") + +# Values for iOS SDK flag (where the iOS app will run) +_APPLE_SDK_DEVICE = "real" +_APPLE_SDK_SIMULATOR = "virtual" +_SUPPORTED_APPLE_SDK = (_APPLE_SDK_DEVICE, _APPLE_SDK_SIMULATOR) + +_DEFAULT_RUN_TIMEOUT_SECONDS = 4800 # 1 hour 20 min + +FLAGS = flags.FLAGS + +flags.DEFINE_string( + "packaged_sdk", None, "Firebase SDK directory.") + +flags.DEFINE_string( + "output_directory", "~", + "Build output will be placed in this directory.") + +flags.DEFINE_string( + "artifact_name", "local-build", + "artifacts will be created and placed in output_directory." + " testapps artifact is testapps-$artifact_name;" + " build log artifact is build-results-$artifact_name.log.") + +flags.DEFINE_string( + "repo_dir", os.getcwd(), + "Firebase C++ Quickstart Git repository. Current directory by default.") + +flags.DEFINE_list( + "testapps", None, "Which testapps (Firebase APIs) to build, e.g." + " 'analytics,auth'.", + short_name="t") + +flags.DEFINE_list( + "platforms", None, "Which platforms to build. Can be Android, iOS and/or" + " Desktop", short_name="p") + +flags.DEFINE_bool( + "add_timestamp", True, + "Add a timestamp to the output directory for disambiguation." + " Recommended when running locally, so each execution gets its own " + " directory.") + +flags.DEFINE_list( + "ios_sdk", _APPLE_SDK_DEVICE, + "(iOS only) Build for real device (.ipa), virtual device / simulator (.app), " + "or both. Building for both will produce both an .app and an .ipa.") + +flags.DEFINE_list( + "tvos_sdk", _APPLE_SDK_SIMULATOR, + "(tvOS only) Build for real device (.ipa), virtual device / simulator (.app), " + "or both. Building for both will produce both an .app and an .ipa.") + +flags.DEFINE_bool( + "update_pod_repo", True, + "(iOS/tvOS only) Will run 'pod repo update' before building for iOS/tvOS to update" + " the local spec repos available on this machine. Must also include iOS/tvOS" + " in platforms flag.") + +flags.DEFINE_string( + "compiler", None, + "(Desktop only) Specify the compiler with CMake during the testapps build." + " Check the config file to see valid choices for this flag." + " If none, will invoke cmake without specifying a compiler.") + +flags.DEFINE_string( + "arch", "x64", + "(Desktop only) Which architecture to build: x64 (all), x86 (Windows/Linux), " + "or arm64 (Mac only).") + +# Get the number of CPUs for the default value of FLAGS.jobs +CPU_COUNT = os.cpu_count(); +# If CPU count couldn't be determined, default to 2. +DEFAULT_CPU_COUNT = 2 +if CPU_COUNT is None: CPU_COUNT = DEFAULT_CPU_COUNT +# Cap at 4 CPUs. +MAX_CPU_COUNT = 4 +if CPU_COUNT > MAX_CPU_COUNT: CPU_COUNT = MAX_CPU_COUNT + +flags.DEFINE_integer( + "jobs", CPU_COUNT, + "(Desktop only) If > 0, pass in -j to make CMake parallelize the" + " build. Defaults to the system's CPU count (max %s)." % MAX_CPU_COUNT) + +flags.DEFINE_multi_string( + "cmake_flag", None, + "Pass an additional flag to the CMake configure step." + " This option can be specified multiple times.") + +flags.register_validator( + "platforms", + lambda p: all(platform in _SUPPORTED_PLATFORMS for platform in p), + message="Valid platforms: " + ",".join(_SUPPORTED_PLATFORMS), + flag_values=FLAGS) + +flags.register_validator( + "ios_sdk", + lambda s: all(ios_sdk in _SUPPORTED_APPLE_SDK for ios_sdk in s), + message="Valid platforms: " + ",".join(_SUPPORTED_APPLE_SDK), + flag_values=FLAGS) + +flags.register_validator( + "tvos_sdk", + lambda s: all(tvos_sdk in _SUPPORTED_APPLE_SDK for tvos_sdk in s), + message="Valid platforms: " + ",".join(_SUPPORTED_APPLE_SDK), + flag_values=FLAGS) + +flags.DEFINE_bool( + "short_output_paths", False, + "Use short directory names for output paths. Useful to avoid hitting file " + "path limits on Windows.") + +flags.DEFINE_bool( + "gha_build", False, + "Set to true if this is a GitHub Actions build.") + +def main(argv): + if len(argv) > 1: + raise app.UsageError("Too many command-line arguments.") + + platforms = FLAGS.platforms + testapps = FLAGS.testapps + + sdk_dir = _fix_path(FLAGS.packaged_sdk) + root_output_dir = _fix_path(FLAGS.output_directory) + repo_dir = _fix_path(FLAGS.repo_dir) + + update_pod_repo = FLAGS.update_pod_repo + if FLAGS.add_timestamp: + timestamp = datetime.datetime.now().strftime("%Y_%m_%d-%H_%M_%S") + else: + timestamp = "" + + if FLAGS.short_output_paths: + output_dir = os.path.join(root_output_dir, "ta") + else: + output_dir = os.path.join(root_output_dir, "testapps" + timestamp) + + config = config_reader.read_config() + + xcframework_dir = os.path.join(sdk_dir, "xcframeworks") + xcframework_exist = os.path.isdir(xcframework_dir) + if not xcframework_exist: + if _IOS in platforms: + _build_xcframework_from_repo(repo_dir, "ios", testapps, config) + if _TVOS in platforms: + _build_xcframework_from_repo(repo_dir, "tvos", testapps, config) + + if update_pod_repo and (_IOS in platforms or _TVOS in platforms): + _run(["pod", "repo", "update"]) + + cmake_flags = _get_desktop_compiler_flags(FLAGS.compiler, config.compilers) + + if (_DESKTOP in platforms and utils.is_linux_os() and FLAGS.arch == "x86"): + # Write out a temporary toolchain file to force 32-bit Linux builds, as + # the SDK-included toolchain file may not be present when building against + # the packaged SDK. + temp_toolchain_file = tempfile.NamedTemporaryFile("w+", suffix=".cmake") + temp_toolchain_file.writelines([ + 'set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32")\n', + 'set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32")\n', + 'set(CMAKE_LIBRARY_PATH "/usr/lib/i386-linux-gnu")\n', + 'set(INCLUDE_DIRECTORIES ${INCLUDE_DIRECTORIES} "/usr/include/i386-linux-gnu")\n']) + temp_toolchain_file.flush() + # Leave the file open, as it will be deleted on close, i.e. when this script exits. + # (On Linux, the file can be opened a second time by cmake while still open by + # this script) + cmake_flags.extend(["-DCMAKE_TOOLCHAIN_FILE=%s" % temp_toolchain_file.name]) + + if FLAGS.cmake_flag: + cmake_flags.extend(FLAGS.cmake_flag) + + failures = [] + for testapp in testapps: + api_config = config.get_api(testapp) + testapp_dirs = [api_config.testapp_path] + for testapp_dir in testapp_dirs: + logging.info("BEGIN building for %s: %s", testapp, testapp_dir) + failures += _build( + testapp=testapp, + platforms=platforms, + api_config=config.get_api(testapp), + testapp_dir=testapp_dir, + output_dir=output_dir, + sdk_dir=sdk_dir, + xcframework_exist=xcframework_exist, + repo_dir=repo_dir, + ios_sdk=FLAGS.ios_sdk, + tvos_sdk=FLAGS.tvos_sdk, + cmake_flags=cmake_flags, + short_output_paths=FLAGS.short_output_paths) + logging.info("END building for %s", testapp) + + _collect_integration_tests(testapps, root_output_dir, output_dir, FLAGS.artifact_name) + + _summarize_results(testapps, platforms, failures, root_output_dir, FLAGS.artifact_name) + return 1 if failures else 0 + + +def _build( + testapp, platforms, api_config, testapp_dir, output_dir, sdk_dir, xcframework_exist, + repo_dir, ios_sdk, tvos_sdk, cmake_flags, short_output_paths): + """Builds one testapp on each of the specified platforms.""" + os.chdir(repo_dir) + project_dir = os.path.join(output_dir, api_config.name) + if short_output_paths: + # Combining the first letter of every part separated by underscore for + # testapp paths. This is a trick to reduce file path length as we were + # exceeding the limit on Windows. + testapp_dir_parts = os.path.basename(testapp_dir).split('_') + output_testapp_dir = ''.join([x[0] for x in testapp_dir_parts]) + else: + output_testapp_dir = os.path.basename(testapp_dir) + + project_dir = os.path.join(project_dir, output_testapp_dir) + + logging.info("Copying testapp project to %s", project_dir) + os.makedirs(project_dir) + dir_util.copy_tree(testapp_dir, project_dir) + + logging.info("Changing directory to %s", project_dir) + os.chdir(project_dir) + + # TODO(DDB): remove + # _run_setup_script(repo_dir, project_dir) + + failures = [] + + if _DESKTOP in platforms: + logging.info("BEGIN %s, %s", testapp, _DESKTOP) + try: + _build_desktop(sdk_dir, cmake_flags) + except subprocess.SubprocessError as e: + failures.append( + Failure(testapp=testapp, platform=_DESKTOP, error_message=str(e))) + _rm_dir_safe(os.path.join(project_dir, "bin")) + logging.info("END %s, %s", testapp, _DESKTOP) + + if _ANDROID in platforms: + logging.info("BEGIN %s, %s", testapp, _ANDROID) + try: + _validate_android_environment_variables() + _build_android(project_dir, sdk_dir) + except subprocess.SubprocessError as e: + failures.append( + Failure(testapp=testapp, platform=_ANDROID, error_message=str(e))) + _rm_dir_safe(os.path.join(project_dir, "build", "intermediates")) + _rm_dir_safe(os.path.join(project_dir, ".externalNativeBuild")) + logging.info("END %s, %s", testapp, _ANDROID) + + if _IOS in platforms: + logging.info("BEGIN %s, %s", testapp, _IOS) + try: + _build_apple( + sdk_dir=sdk_dir, + xcframework_exist=xcframework_exist, + project_dir=project_dir, + repo_dir=repo_dir, + api_config=api_config, + target=api_config.ios_target, + scheme=api_config.ios_scheme, + apple_platfrom=_IOS, + apple_sdk=ios_sdk) + + except subprocess.SubprocessError as e: + failures.append( + Failure(testapp=testapp, platform=_IOS, error_message=str(e))) + logging.info("END %s, %s", testapp, _IOS) + + if _TVOS in platforms and api_config.tvos_target: + logging.info("BEGIN %s, %s", testapp, _TVOS) + try: + _build_apple( + sdk_dir=sdk_dir, + xcframework_exist=xcframework_exist, + project_dir=project_dir, + repo_dir=repo_dir, + api_config=api_config, + target=api_config.tvos_target, + scheme=api_config.tvos_scheme, + apple_platfrom=_TVOS, + apple_sdk=tvos_sdk) + except subprocess.SubprocessError as e: + failures.append( + Failure(testapp=testapp, platform=_TVOS, error_message=str(e))) + logging.info("END %s, %s", testapp, _TVOS) + + return failures + + +def _collect_integration_tests(testapps, root_output_dir, output_dir, artifact_name): + testapps_artifact_dir = "testapps-" + artifact_name + android_testapp_extension = ".apk" + ios_testapp_extension = ".ipa" + ios_simualtor_testapp_extension = ".app" + desktop_testapp_name = "testapp" + if platform.system() == "Windows": + desktop_testapp_name += ".exe" + + testapp_paths = [] + testapp_google_services = {} + for file_dir, directories, file_names in os.walk(output_dir): + for directory in directories: + if directory.endswith(ios_simualtor_testapp_extension): + testapp_paths.append(os.path.join(file_dir, directory)) + for file_name in file_names: + if ((file_name == desktop_testapp_name and "ios_build" not in file_dir) + or file_name.endswith(android_testapp_extension) + or file_name.endswith(ios_testapp_extension)): + testapp_paths.append(os.path.join(file_dir, file_name)) + if (file_name == "google-services.json"): + testapp_google_services[file_dir.split(os.path.sep)[-2]] = os.path.join(file_dir, file_name) + + artifact_path = os.path.join(root_output_dir, testapps_artifact_dir) + _rm_dir_safe(artifact_path) + for testapp in testapps: + os.makedirs(os.path.join(artifact_path, testapp)) + for path in testapp_paths: + for testapp in testapps: + if testapp in path: + if os.path.isfile(path): + shutil.copy(path, os.path.join(artifact_path, testapp)) + if path.endswith(desktop_testapp_name) and testapp_google_services.get(testapp): + shutil.copy(testapp_google_services[testapp], os.path.join(artifact_path, testapp)) + else: + dir_util.copy_tree(path, os.path.join(artifact_path, testapp, os.path.basename(path))) + break + + +def _write_summary(testapp_dir, summary, file_name="summary.log"): + with open(os.path.join(testapp_dir, file_name), "a") as f: + timestamp = datetime.datetime.now().strftime("%Y_%m_%d-%H_%M_%S") + f.write("\n%s\n%s\n" % (timestamp, summary)) + + +def _summarize_results(testapps, platforms, failures, root_output_dir, artifact_name): + """Logs a readable summary of the results of the build.""" + file_name = "build-results-" + artifact_name + ".log" + + summary = [] + summary.append("BUILD SUMMARY:") + summary.append("TRIED TO BUILD: " + ",".join(testapps)) + summary.append("ON PLATFORMS: " + ",".join(platforms)) + + if not failures: + summary.append("ALL BUILDS SUCCEEDED") + else: + summary.append("SOME ERRORS OCCURRED:") + for i, failure in enumerate(failures, start=1): + summary.append("%d: %s" % (i, failure.describe())) + summary = "\n".join(summary) + + logging.info(summary) + _write_summary(root_output_dir, summary, file_name=file_name) + + summary_json = {} + summary_json["type"] = "build" + summary_json["testapps"] = testapps + summary_json["errors"] = {failure.testapp:failure.error_message for failure in failures} + with open(os.path.join(root_output_dir, file_name+".json"), "a") as f: + f.write(json.dumps(summary_json, indent=2)) + + +def _build_desktop(sdk_dir, cmake_flags): + cmake_configure_cmd = ["cmake", ".", "-DCMAKE_BUILD_TYPE=Debug", + "-DFIREBASE_CPP_SDK_DIR=" + sdk_dir] + if utils.is_windows_os(): + cmake_configure_cmd += ["-A", + "Win32" if FLAGS.arch == "x86" else FLAGS.arch] + elif utils.is_mac_os(): + # Ensure that correct Mac architecture is built. + cmake_configure_cmd += ["-DCMAKE_OSX_ARCHITECTURES=%s" % + ("arm64" if FLAGS.arch == "arm64" else "x86_64")] + + _run(cmake_configure_cmd + cmake_flags) + _run(["cmake", "--build", ".", "--config", "Debug"] + + ["-j", str(FLAGS.jobs)] if FLAGS.jobs > 0 else []) + + +def _get_desktop_compiler_flags(compiler, compiler_table): + """Returns the command line flags for this compiler.""" + if not compiler: # None is an acceptable default value + return [] + try: + return compiler_table[compiler] + except KeyError: + valid_keys = ", ".join(compiler_table.keys()) + raise ValueError( + "Given compiler: %s. Valid compilers: %s" % (compiler, valid_keys)) + + +def _build_android(project_dir, sdk_dir): + """Builds an Android binary (apk).""" + if platform.system() == "Windows": + gradlew = "gradlew.bat" + sdk_dir = sdk_dir.replace("\\", "/") # Gradle misinterprets backslashes. + else: + gradlew = "./gradlew" + logging.info("Patching gradle properties with path to SDK") + gradle_properties = os.path.join(project_dir, "gradle.properties") + with open(gradle_properties, "a+") as f: + f.write("systemProp.firebase_cpp_sdk.dir=" + sdk_dir + "\n") + f.write("http.keepAlive=false\n") + f.write("maven.wagon.http.pool=false\n") + f.write("maven.wagon.httpconnectionManager.ttlSeconds=120") + # This will log the versions of dependencies for debugging purposes. + _run([gradlew, "dependencies", "--configuration", "debugCompileClasspath",]) + _run([gradlew, "assembleDebug", "--stacktrace"]) + + +def _validate_android_environment_variables(): + """Checks environment variables that may be required for Android.""" + # Ultimately we let the gradle build be the source of truth on what env vars + # are required, but try to repair holes and log warnings if we can't. + android_home = os.environ.get(_ANDROID_HOME) + if not os.environ.get(_JAVA_HOME): + logging.warning("%s not set", _JAVA_HOME) + if not os.environ.get(_ANDROID_SDK_HOME): + if android_home: # Use ANDROID_HOME as backup for ANDROID_SDK_HOME + os.environ[_ANDROID_SDK_HOME] = android_home + logging.info("%s not found, using %s", _ANDROID_SDK_HOME, _ANDROID_HOME) + else: + logging.warning("Missing: %s and %s", _ANDROID_SDK_HOME, _ANDROID_HOME) + # Different environments may have different NDK env vars specified. We look + # for these, in this order, and set the others to the first found. + # If none are set, we check the default location for the ndk. + ndk_path = None + ndk_vars = [_NDK_ROOT, _ANDROID_NDK_HOME] + for env_var in ndk_vars: + val = os.environ.get(env_var) + if val: + ndk_path = val + break + if not ndk_path: + if android_home: + default_ndk_path = os.path.join(android_home, "ndk-bundle") + if os.path.isdir(default_ndk_path): + ndk_path = default_ndk_path + if ndk_path: + logging.info("Found ndk: %s", ndk_path) + for env_var in ndk_vars: + if os.environ.get(env_var) != ndk_path: + logging.info("Setting %s to %s", env_var, ndk_path) + os.environ[env_var] = ndk_path + else: + logging.warning("No NDK env var set. Set one of %s", ", ".join(ndk_vars)) + +# build required ios xcframeworks based on makefiles +# the xcframeworks locates at repo_dir/ios_build +def _build_xcframework_from_repo(repo_dir, apple_platform, testapps, config): + """Builds xcframework from SDK source.""" + output_path = os.path.join(repo_dir, apple_platform + "_build") + _rm_dir_safe(output_path) + xcframework_builder = os.path.join( + repo_dir, "scripts", "gha", "build_ios_tvos.py") + + # build only required targets to save time + target = set() + for testapp in testapps: + api_config = config.get_api(testapp) + if apple_platform == "ios" or (apple_platform == "tvos" and api_config.tvos_target): + for framework in api_config.frameworks: + # firebase_analytics.framework -> firebase_analytics + target.add(os.path.splitext(framework)[0]) + + # firebase is not a target in CMake, firebase_app is the target + # firebase_app will be built by other target as well + target.remove("firebase") + + framework_builder_args = [ + sys.executable, xcframework_builder, + "-b", output_path, + "-s", repo_dir, + "-o", apple_platform, + "-t" + ] + framework_builder_args.extend(target) + _run(framework_builder_args) + + +def _build_apple( + sdk_dir, xcframework_exist, project_dir, repo_dir, api_config, + target, scheme, apple_platfrom, apple_sdk): + """Builds an iOS application (.app, .ipa or both).""" + build_dir = apple_platfrom.lower() + "_build" + if not xcframework_exist: + sdk_dir = os.path.join(repo_dir, build_dir) + + build_dir = os.path.join(project_dir, build_dir) + os.makedirs(build_dir) + + logging.info("Copying XCFrameworks") + framework_src_dir = os.path.join(sdk_dir, "xcframeworks") + framework_paths = [] # Paths to the copied frameworks. + for framework in api_config.frameworks: + framework_src_path = os.path.join(framework_src_dir, framework) + framework_dest_path = os.path.join(project_dir, "Frameworks", framework) + dir_util.copy_tree(framework_src_path, framework_dest_path) + framework_paths.append(framework_dest_path) + + _run(["pod", "install"]) + + entitlements_path = os.path.join( + project_dir, api_config.ios_target + ".entitlements") + xcode_tool_path = os.path.join( + repo_dir, "scripts", "gha", "integration_testing", "xcode_tool.rb") + xcode_patcher_args = [ + "ruby", xcode_tool_path, + "--XCodeCPP.xcodeProjectDir", project_dir, + "--XCodeCPP.target", target, + "--XCodeCPP.frameworks", ",".join(framework_paths) + ] + # Internal integration tests require the SDK root as an include path. + if repo_dir and api_config.internal_testapp_path: + xcode_patcher_args.extend(("--XCodeCPP.include", repo_dir)) + if os.path.isfile(entitlements_path): # Not all testapps require entitlements + logging.info("Entitlements file detected.") + xcode_patcher_args.extend(("--XCodeCPP.entitlement", entitlements_path)) + else: + logging.info("No entitlements found at %s.", entitlements_path) + _run(xcode_patcher_args) + + xcode_path = os.path.join(project_dir, "integration_test.xcworkspace") + if _APPLE_SDK_SIMULATOR in apple_sdk: + _run( + xcodebuild.get_args_for_build( + path=xcode_path, + scheme=scheme, + output_dir=build_dir, + apple_platfrom=apple_platfrom, + apple_sdk=_APPLE_SDK_SIMULATOR, + configuration="Debug")) + + if _APPLE_SDK_DEVICE in apple_sdk: + _run( + xcodebuild.get_args_for_build( + path=xcode_path, + scheme=scheme, + output_dir=build_dir, + apple_platfrom=apple_platfrom, + apple_sdk=_APPLE_SDK_DEVICE, + configuration="Debug")) + + xcodebuild.generate_unsigned_ipa( + output_dir=build_dir, configuration="Debug") + + +# This should be executed before performing any builds. +def _run_setup_script(root_dir, testapp_dir): + """Runs the setup_integration_tests.py script.""" + # This script will download gtest to its own directory. + # The CMake projects were configured to download gtest, but this was + # found to be flaky and errors didn't propagate up the build system + # layers. The workaround is to download gtest with this script and copy it. + downloader_dir = os.path.join(root_dir, "testing", "test_framework") + _run([sys.executable, os.path.join(downloader_dir, "download_googletest.py")]) + # Copies shared test framework files into the project, including gtest. + script_path = os.path.join(root_dir, "setup_integration_tests.py") + _run([sys.executable, script_path, testapp_dir]) + + +def _run(args, timeout=_DEFAULT_RUN_TIMEOUT_SECONDS, capture_output=False, text=None, check=True): + """Executes a command in a subprocess.""" + logging.info("Running in subprocess: %s", " ".join(args)) + return subprocess.run( + args=args, + timeout=timeout, + capture_output=capture_output, + text=text, + check=check) + + +def _handle_readonly_file(func, path, excinfo): + """Function passed into shutil.rmtree to handle Access Denied error""" + os.chmod(path, stat.S_IWRITE) + func(path) # will re-throw if a different error occurrs + + +def _rm_dir_safe(directory_path): + """Removes directory at given path. No error if dir doesn't exist.""" + logging.info("Deleting %s...", directory_path) + try: + shutil.rmtree(directory_path, onerror=_handle_readonly_file) + except OSError as e: + # There are two known cases where this can happen: + # The directory doesn't exist (FileNotFoundError) + # A file in the directory is open in another process (PermissionError) + logging.warning("Failed to remove directory:\n%s", e.strerror) + + +def _fix_path(path): + """Expands ~, normalizes slashes, and converts relative paths to absolute.""" + return os.path.abspath(os.path.expanduser(path)) + + +@attr.s(frozen=True, eq=False) +class Failure(object): + """Holds context for the failure of a testapp to build/run.""" + testapp = attr.ib() + platform = attr.ib() + error_message = attr.ib() + + def describe(self): + return "%s, %s: %s" % (self.testapp, self.platform, self.error_message) + + +if __name__ == "__main__": + flags.mark_flag_as_required("testapps") + flags.mark_flag_as_required("platforms") + flags.mark_flag_as_required("packaged_sdk") + app.run(main) diff --git a/scripts/build_scripts/config_reader.py b/scripts/build_scripts/config_reader.py new file mode 100644 index 00000000..a24f5d7a --- /dev/null +++ b/scripts/build_scripts/config_reader.py @@ -0,0 +1,139 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A utility for working with testapp builder JSON files. + +This module handles loading the central configuration file for a testapp +builder, returning a 'Config' object that exposes all the data. + +The motivation for loading the config into a class as opposed to returning +the loaded JSON directly is to validate the data upfront, to fail fast if +anything is missing or formatted incorrectly. + +Example of such a configuration file: + +{ + "apis": [ + { + "name": "analytics", + "full_name": "FirebaseAnalytics", + "bundle_id": "com.google.ios.analytics.testapp", + "ios_target": "testapp", + "tvos_target": "", + "testapp_path": "analytics/testapp", + "frameworks": [ + "firebase_analytics.framework", + "firebase.framework" + ], + "provision": "Google_Development.mobileprovision" + }, + { + "name": "admob", + "full_name": "FirebaseAdmob", + "bundle_id": "com.google.ios.admob.testapp", + "ios_target": "testapp", + "tvos_target": "", + "testapp_path": "admob/testapp", + "frameworks": [ + "firebase_admob.framework", + "firebase.framework" + ], + "provision": "Google_Development.mobileprovision" + } + ], + "dev_team": "ABCDEFGHIJK" +} + +""" + +import json +import os +import pathlib + +import attr + +_DEFAULT_CONFIG_NAME = "build_testapps.json" + + +def read_config(path=None): + """Creates an in-memory 'Config' object out of a testapp config file. + + Args: + path (str): Path to a testapp builder config file. If not specified, will + look for 'build_testapps.json' in the same directory as this file. + + Returns: + Config: All of the testapp builder's configuration. + + """ + if not path: + directory = pathlib.Path(__file__).parent.absolute() + path = os.path.join(directory, _DEFAULT_CONFIG_NAME) + with open(path, "r") as config: + config = json.load(config) + api_configs = dict() + try: + for api in config["apis"]: + api_name = api["name"] + api_configs[api_name] = APIConfig( + name=api_name, + full_name=api["full_name"], + bundle_id=api["bundle_id"], + ios_target=api["ios_target"], + tvos_target=api["tvos_target"], + ios_scheme=api["ios_target"], # Scheme assumed to be same as target. + tvos_scheme=api["tvos_target"], + testapp_path=api["testapp_path"], + internal_testapp_path=api.get("internal_testapp_path", None), + frameworks=api["frameworks"], + provision=api["provision"], + minify=api.get("minify", None)) + return Config( + apis=api_configs, + compilers=config["compiler_dict"]) + except (KeyError, TypeError, IndexError): + # The error will be cryptic on its own, so we dump the JSON to + # offer context, then reraise the error. + print( + "Error occurred while parsing config. Full config dump:\n" + + json.dumps(config, sort_keys=True, indent=4, separators=(",", ":"))) + raise + + +@attr.s(frozen=True, eq=False) +class Config(object): + apis = attr.ib() # Mapping of str: APIConfig + compilers = attr.ib() + + def get_api(self, api): + """Returns the APIConfig object for the given api, e.g. 'analytics'.""" + return self.apis[api] + + +@attr.s(frozen=True, eq=False) +class APIConfig(object): + """Holds all the configuration for a single testapp project.""" + name = attr.ib() + full_name = attr.ib() + bundle_id = attr.ib() + ios_target = attr.ib() + tvos_target = attr.ib() + ios_scheme = attr.ib() + tvos_scheme = attr.ib() + testapp_path = attr.ib() # testapp dir relative to sdk root + internal_testapp_path = attr.ib() # Internal testdir dir relative to sdk root + frameworks = attr.ib() # Required custom xcode frameworks + provision = attr.ib() # Path to the local mobile provision + minify = attr.ib() # (Optional) Android minification. + diff --git a/scripts/build_scripts/python_requirements.txt b/scripts/build_scripts/python_requirements.txt new file mode 100644 index 00000000..214a8aac --- /dev/null +++ b/scripts/build_scripts/python_requirements.txt @@ -0,0 +1,2 @@ +attrs +absl-py diff --git a/scripts/build_scripts/utils.py b/scripts/build_scripts/utils.py new file mode 100644 index 00000000..631dc8df --- /dev/null +++ b/scripts/build_scripts/utils.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +# Copyright 2022 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Helper functions that are shared amongst prereqs and build scripts across various +platforms. +""" + +import distutils.spawn +import platform +import shutil +import subprocess +import os +import urllib.request + +def run_command(cmd, capture_output=False, cwd=None, check=False, as_root=False, + print_cmd=True): + """Run a command. + + Args: + cmd (list(str)): Command to run as a list object. + Eg: ['ls', '-l']. + capture_output (bool): Capture the output of this command. + Output can be accessed as .stdout + cwd (str): Directory to execute the command from. + check (bool): Raises a CalledProcessError if True and the command errored out + as_root (bool): Run command as root user with admin priveleges (supported on mac and linux). + print_cmd (bool): Print the command we are running to stdout. + + Raises: + (subprocess.CalledProcessError): If command errored out and `text=True` + + Returns: + (`subprocess.CompletedProcess`): object containing information from + command execution + """ + + if as_root and (is_mac_os() or is_linux_os()): + cmd.insert(0, 'sudo') + + cmd_string = ' '.join(cmd) + if print_cmd: + print('Running cmd: {0}\n'.format(cmd_string)) + # If capture_output is requested, we also set text=True to store the returned value of the + # command as a string instead of bytes object + return subprocess.run(cmd, capture_output=capture_output, cwd=cwd, + check=check, text=capture_output) + + +def is_command_installed(tool): + """Check if a command is installed on the system.""" + return distutils.spawn.find_executable(tool) + + +def delete_directory(dir_path): + """Recursively delete a valid directory""" + if os.path.exists(dir_path): + shutil.rmtree(dir_path) + + +def download_file(url, file_path): + """Download from url and save to specified file path.""" + with urllib.request.urlopen(url) as response, open(file_path, 'wb') as out_file: + shutil.copyfileobj(response, out_file) + + +def unpack_files(archive_file_path, output_dir=None): + """Unpack/extract an archive to specified output_directory""" + shutil.unpack_archive(archive_file_path, output_dir) + + +def is_windows_os(): + return platform.system() == 'Windows' + + +def is_mac_os(): + return platform.system() == 'Darwin' + + +def is_linux_os(): + return platform.system() == 'Linux' diff --git a/scripts/build_scripts/xcodebuild.py b/scripts/build_scripts/xcodebuild.py new file mode 100644 index 00000000..b60c70dc --- /dev/null +++ b/scripts/build_scripts/xcodebuild.py @@ -0,0 +1,119 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helper module for working with xcode projects. + +The tool xcodebuild provides support to build xcode projects from the command +line. The motivation was to simplify usage of xcodebuild, since it was non-trivial +to figure out which flags were needed to get it working in a CI environment. +The options required by the methods in this module were found to work both +locally and on CI, with both the Unity and C++ projects. + +get_args_for_build() method doesn't performing operations with xcodebuild directly, +this module returns arg sequences. These sequences can be passed to e.g. +subprocess.run to execute the operations. + +get_args_for_build() support either device or simulator builds. For simulator +builds, it suffices to use get_args_for_build() to create a .app that can be +used with simulators. For unsigned device builds, generate .app via +get_args_for_build() step and then use generate_unsigned_ipa() to package +the .app to .ipa. + +""" + +import os +import shutil + +def get_args_for_build( + path, scheme, output_dir, apple_platfrom, apple_sdk, configuration): + """Constructs subprocess args for an unsigned xcode build. + + Args: + path (str): Full path to the project or workspace to build. Must end in + either .xcodeproj or .xcworkspace. + scheme (str): Name of the scheme to build. + output_dir (str): Directory for the resulting build artifacts. Will be + created if it doesn't already exist. + apple_platfrom (str): iOS or tvOS. + apple_sdk (str): Where this build will be run: real device or virtual device (simulator). + configuration (str): Value for the -configuration flag. + + Returns: + Sequence of strings, corresponding to valid args for a subprocess call. + + """ + args = [ + "xcodebuild", + "-sdk", _get_apple_env_from_target(apple_platfrom, apple_sdk), + "-scheme", scheme, + "-configuration", configuration, + "-quiet", + "BUILD_DIR=" + output_dir + ] + + if apple_sdk == "real": + args.extend(['CODE_SIGN_IDENTITY=""', + "CODE_SIGNING_REQUIRED=NO", + "CODE_SIGNING_ALLOWED=NO"]) + elif apple_sdk == "virtual" and apple_platfrom == "tvOS": + args.extend(['-arch', "x86_64"]) + + if not path: + raise ValueError("Must supply a path.") + if path.endswith(".xcworkspace"): + args.extend(("-workspace", path)) + elif path.endswith(".xcodeproj"): + args.extend(("-project", path)) + else: + raise ValueError("Path must end with .xcworkspace or .xcodeproj: %s" % path) + return args + + +def _get_apple_env_from_target(apple_platfrom, apple_sdk): + """Return a value for the -sdk flag based on the target (device/simulator).""" + if apple_platfrom == "iOS": + if apple_sdk == "real": + return "iphoneos" + elif apple_sdk == "virtual": + return "iphonesimulator" + else: + raise ValueError("Unrecognized apple_sdk: %s" % apple_sdk) + elif apple_platfrom == "tvOS": + if apple_sdk == "real": + return "appletvos" + elif apple_sdk == "virtual": + return "appletvsimulator" + else: + raise ValueError("Unrecognized apple_sdk: %s" % apple_sdk) + else: + raise ValueError("Unrecognized apple_sdk: %s" % apple_sdk) + + +def generate_unsigned_ipa(output_dir, configuration): + """create unsigned .ipa from .app, then remove .app afterwards + + Args: + output_dir (str): Same value as get_args_for_build. generated unsigned .ipa + will be placed within the subdirectory "Debug-iphoneos" or "Release-iphoneos". + configuration (str): Same value as get_args_for_build. + """ + iphone_build_dir = os.path.join(output_dir, configuration + "-iphoneos") + payload_path = os.path.join(iphone_build_dir, "Payload") + app_path = os.path.join(iphone_build_dir, "integration_test.app") + ipa_path = os.path.join(iphone_build_dir, "integration_test.ipa") + os.mkdir(payload_path) + shutil.move(app_path, payload_path) + shutil.make_archive(payload_path, 'zip', root_dir=iphone_build_dir, base_dir='Payload') + shutil.move('%s.%s'%(payload_path, 'zip'), ipa_path) + shutil.rmtree(payload_path) diff --git a/scripts/gha-encrypted/README b/scripts/gha-encrypted/README new file mode 100644 index 00000000..e73cd287 --- /dev/null +++ b/scripts/gha-encrypted/README @@ -0,0 +1,3 @@ +See https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets + +Googlers: code search firebase/cpp/Secrets to find the sources. \ No newline at end of file diff --git a/scripts/gha-encrypted/analytics/GoogleService-Info.plist.gpg b/scripts/gha-encrypted/analytics/GoogleService-Info.plist.gpg new file mode 100644 index 00000000..a7fad00c Binary files /dev/null and b/scripts/gha-encrypted/analytics/GoogleService-Info.plist.gpg differ diff --git a/scripts/gha-encrypted/analytics/google-services.json.gpg b/scripts/gha-encrypted/analytics/google-services.json.gpg new file mode 100644 index 00000000..8e2a09d3 Binary files /dev/null and b/scripts/gha-encrypted/analytics/google-services.json.gpg differ diff --git a/scripts/gha-encrypted/auth/GoogleService-Info.plist.gpg b/scripts/gha-encrypted/auth/GoogleService-Info.plist.gpg new file mode 100644 index 00000000..896aeabf --- /dev/null +++ b/scripts/gha-encrypted/auth/GoogleService-Info.plist.gpg @@ -0,0 +1,3 @@ +  ᗩ9p0E,uv%|{G+:q{y9Նz J{;.P^]pnę] -2Oe;0żC f]fۇ* +"*{t?eigW}2f)1gf69>r(goO0d-z9cK @)Kg~)T'm~H/>X64Oئ{5N-V/sw"Df>%>wNAǽ@\B"\Dp35i` ܨ̺哐ʡvM:K!\ٌ<6XNj|MIv̶R51Bd4 'x(A15JH6{H@/ |IR_M$[i +?s^NTyzc]q.-[Mz ¶O&eSvrlAcR5rK~Ή T +[AbZgcӴպX5AS|ӧm7l{~*K& ) ?8tR狮UL衧q,!7eI}B{iTrHɍ;[EAPV!Dk" 陑dyb"kJՖ \ No newline at end of file diff --git a/scripts/gha-encrypted/auth/google-services.json.gpg b/scripts/gha-encrypted/auth/google-services.json.gpg new file mode 100644 index 00000000..9f86b0a3 Binary files /dev/null and b/scripts/gha-encrypted/auth/google-services.json.gpg differ diff --git a/scripts/gha-encrypted/database/GoogleService-Info.plist.gpg b/scripts/gha-encrypted/database/GoogleService-Info.plist.gpg new file mode 100644 index 00000000..9d7f62a8 Binary files /dev/null and b/scripts/gha-encrypted/database/GoogleService-Info.plist.gpg differ diff --git a/scripts/gha-encrypted/database/google-services.json.gpg b/scripts/gha-encrypted/database/google-services.json.gpg new file mode 100644 index 00000000..a9421faa Binary files /dev/null and b/scripts/gha-encrypted/database/google-services.json.gpg differ diff --git a/scripts/gha-encrypted/dynamic_links/GoogleService-Info.plist.gpg b/scripts/gha-encrypted/dynamic_links/GoogleService-Info.plist.gpg new file mode 100644 index 00000000..7d577bef Binary files /dev/null and b/scripts/gha-encrypted/dynamic_links/GoogleService-Info.plist.gpg differ diff --git a/scripts/gha-encrypted/dynamic_links/google-services.json.gpg b/scripts/gha-encrypted/dynamic_links/google-services.json.gpg new file mode 100644 index 00000000..0db36dbf --- /dev/null +++ b/scripts/gha-encrypted/dynamic_links/google-services.json.gpg @@ -0,0 +1,3 @@ +   3u=ێpAbg3.) zs/@Zrӓ$i^$1>X3OZh%YM t?_C:AiclƎ \񏔸m.IMi?9 {S~ZUXt⾘=}ݔV||1M-u_m8cVS]brQ㈃:rE[=ۅ.nj=zU>5h)Zar Pr"8qбo>E 3|nF(KUe5(uo ?CiN_:X>vJ ['g_okL\ IZJٕ'@ȲN? k$-SaCSRqv7 +rlYyRQEx%L蔘%p8y e*/\g oOM'w ܊fY +g"KFra6X׼18?خIu2Bmx>mZ;V2zy4LuiД;T]̄SI brZ*R|*'6ȑ~XSMG| !ˇ2֠Butq!3nr@lVu H &P5KO y}e;eUa">g \ No newline at end of file diff --git a/scripts/gha-encrypted/firestore/GoogleService-Info.plist.gpg b/scripts/gha-encrypted/firestore/GoogleService-Info.plist.gpg new file mode 100644 index 00000000..4ae5fb41 Binary files /dev/null and b/scripts/gha-encrypted/firestore/GoogleService-Info.plist.gpg differ diff --git a/scripts/gha-encrypted/firestore/google-services.json.gpg b/scripts/gha-encrypted/firestore/google-services.json.gpg new file mode 100644 index 00000000..98073347 Binary files /dev/null and b/scripts/gha-encrypted/firestore/google-services.json.gpg differ diff --git a/scripts/gha-encrypted/functions/GoogleService-Info.plist.gpg b/scripts/gha-encrypted/functions/GoogleService-Info.plist.gpg new file mode 100644 index 00000000..6ed5b9d8 Binary files /dev/null and b/scripts/gha-encrypted/functions/GoogleService-Info.plist.gpg differ diff --git a/scripts/gha-encrypted/functions/google-services.json.gpg b/scripts/gha-encrypted/functions/google-services.json.gpg new file mode 100644 index 00000000..c9edac06 Binary files /dev/null and b/scripts/gha-encrypted/functions/google-services.json.gpg differ diff --git a/scripts/gha-encrypted/gma/GoogleService-Info.plist.gpg b/scripts/gha-encrypted/gma/GoogleService-Info.plist.gpg new file mode 100644 index 00000000..65b703ce Binary files /dev/null and b/scripts/gha-encrypted/gma/GoogleService-Info.plist.gpg differ diff --git a/scripts/gha-encrypted/gma/google-services.json.gpg b/scripts/gha-encrypted/gma/google-services.json.gpg new file mode 100644 index 00000000..c46f5964 Binary files /dev/null and b/scripts/gha-encrypted/gma/google-services.json.gpg differ diff --git a/scripts/gha-encrypted/messaging/GoogleService-Info.plist.gpg b/scripts/gha-encrypted/messaging/GoogleService-Info.plist.gpg new file mode 100644 index 00000000..cf95c652 Binary files /dev/null and b/scripts/gha-encrypted/messaging/GoogleService-Info.plist.gpg differ diff --git a/scripts/gha-encrypted/messaging/google-services.json.gpg b/scripts/gha-encrypted/messaging/google-services.json.gpg new file mode 100644 index 00000000..8ad06ccf Binary files /dev/null and b/scripts/gha-encrypted/messaging/google-services.json.gpg differ diff --git a/scripts/gha-encrypted/remote_config/GoogleService-Info.plist.gpg b/scripts/gha-encrypted/remote_config/GoogleService-Info.plist.gpg new file mode 100644 index 00000000..ea9dc946 Binary files /dev/null and b/scripts/gha-encrypted/remote_config/GoogleService-Info.plist.gpg differ diff --git a/scripts/gha-encrypted/remote_config/google-services.json.gpg b/scripts/gha-encrypted/remote_config/google-services.json.gpg new file mode 100644 index 00000000..b9dd70a0 Binary files /dev/null and b/scripts/gha-encrypted/remote_config/google-services.json.gpg differ diff --git a/scripts/gha-encrypted/storage/GoogleService-Info.plist.gpg b/scripts/gha-encrypted/storage/GoogleService-Info.plist.gpg new file mode 100644 index 00000000..01d28ec9 Binary files /dev/null and b/scripts/gha-encrypted/storage/GoogleService-Info.plist.gpg differ diff --git a/scripts/gha-encrypted/storage/google-services.json.gpg b/scripts/gha-encrypted/storage/google-services.json.gpg new file mode 100644 index 00000000..57ba134a Binary files /dev/null and b/scripts/gha-encrypted/storage/google-services.json.gpg differ diff --git a/scripts/restore_secrets.py b/scripts/restore_secrets.py new file mode 100644 index 00000000..cd72cf58 --- /dev/null +++ b/scripts/restore_secrets.py @@ -0,0 +1,186 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Script for restoring secrets into the testapp projects. + +Usage: + +python restore_secrets.py --passphrase [--repo_dir ] +python restore_secrets.py --passphrase_file [--repo_dir ] + +--passphrase: Passphrase to decrypt the files. This option is insecure on a + multi-user machine; use the --passphrase_file option instead. +--passphrase_file: Specify a file to read the passphrase from (only reads the + first line). Use "-" (without quotes) for stdin. +--repo_dir: Path to C++ Quickstart Github repository. Defaults to current + directory. +--apis: Specify a list of particular product APIs and retrieve only their + secrets. + +This script will perform the following: + +- Google Service files (plist and json) will be restored into the + testapp directories. +- The reverse id will be patched into all Info.plist files, using the value from + the decrypted Google Service plist files as the source of truth. + +""" + +import os +import plistlib +import subprocess + +from absl import app +from absl import flags + + +FLAGS = flags.FLAGS + +flags.DEFINE_string("repo_dir", os.getcwd(), "Path to C++ SDK Github repo.") +flags.DEFINE_string("passphrase", None, "The passphrase itself.") +flags.DEFINE_string("passphrase_file", None, + "Path to file with passphrase. Use \"-\" (without quotes) for stdin.") +flags.DEFINE_string("artifact", None, "Artifact Path, google-services.json will be placed here.") +flags.DEFINE_list("apis",[], "Optional comma-separated list of APIs for which to retreive " + " secrets. All secrets will be fetched if this is flag is not defined.") + + +def main(argv): + if len(argv) > 1: + raise app.UsageError("Too many command-line arguments.") + + repo_dir = FLAGS.repo_dir + # The passphrase is sensitive, do not log. + if FLAGS.passphrase: + passphrase = FLAGS.passphrase + elif FLAGS.passphrase_file == "-": + passphrase = input() + elif FLAGS.passphrase_file: + with open(FLAGS.passphrase_file, "r") as f: + passphrase = f.readline().strip() + else: + raise ValueError("Must supply passphrase or passphrase_file arg.") + + if FLAGS.apis: + print("Retrieving secrets for product APIs: ", FLAGS.apis) + + secrets_dir = os.path.join(repo_dir, "scripts", "gha-encrypted") + encrypted_files = _find_encrypted_files(secrets_dir) + print("Found these encrypted files:\n%s" % "\n".join(encrypted_files)) + + for path in encrypted_files: + if "google-services" in path or "GoogleService" in path: + # We infer the destination from the file's directory, example: + # /scripts/gha-encrypted/auth/google-services.json.gpg turns into + # //auth/testapp/google-services.json + api = os.path.basename(os.path.dirname(path)) + if FLAGS.apis and api not in FLAGS.apis: + print("Skipping secret found in product api", api) + continue + print("Encrypted Google Service file found: %s" % path) + file_name = os.path.basename(path).replace(".gpg", "") + dest_paths = [os.path.join(repo_dir, api, "testapp", file_name)] + if FLAGS.artifact: + # ///auth/google-services.json + if "google-services" in path and os.path.isdir(os.path.join(repo_dir, FLAGS.artifact, api)): + dest_paths = [os.path.join(repo_dir, FLAGS.artifact, api, file_name)] + else: + continue + + decrypted_text = _decrypt(path, passphrase) + for dest_path in dest_paths: + with open(dest_path, "w") as f: + f.write(decrypted_text) + print("Copied decrypted google service file to %s" % dest_path) + # We use a Google Service file as the source of truth for the reverse id + # that needs to be patched into the Info.plist files. + if dest_path.endswith(".plist"): + _patch_reverse_id(dest_path) + _patch_bundle_id(dest_path) + + if FLAGS.artifact: + return + +def _find_encrypted_files(directory_to_search): + """Returns a list of full paths to all files encrypted with gpg.""" + encrypted_files = [] + for prefix, _, files in os.walk(directory_to_search): + for relative_path in files: + if relative_path.endswith(".gpg"): + encrypted_files.append(os.path.join(prefix, relative_path)) + return encrypted_files + + +def _decrypt(encrypted_file, passphrase): + """Returns the decrypted contents of the given .gpg file.""" + print("Decrypting %s" % encrypted_file) + # Note: if setting check=True, be sure to catch the error and not rethrow it + # or print a traceback, as the message will include the passphrase. + result = subprocess.run( + args=[ + "gpg", + "--passphrase", passphrase, + "--quiet", + "--batch", + "--yes", + "--decrypt", + encrypted_file], + check=False, + text=True, + capture_output=True) + if result.returncode: + # Remove any instances of the passphrase from error before logging it. + raise RuntimeError(result.stderr.replace(passphrase, "****")) + print("Decryption successful") + # rstrip to eliminate a linebreak that GPG may introduce. + return result.stdout.rstrip() + + +def _patch_reverse_id(service_plist_path): + """Patches the Info.plist file with the reverse id from the Service plist.""" + print("Attempting to patch reverse id in Info.plist") + with open(service_plist_path, "rb") as f: + service_plist = plistlib.load(f) + _patch_file( + path=os.path.join(os.path.dirname(service_plist_path), "testapp", "Info.plist"), + placeholder="REPLACE_WITH_REVERSED_CLIENT_ID", + value=service_plist["REVERSED_CLIENT_ID"]) + + +def _patch_bundle_id(service_plist_path): + """Patches the Info.plist file with the bundle id from the Service plist.""" + print("Attempting to patch bundle id in Info.plist") + with open(service_plist_path, "rb") as f: + service_plist = plistlib.load(f) + _patch_file( + path=os.path.join(os.path.dirname(service_plist_path), "testapp", "Info.plist"), + placeholder="$(PRODUCT_BUNDLE_IDENTIFIER)", + value=service_plist["BUNDLE_ID"]) + + +def _patch_file(path, placeholder, value): + """Patches instances of the placeholder with the given value.""" + # Note: value may be sensitive, so do not log. + with open(path, "r") as f_read: + text = f_read.read() + # Count number of times placeholder appears for debugging purposes. + replacements = text.count(placeholder) + patched_text = text.replace(placeholder, value) + with open(path, "w") as f_write: + f_write.write(patched_text) + print("Patched %d instances of %s in %s" % (replacements, placeholder, path)) + + +if __name__ == "__main__": + app.run(main) diff --git a/storage/testapp/AndroidManifest.xml b/storage/testapp/AndroidManifest.xml index d5428bbe..253ce8c0 100644 --- a/storage/testapp/AndroidManifest.xml +++ b/storage/testapp/AndroidManifest.xml @@ -6,9 +6,10 @@ - + sign_in_future = + firebase::Future sign_in_future = auth->SignInAnonymously(); WaitForCompletion(sign_in_future, "SignInAnonymously"); if (sign_in_future.error() == firebase::auth::kAuthErrorNone) { diff --git a/storage/testapp/testapp.xcodeproj/project.pbxproj b/storage/testapp/testapp.xcodeproj/project.pbxproj index 6c325725..5769362c 100644 --- a/storage/testapp/testapp.xcodeproj/project.pbxproj +++ b/storage/testapp/testapp.xcodeproj/project.pbxproj @@ -208,7 +208,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -245,7 +245,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2";