사용자 도구

사이트 도구


kb:perforcecppapi

Perforce C++ API

애플리케이션 안에서 퍼포스 저장소에 액세스하면 좋을 때가 있다.

개발팀 내부에서 사용하는 각종 인하우스 툴이 대표적이라고 할 수 있다. 퍼포스에서는 이를 위해 여러가지 언어를 위한 API를 제공하는데, 불행인지 다행인지 C++ API도 그 목록 중의 하나다. 그러나 각종 기능에 대해서 별도의 API가 존재하는 것이 아니라, 커맨드 라인에서 입력하는 퍼포스 명령어를 그대로 입력하고, 그 출력을 그냥 문자열로 받도록 되어있다. 즉 뭔가 대단한 일을 하기 위해서는 일일이 문자열 파싱해야 한다는 이야기다. 이럴 거면 그냥 퍼포스 클라이언트 깔고, system API 사용하는 거랑 별반 차이가 없는 것 같은데, 뭐 어쩌랴.

퍼포스 클라이언트/서버와는 달리 API 쪽은 문서가 매우 빈약하다. 퍼포스 측에서 딱히 개선할 의지도 없어보인다. 개선할 의지가 있었다면 API를 이렇게 만들어놓지는 않았겠지.

래퍼

////////////////////////////////////////////////////////////////////////////////////////////////////
/// \file Perforce.h
/// \author excel96
/// \date 2008.12.30
////////////////////////////////////////////////////////////////////////////////////////////////////
 
#pragma once
 
#include <map>
#include <objbase.h>
#include <string>
#include <tchar.h>
#include <vector>
 
#pragma warning(push)
 
#pragma warning(disable:4100)
#pragma warning(disable:4244)
#pragma warning(disable:4267)
 
#include <p4/clientapi.h>
 
namespace Perforce
{
    struct LexLess
    {
        bool operator()(const std::string& lhs, const std::string& rhs) const
        {
            return CompareStringA(
                LOCALE_USER_DEFAULT, NORM_IGNORECASE, lhs.c_str(), -1, rhs.c_str(), -1
                ) == CSTR_LESS_THAN;
        }
    };
 
    typedef std::vector<std::string> Array;
    typedef std::map<std::string, std::string, LexLess> Dictionary;
 
    /// 퍼포스 래퍼
    class Client : protected ClientUser
    {
    private:
        ClientApi  m_Client; ///< API 컨텍스트
        Dictionary m_Output; ///< 명령 결과물
        Error      m_Error;  ///< 에러값
 
 
    public:
        /// \brief 생성자
        Client();
 
        /// \brief 소멸자
        ~Client();
 
 
    public:
        /// \brief 로그인한다.
        bool Login();
 
        /// \brief 로그아웃한다.
        void Logout();
 
 
    public:
        /// \brief 명령을 실행한다.
        bool Run(const std::string& command, const Array& arguments);
 
        /// \brief 명령을 실행한다.
        bool Run(const std::string& command);
 
        /// \brief 명령을 실행한다.
        bool Run(const std::string& command, const std::string& arg0);
 
        /// \brief 명령을 실행한다.
        bool Run(const std::string& command, const std::string& arg0, const std::string& arg1);
 
        /// \brief 명령 실행 결과를 반환한다.
        const Dictionary& GetOutput() const;
 
        /// \brief 명령 실행 결과를 반환한다.
        const std::string& GetOutput(const std::string& name) const;
 
        /// \brief 에러 문자열을 반환한다.
        std::string GetErrorString();
 
 
    public:
        /// \brief 로컬 경로를 디팟 경로로 변환한다.
        std::string Local2Depot(std::string path, bool dir);
 
        /// \brief 디팟 경로를 로컬 경로로 변환한다.
        std::string Depot2Local(const std::string& path);
 
        /// \brief 편집을 위해 연다.
        bool Edit(const std::string& path);
 
 
    private:
        /// \brief ClientUser 클래스 콜백
        virtual void OutputError(const char* errBuf);
 
        /// \brief ClientUser 클래스 콜백
        virtual void OutputInfo(char level, const char* data);
 
        /// \brief ClientUser 클래스 콜백
        virtual void OutputBinary(const char* data, int length);
 
        /// \brief ClientUser 클래스 콜백
        virtual void OutputText(const char* data, int length);
 
        /// \brief ClientUser 클래스 콜백
        virtual void OutputStat(StrDict* varList);
    };
 
    /// \brief 출력 결과물을 문자열로 통합해서 반환한다.
    std::string Join(const Dictionary& dict, const std::string& seperator = "\r\n");
}
 
#pragma warning(pop)
////////////////////////////////////////////////////////////////////////////////////////////////////
/// \file Perforce.cpp
/// \author excel96
/// \date 2008.12.30
////////////////////////////////////////////////////////////////////////////////////////////////////
 
#include "Perforce.h"
 
#include <sstream>
 
#ifdef _DEBUG
    #ifdef _DLL
        #pragma comment(lib, "debugdll_libclient")
        #pragma comment(lib, "debugdll_librpc")
        #pragma comment(lib, "debugdll_libsupp")
    #else
        #pragma comment(lib, "debug_libclient")
        #pragma comment(lib, "debug_librpc")
        #pragma comment(lib, "debug_libsupp")
    #endif
#else
    #ifdef _DLL
        #pragma comment(lib, "releasedll_libclient")
        #pragma comment(lib, "releasedll_librpc")
        #pragma comment(lib, "releasedll_libsupp")
    #else
        #pragma comment(lib, "release_libclient")
        #pragma comment(lib, "release_librpc")
        #pragma comment(lib, "release_libsupp")
    #endif
#endif
 
namespace Perforce
{
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief 생성자
    ////////////////////////////////////////////////////////////////////////////////////////////////
    Client::Client()
    {
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief 소멸자
    ////////////////////////////////////////////////////////////////////////////////////////////////
    Client::~Client()
    {
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief 로그인한다.
    ///
    /// 레지스트리에 존재하는 퍼포스 정보를 이용해 로그인하기 때문에 워크스페이스가 미리 세팅되어 
    /// 있어야 한다.
    ///
    /// \return bool 무사히 로그인했다면 true
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool Client::Login()
    {
        m_Client.SetProtocol("tag", ""); 
        m_Client.Init(&m_Error);
        return m_Error.Test() == false;
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief 로그아웃한다.
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void Client::Logout()
    {
        m_Client.Final(&m_Error);
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief 명령을 실행한다.
    /// \param command 실행할 명령
    /// \param arguments 인자
    /// \return bool 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool Client::Run(const std::string& command, const Array& arguments)
    {
        m_Output.clear();
 
        char** argv = NULL;
 
        if (!arguments.empty())
        {
            argv = new char*[arguments.size()];
            for (size_t i=0; i<arguments.size(); ++i)
                argv[i] = (char*)(arguments[i].c_str());
        }
 
        m_Client.SetArgv(static_cast<int>(arguments.size()), argv);
        m_Client.Run(command.c_str(), this);
 
        if (argv)
            delete [] argv;
 
        return !m_Error.Test();
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief 명령을 실행한다.
    /// \param command 실행할 명령어
    /// \return bool 무사히 실행했다면 true, 뭔가 에러가 생겼다면 false
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool Client::Run(const std::string& command)
    {
        return Run(command, Array());
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief 명령을 실행한다.
    /// \param command 실행할 명령
    /// \param arg0 인자
    /// \return bool 무사히 실행했다면 true, 뭔가 에러가 생겼다면 false
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool Client::Run(const std::string& command, const std::string& arg0)
    {
        Array arguments;
        arguments.push_back(arg0);
        return Run(command, arguments);
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief 명령을 실행한다.
    /// \param command 실행할 명령
    /// \param arg0 인자
    /// \param arg1 인자
    /// \return bool 무사히 실행했다면 true, 뭔가 에러가 생겼다면 false
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool Client::Run(const std::string& command, const std::string& arg0, const std::string& arg1)
    {
        Array arguments;
        arguments.push_back(arg0);
        arguments.push_back(arg1);
        return Run(command, arguments);
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief 명령 실행 결과를 반환한다.
    /// \return const Dictionary& 실행 결과
    ////////////////////////////////////////////////////////////////////////////////////////////////
    const Dictionary& Client::GetOutput() const
    {
        return m_Output;
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief 명령 실행 결과를 반환한다.
    /// \param name 값 이름
    /// \return const std::string& 값
    ////////////////////////////////////////////////////////////////////////////////////////////////
    const std::string& Client::GetOutput(const std::string& name) const
    {
        static std::string dummy;
 
        Dictionary::const_iterator itr(m_Output.find(name));
        return itr != m_Output.end() ? itr->second : dummy;
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief 에러 문자열을 반환한다.
    /// \return std::string 에러 문자열
    ////////////////////////////////////////////////////////////////////////////////////////////////
    std::string Client::GetErrorString()
    {
        std::string result;
 
        if (m_Error.Test())
        {
            StrBuf msg;
            m_Error.Fmt(&msg);
            return std::string(msg.Text());
        }
 
        return result;
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief 로컬 경로를 디팟 경로로 변환한다.
    /// \param path 로컬 경로
    /// \return std::string 디팟 경로
    ////////////////////////////////////////////////////////////////////////////////////////////////
    std::string Client::Local2Depot(std::string path, bool dir)
    {
        std::string converted;
 
        if (dir)
        {
            if (!path.empty() && path[path.size()-1] != '\\')
                path += '\\';
 
            path += _T("1");
        }
 
        Array arguments;
        arguments.push_back(path);
 
        if (Run("where", arguments))
        {
            converted = GetOutput("depotFile");
 
            if (dir)
                converted = converted.substr(0, converted.size()-1);
        }
 
        return converted;
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief 디팟 경로를 로컬 경로로 변환한다.
    /// \param path 디팟 경로
    /// \return std::string 로컬 경로
    ////////////////////////////////////////////////////////////////////////////////////////////////
    std::string Client::Depot2Local(const std::string& path)
    {
        std::string converted;
 
        bool dir = !path.empty() && path[path.size()-1] == '/';
 
        Array arguments;
        if (dir)
            arguments.push_back(path + "...");
        else
            arguments.push_back(path);
 
        if (Run("where", arguments))
        {
            converted = GetOutput("path");
 
            if (dir)
                converted = converted.substr(0, converted.size()-3);
        }
 
        return converted;
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief 편집을 위해 연다.
    /// \param path 디팟 경로
    /// \return bool 무사히 열었다면 true
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool Client::Edit(const std::string& path)
    {
        Array arguments;
        arguments.push_back(path);
 
        return Run("edit", arguments);
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief ClientUser 클래스 콜백
    /// \param errBuf 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void Client::OutputError(const char* /*errBuf*/)
    {
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief ClientUser 클래스 콜백
    /// \param level 
    /// \param data 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void Client::OutputInfo(char /*level*/, const char* /*data*/)
    {
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief ClientUser 클래스 콜백
    /// \param data 
    /// \param length 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void Client::OutputBinary(const char* /*data*/, int /*length*/)
    {
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief ClientUser 클래스 콜백
    /// \param data 
    /// \param length 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void Client::OutputText(const char* /*data*/, int /*length*/)
    {
    }
 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    /// \brief ClientUser 클래스 콜백
    /// \param varList 
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void Client::OutputStat(StrDict* varList)
    {
        StrBuf msg;
        StrRef var, val;
 
        for (int i = 0; varList->GetVar(i, var, val); ++i)
        {
            if (var == "func" || var == P4Tag::v_specFormatted)
                continue;
 
            if (m_Output.find(var.Text()) == m_Output.end())
                m_Output.insert(Dictionary::value_type(var.Text(), val.Text()));
        }
    }
}

샘플

Perforce::Client perforce;
if (perforce.Login())
{
    perforce.Run("edit", "somewhere/over/the/rainbow/*.csv");
    ...
    perforce.Run("revert", "-a", "somewhere/over/the/rainbow/*.csv");
    ...
}

링크

kb/perforcecppapi.txt · 마지막으로 수정됨: 2014/11/11 14:50 저자 excel96