SDKBOX gpg services integration into Cocos2dx Android Studio, Build fail: Error:error: ld returned 1 exit status

I have configured cocos2dx into Android Studio works perfectly fine (NDK 15 latest update). Integrated SDKBOX with the command $ sdkbox import gpg ran on my root folder of cocos2dx project. Integration seemed working fine and sdkbox::PluginGPG::init(); inside my AppDelegate::applicationDidFinishLaunching() didnt seem to fire any exceptions, build and ran my game successfully. It was only after I tried to implement gpg Real-Time Multiplayer in a class which I DIDNT actually use that I started getting compile time error: Error:error: ld returned 1 exit status the full stack is as follows:

Information:Gradle tasks [:mProject:assembleDebug]
Error:error: ld returned 1 exit status
Information:BUILD FAILED
Information:Total time: 2 mins 9.195 secs
Information:1 error
Information:0 warnings
Information:See complete output in console

Simple enough right? But no matter what I tried I couldnt get rid of the build error. Tried rebuilding, checking the integrity of SDKBOX by following the manual Android Studio installation, no luck. Following are my new classes which cause compilation errors: (very similar to official C++ samples)

AMSMPNativeActivity.cpp

//
// Created by Ams on 9/1/2017.
//

//#include <platform/CCStdC.h>
//#include <base/CCConsole.h>
#include "AMSMPNativeActivity.h"



void AMSMPNativeActivity::InitGooglePlayGameServices() {
    if (service_ != nullptr) {
        return;
    }

    gpg::AndroidInitialization::android_main(app_);

    // Game Services have not been initialized, create a new Game Services.
    gpg::AndroidPlatformConfiguration platform_configuration;
    platform_configuration.SetActivity(app_->activity->clazz);

    gpg::GameServices::Builder builder;
    service_ =
            builder.SetOnAuthActionStarted([this](gpg::AuthOperation op) {
                        // This callback is invoked when auth
                        // action started
                        // While auth action is going on, disable
                        // auth and related UI
                        OnAuthActionStarted(op);
                    })
                    .SetOnAuthActionFinished([this](gpg::AuthOperation op,
                                                    gpg::AuthStatus status) {
                        // This callback is invoked when auth action finished
                        // Check status code and update UI to signed-in state
                        OnAuthActionFinished(op, status);
                    })
                    .SetOnMultiplayerInvitationEvent([this](
                            gpg::MultiplayerEvent event, std::string match_id,
                            gpg::MultiplayerInvitation invitation) {
                        // Invoked when invitation has been received
                        // It can be received from the Play Game app, a notification, or
                        // live while the app is running.
                        // (e.g. Though PlayGam app, while the app is running)
                        //CCLOG("MultiplayerInvitationEvent callback");

                        if (event ==
                            gpg::TurnBasedMultiplayerEvent::UPDATED_FROM_APP_LAUNCH) {

                            // In this case, an invitation has been accepted already
                            // in notification or in Play game app
                            gpg::RealTimeMultiplayerManager::RealTimeRoomResponse result =
                                    service_->RealTimeMultiplayer().AcceptInvitationBlocking(
                                            invitation, this);
                            if (gpg::IsSuccess(result.status)) {
                                room_ = result.room;
                                service_->RealTimeMultiplayer().ShowWaitingRoomUI(
                                        room_, MIN_PLAYERS,
                                        [this](gpg::RealTimeMultiplayerManager::
                                               WaitingRoomUIResponse const &waitResult) {
                                            EnableUI(true);
                                            if (gpg::IsSuccess(waitResult.status)) {
                                                PlayGame();
                                            }
                                        });
                            } else {
                                LeaveGame();
                            }
                        } else {
                            // Otherwise, show default inbox and let players to accept an
                            // invitation
                            ShowRoomInbox();
                        }
                    })
                    .Create(platform_configuration);
}


void AMSMPNativeActivity::OnAuthActionStarted(gpg::AuthOperation op) {
    if(!initialized_resources_) {
        return;
    }

    // do whatever when signing in

    /*ndk_helper::JNIHelper::GetInstance()->RunOnUiThread([this, op]() {
        EnableUI(false);
        if (op == gpg::AuthOperation::SIGN_IN) {
            status_text_->SetAttribute("Text", "Signing In...");
        } else {
            status_text_->SetAttribute("Text", "Signing Out...");
        }
    });*/
}


void AMSMPNativeActivity::OnAuthActionFinished(gpg::AuthOperation op,
                                  gpg::AuthStatus status) {
    if (gpg::IsSuccess(status)) {
        service_->Players().FetchSelf([this](
                gpg::PlayerManager::FetchSelfResponse const &response) {
            if (gpg::IsSuccess(response.status)) {
                self_id_ = response.data.Id();
            }
        });
    }

    if(!initialized_resources_) {
        return;
    }

}


void AMSMPNativeActivity::ShowRoomInbox() {
    service_->RealTimeMultiplayer().ShowRoomInboxUI([this](
            gpg::RealTimeMultiplayerManager::RoomInboxUIResponse const &response) {
        if (gpg::IsSuccess(response.status)) {
            gpg::RealTimeMultiplayerManager::RealTimeRoomResponse result =
                    service_->RealTimeMultiplayer().AcceptInvitationBlocking(
                            response.invitation, this);
            if (gpg::IsSuccess(result.status)) {
                room_ = result.room;
                service_->RealTimeMultiplayer().ShowWaitingRoomUI(
                        room_, MIN_PLAYERS,
                        [this](
                                gpg::RealTimeMultiplayerManager::WaitingRoomUIResponse const &
                                waitResult) {
                            EnableUI(true);
                            if (gpg::IsSuccess(waitResult.status)) {
                                PlayGame();
                            }
                        });
            } else
                EnableUI(true);  // Go back to original state for retry
        } else {
            //CCLOG("Invalid response status");
            EnableUI(true);  // Go back to original state for retry
        }
    });
    EnableUI(false);
}


void AMSMPNativeActivity::QuickMatch() {
    gpg::RealTimeRoomConfig config =
            gpg::RealTimeRoomConfig::Builder()
                    .SetMinimumAutomatchingPlayers(MIN_PLAYERS)
                    .SetMaximumAutomatchingPlayers(MAX_PLAYERS)
                    .Create();

    service_->RealTimeMultiplayer().CreateRealTimeRoom(
            config, this,
            [this](gpg::RealTimeMultiplayerManager::RealTimeRoomResponse const &
            response) {
                //CCLOG("created a room %d", response.status);
                if (gpg::IsSuccess(response.status)) {
                    room_ = response.room;
                    service_->RealTimeMultiplayer().ShowWaitingRoomUI(
                            room_, MIN_PLAYERS,
                            [this](
                                    gpg::RealTimeMultiplayerManager::WaitingRoomUIResponse const &
                                    waitResult) {
                                EnableUI(true);
                                if (gpg::IsSuccess(waitResult.status)) {
                                    PlayGame();
                                }
                            });
                } else
                    EnableUI(true);  // Go back to original state for retry
            });
    EnableUI(false);
}


void AMSMPNativeActivity::InviteFriend() {
    service_->RealTimeMultiplayer().ShowPlayerSelectUI(
            MIN_PLAYERS, MAX_PLAYERS, true,
            [this](gpg::RealTimeMultiplayerManager::PlayerSelectUIResponse const &
            response) {
                //CCLOG("inviting friends %d", response.status);
                if (gpg::IsSuccess(response.status)) {
                    // Create room
                    gpg::RealTimeRoomConfig config =
                            gpg::RealTimeRoomConfig::Builder()
                                    .PopulateFromPlayerSelectUIResponse(response)
                                    .Create();

                    auto roomResponse =
                            service_->RealTimeMultiplayer().CreateRealTimeRoomBlocking(config,
                                                                                       this);
                    if (gpg::IsSuccess(roomResponse.status)) {
                        room_ = roomResponse.room;
                        service_->RealTimeMultiplayer().ShowWaitingRoomUI(
                                room_, MIN_PLAYERS,
                                [this](gpg::RealTimeMultiplayerManager::
                                       WaitingRoomUIResponse const &waitResult) {
                                    EnableUI(true);
                                    if (gpg::IsSuccess(waitResult.status)) {
                                        PlayGame();
                                    }
                                });
                    } else
                        EnableUI(true);  // Go back to original state for retry
                } else
                    EnableUI(true);  // Go back to original state for retry
            });
    EnableUI(false);
}

void AMSMPNativeActivity::BroadcastScore(bool bFinal) {
    std::vector<uint8_t> v;
    if (!bFinal) {
        v.push_back('U');
        v.push_back(static_cast<uint8_t>(score_counter_));
        service_->RealTimeMultiplayer().SendUnreliableMessageToOthers(room_, v);
    } else {
        v.push_back('F');
        v.push_back(static_cast<uint8_t>(score_counter_));

        const std::vector<gpg::MultiplayerParticipant> participants =
                room_.Participants();
        for (gpg::MultiplayerParticipant participant : participants) {
            service_->RealTimeMultiplayer().SendReliableMessage(
                    room_, participant, v, [](gpg::MultiplayerStatus const &) {});
        }
    }
}


void AMSMPNativeActivity::OnDataReceived(gpg::RealTimeRoom const &room,
                            gpg::MultiplayerParticipant const &from_participant,
                            std::vector<uint8_t> data, bool is_reliable) {
    if (data[0] == 'F' && is_reliable) {
        // Got final score
        players_score_[from_participant.Id()].score = data[1];
        players_score_[from_participant.Id()].finished = true;
        //CCLOG("Got final data from Dispname:%s ID:%s",
             //from_participant.DisplayName().c_str(), from_participant.Id().c_str());
    } else if (data[0] == 'U' && !is_reliable) {
        // Got current score
        uint8_t score = players_score_[from_participant.Id()].score;
        players_score_[from_participant.Id()].score = std::max(score, data[1]);
        //CCLOG("Got data from Dispname:%s ID:%s",
        //     from_participant.DisplayName().c_str(), from_participant.Id().c_str());
    }
    UpdateScore();
}

void AMSMPNativeActivity::OnRoomStatusChanged(gpg::RealTimeRoom const &room) {
    room_ = room;
}

void AMSMPNativeActivity::OnParticipantStatusChanged(
        gpg::RealTimeRoom const &room,
        gpg::MultiplayerParticipant const &participant) {

  
    if (participant.Status() != gpg::ParticipantStatus::JOINED) {
        {
            std::lock_guard<std::mutex> lock(mutex_);
            if (players_score_.find(participant.Id()) != players_score_.end()) {
                players_score_[participant.Id()].finished = true;
            }
        }
        UpdateScore();
    }
}


void AMSMPNativeActivity::PlayGame() {
   
}


void AMSMPNativeActivity::UpdateScore() {
    
    std::lock_guard<std::mutex> lock(mutex_);

    int32_t SIZE = 64;
    char str[SIZE];
    snprintf(str, SIZE, "%03d", score_counter_);
    std::string str_myscore(str);

    snprintf(str, SIZE, "My score: %03d %s\n", score_counter_,
             playing_ ? "" : "*");
    std::string allstr(str);

    std::vector<gpg::MultiplayerParticipant> participants = room_.Participants();
    for (gpg::MultiplayerParticipant participant : participants) {
        
        
        if (participant.HasPlayer() &&
            participant.Player().Id().compare(self_id_) == 0)
            continue;  // Skip local player

        int32_t score = 0;
        bool finished = false;
        if (players_score_.find(participant.Id()) != players_score_.end()) {
            score = players_score_[participant.Id()].score;
            finished = players_score_[participant.Id()].finished;
        }

        snprintf(str, SIZE, "%s: %03d %s\n", participant.DisplayName().c_str(),
                 score, finished ? "*" : "");
        allstr += str;
    }

   
}

void AMSMPNativeActivity::LeaveGame() {

    std::lock_guard<std::mutex> lock(mutex_);

    service_->RealTimeMultiplayer().LeaveRoom(
            room_, [](gpg::ResponseStatus const &status) {});

    playing_ = false;
}







void AMSMPNativeActivity::EnableUI(bool enable) {
   
}


extern "C" {



JNIEXPORT void
Java_com_google_example_games_ButtonClicker_ButtonClickerNativeActivity_nativeOnActivityResult(
        JNIEnv *env, jobject thiz, jobject activity, jint requestCode,
        jint resultCode, jobject data) {
    gpg::AndroidSupport::OnActivityResult(env, activity, requestCode, resultCode,
                                          data);
}

JNIEXPORT void
Java_com_google_example_games_ButtonClicker_ButtonClickerNativeActivity_nativeOnActivityCreated(
        JNIEnv *env, jobject thiz, jobject activity, jobject saved_instance_state) {
    gpg::AndroidSupport::OnActivityCreated(env, activity, saved_instance_state);
}

JNIEXPORT void
Java_com_google_example_games_ButtonClicker_ButtonClickerNativeActivity_nativeOnActivityDestroyed(
        JNIEnv *env, jobject thiz, jobject activity) {
    gpg::AndroidSupport::OnActivityDestroyed(env, activity);
}

JNIEXPORT void
Java_com_google_example_games_ButtonClicker_ButtonClickerNativeActivity_nativeOnActivityPaused(
        JNIEnv *env, jobject thiz, jobject activity) {
    gpg::AndroidSupport::OnActivityPaused(env, activity);
}

JNIEXPORT void
Java_com_google_example_games_ButtonClicker_ButtonClickerNativeActivity_nativeOnActivityResumed(
        JNIEnv *env, jobject thiz, jobject activity) {
    gpg::AndroidSupport::OnActivityResumed(env, activity);
}

JNIEXPORT void
Java_com_google_example_games_ButtonClicker_ButtonClickerNativeActivity_nativeOnActivitySaveInstanceState(
        JNIEnv *env, jobject thiz, jobject activity, jobject out_state) {
    gpg::AndroidSupport::OnActivitySaveInstanceState(env, activity, out_state);
}

JNIEXPORT void
Java_com_google_example_games_ButtonClicker_ButtonClickerNativeActivity_nativeOnActivityStarted(
        JNIEnv *env, jobject thiz, jobject activity) {
    gpg::AndroidSupport::OnActivityStarted(env, activity);
}

JNIEXPORT void
Java_com_google_example_games_ButtonClicker_ButtonClickerNativeActivity_nativeOnActivityStopped(
        JNIEnv *env, jobject thiz, jobject activity) {
    gpg::AndroidSupport::OnActivityStopped(env, activity);
}
}

AMSMPNativeActivity.h

  //
// Created by Ams on 9/1/2017.
//

#ifndef PROJ_ANDROID_STUDIO_AMSMPNATIVEACTIVITY_H
#define PROJ_ANDROID_STUDIO_AMSMPNATIVEACTIVITY_H

/*
 * Include files
 */
#include <jni.h>
#include <errno.h>

#include <android/log.h>
#include <android_native_app_glue.h>
#include <android/native_window_jni.h>
#include <cpu-features.h>
#include <sstream>
#include <algorithm>
#include <thread>
#include <unordered_map>
#include <mutex>

// For GPGS
#include "gpg/gpg.h"
/*
 * Preprocessors
 */

// Class name of helper function
#define HELPER_CLASS_NAME "com.sample.helper.NDKHelper"
// Class name of JUIhelper function
#define JUIHELPER_CLASS_NAME "com.sample.helper.JUIHelper"
// Share object name of helper function library
#define HELPER_CLASS_SONAME "ButtonClickerNativeActivity"

//
// Also set "com.google.android.gms.games.APP_ID" in AndrdoiManifest.xml
//

const int32_t MIN_PLAYERS = 1;
const int32_t MAX_PLAYERS = 3;
const double GAME_DURATION = 20.0;

enum NEXT_PARTICIPANT {
    NEXT_PARTICIPANT_AUTOMATCH = -1,
    NEXT_PARTICIPANT_NONE = -2,
};

struct PLAYER_STATUS {
    int32_t score;
    bool finished;
};
/*
 * Engine class of the sample
 */
struct android_app;
class AMSMPNativeActivity : public gpg::IRealTimeEventListener {
public:
    void InitGooglePlayGameServices();
    void InviteFriend();
    void ShowRoomInbox();

    void InitializeGame();
    void PlayGame();
    void LeaveGame();
    void QuickMatch();

    void BroadcastScore(bool bFinal);

    static void HandleCmd(struct android_app *app, int32_t cmd);
    static int32_t HandleInput(android_app *app, AInputEvent *event);
    void UpdatePosition(AInputEvent *event, int32_t iIndex, float &fX, float &fY);

    AMSMPNativeActivity();
    ~AMSMPNativeActivity();
    void SetState(android_app *state);
    int InitDisplay(const int32_t cmd);
    void LoadResources();
    void UnloadResources();
    void DrawFrame();
    void TermDisplay(const int32_t cmd);
    void TrimMemory();
    bool IsReady();

    virtual void OnRoomStatusChanged(gpg::RealTimeRoom const &room);

    virtual void OnParticipantStatusChanged(
            gpg::RealTimeRoom const &room,
            gpg::MultiplayerParticipant const &participant);

    virtual void OnDataReceived(
            gpg::RealTimeRoom const &room,
            gpg::MultiplayerParticipant const &from_participant,
            std::vector<uint8_t> data, bool is_reliable);


    virtual void OnConnectedSetChanged(gpg::RealTimeRoom const &room) {}

    virtual void OnP2PConnected(gpg::RealTimeRoom const &room,
                                gpg::MultiplayerParticipant const &participant) {}
    virtual void OnP2PDisconnected(
            gpg::RealTimeRoom const &room,
            gpg::MultiplayerParticipant const &participant) {}

private:
    // Callbacks for GPG authentication.
    void OnAuthActionStarted(gpg::AuthOperation op);
    void OnAuthActionFinished(gpg::AuthOperation op, gpg::AuthStatus status);

    void UpdateScore();
    bool UpdateTime();

    void EnableUI(bool enable);
    void InitUI();
    void UpdateFPS(float fFPS);

    std::unique_ptr<gpg::GameServices> service_;  // gpg service instance
    std::unordered_map<std::string, PLAYER_STATUS> players_score_;  // hashmap to
   
    gpg::RealTimeRoom room_;  
    int32_t score_counter_;   // Score counter of local player
    bool playing_;            // Am I playing a game?
    std::string self_id_;     // Local player's ID
    double start_time_;       // Game start time

    mutable std::mutex mutex_;


    bool initialized_resources_;
    bool has_focus_;

    android_app *app_;

};


#endif //PROJ_ANDROID_STUDIO_AMSMPNATIVEACTIVITY_H

I could also put Android.mk or gradle files here but again, prior to adding the above C++ codes into my build path I did not get any compile time errors so there must be something wrong in the implementation of AMSMPNativeActivity.h and AMSMPNativeActivity.cpp then again, I could put them here too if they could potentially help solve the problem.

Update:

I removed the two classes mentioned above. But the error didn’t go away. Basically I reverted things to where it was working before and now even that doesnt work anymore, i get the same error with a bunch of warning messages this time. I wonder if I use a real time machine and go back to 2 hours ago, would I be able to watch myself happy that atleast the integration actually worked? I doubt it…
This is really frustrating and here I was hoping that SDKBOX would make things easier. The error message does not even make any sense Error:error: ld returned 1 exit status
Cocos2dx + sdkbox gpg is way more appealing than using engines like unity for me and I hope developers here come up with a proper tutorial and make it work

Update 2:

I solved it by removing android_ndk_app_glue from my Android.mk LOCAL_STATIC_LIBRARY. Apparently including that that library must have duplicated it since Cocos2dx takes care of that internally. But ofcourse gpg c++ sdk sample was doing it for the first time!
So it seems ok for now :slight_smile:

Update 3:

I managed to log in, seems SDKBOX does work like a charm afterall :slight_smile:
I’ll try to help anyone else facing similar problems under this post.

Great to hear that.