CMake + EOSIO
Purpose
This tutorial describes how to use CMake (and CLion) to build smart contracts for the eosio blockchain. This work was motivated by the absence of build tools that can be used directly from an IDE. Also it was inspired from a previous work done by InfiniteXLabs and try to solve the following shortcomings:
- build multiple contracts within the same project
- do not have to copy eosio header files in your project directory
- do not have to recompile eosio sdk libs within your project
- provide build configuration similar to c++/CMake project (eg, add_library, target_include_directories, target_link_libraries, etc)
Prerequisites
To complete this tutorial, you need to be familiar with CMake, install eosio sdk for your environment, install clang, optional but recommended CLion (or another C++ IDE).
Limitation
This tutorial is a work in progress hence has been tested only on macosx, lacks some useful features (like CMake tests) and does not cover contract deployment to an eosio blockchain.
Tutorial Source
The tutorial source repositories are : eosio-cmake and eosio-cmake-tutorial:
How it works
Create a new project
Tutorial folder structure
Create a new folder for our tutorial eosio-cmake-tutorial:
Copy
scripts/eosiosdk.cmake
scripts/eosiosdk-util.cmake
scripts/FindEOSIOSDKLibs.cmake
from eosio-cmake toeosio-cmake-tutorial/cmake
.Create
include/contracts/hello.hpp
inside eosio-cmake-tutorialCreate
include/contracts/hello.cpp
inside eosio-cmake-tutorial
Your folder should look like :
|---- eosio-cmake-tutorial
|---- CMakeLists.txt
|---- include
|---- contracts
|----hello.hpp
|---- src
|---- hello.cpp
|---- cmake
|---- eosiosdk.cmake
|---- eosiosdk-util.cmake
|---- FindEOSIOSDKLibs.cmake
Editing our first contract
- Edit include/contracts/hello.hpp:
//
// MIT License.
//
#ifndef EOSIO_HELLO_WORLD_HELLO_HPP
#define EOSIO_HELLO_WORLD_HELLO_HPP
#include <contracts/hello.hpp>
#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>
using namespace eosio;
class hello : public eosio::contract {
public:
hello(account_name n);
/// @abi action
void hi( account_name user );
};
#endif //EOSIO_HELLO_WORLD_HELLO_HPP
- Edit src/hello.cpp:
//
// MIT License.
//
#include "contracts/hello.hpp"
using namespace eosio;
hello::hello(account_name n) : contract(n) {}
void hello::hi(account_name user) {
print("Hello, EOS", name{user});
}
EOSIO_ABI(hello, (hi))
- Edit CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(eosio-cmake-tutorial)
################################
## Project
################################
set(PROJECT_VERSION_MAJOR "0")
set(PROJECT_VERSION_MINOR "1")
set(PROJECT_VERSION_PATCH "0")
set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
set(VERSION ${PROJECT_VERSION})
message("\n-- Configuring ${PROJECT_NAME} ${PROJECT_VERSION}...\n")
################################
## Cmake dependencies
################################
include(cmake/eosiosdk.cmake)
include(cmake/eosiosdk-util.cmake)
include(cmake/FindEOSIOSDKLibs.cmake)
################################
## Compiler Flags
################################
set(CMAKE_CXX_STANDARD 14)
################################
# Library Build
################################
# target hello
add_eosio_wasm_library(hello CONTRACT SOURCES src/hello.cpp)
target_eosio_wasm_compile_definitions(hello PUBLIC -DBOOST_DISABLE_ASSERTS -DBOOST_EXCEPTION_DISABLE)
target_eosio_wasm_include_directories(hello
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>)
# $<INSTALL_INTERFACE:include>)
target_eosio_wasm_link_libraries(hello eosio libc++ libc musl Boost)
add_eosio_wasm_abi(hello HEADERS include/contracts/hello.hpp)
################################
## Install
################################
eosio_wasm_install(hello CONTRACT
CODE DESTINATION "${CMAKE_INSTALL_FULL_CODEDIR}/hello"
ABI DESTINATION "${CMAKE_INSTALL_FULL_ABIDIR}/hello")
Building our first contract
> cd eosio-cmake-tutorial
> mkdir build && cd build
> mkdir install && -DCMAKE_INSTALL_PREFIX=install ..
> make contracts
Scanning dependencies of target hello_link
[ 16%] Building LLVM bitcode src/hello.cpp.bc
[ 33%] Linking LLVM bitcode executable lib/hello.bc
[ 50%] Generating textual assembly hello.s
[ 66%] Generating WAST wast/hello.wast
[ 83%] Generating WASM wast/hello.wast
[ 83%] Built target hello_link
Scanning dependencies of target hello_abi_gen
[100%] Generating ABI abi/hello.abi
2018-09-20T11:17:15.814 thread-0 abi_generator.hpp:68 ricardian_contracts ] Warning, no ricardian clauses found for
[100%] Built target hello_abi_gen
Scanning dependencies of target hello
Target hello output wasm to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/bin
Target hello outputllvm bytecode to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/lib
Target hello output wast and asm to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/code
Target hello output abi to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/abi
[100%] Built target hello
Scanning dependencies of target contracts
[100%] Built target contracts
This step should output the following folders abi``code
bin
lib
respectively containing hello.abi
, hello.wast
, hello.wasm
, hello.bc
.
Installing our first contract
> mkdir hello_install
Scanning dependencies of target hello_install
Installing contracts abi
/Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/abi:/Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/install/abi/hello
Installing contracts wasm
/Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/bin:/Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/install/code/hello
Installing contracts wast
/Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/code:/Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/install/code/hello
[100%] Built target hello_install
This step will install abi and code in install directory.
Editing our second contract
Our second contrat is the tic_tac_toe contract from eosio tutorial.
- Create and edit include/contracts/tic_tac_toe.hpp
/**
* @file
* @copyright defined in eos/LICENSE.txt
*/
#ifndef EOSIO_HELLO_WORLD_TIC_TAC_TOE_HPP
#define EOSIO_HELLO_WORLD_TIC_TAC_TOE_HPP
#include <eosiolib/eosio.hpp>
/**
* @defgroup tictactoecontract Tic Tac Toe Contract
* @brief Defines the PvP tic tac toe contract example
* @ingroup examplecontract
*
* @details
* For the following tic-tac-toe game:
* - Each pair of player can have 2 unique game, one where player_1 become host and player_2 become challenger and vice versa
* - The game data is stored in the "host" scope and use the "challenger" as the key
*
* (0,0) coordinate is on the top left corner of the board
* @code
* (0,2)
* (0,0) - | o | x where - = empty cell
* - | x | - x = move by host
* (2,0) x | o | o o = move by challenger
* @endcode
*
* Board is represented with number:
* - 0 represents empty cell
* - 1 represents cell filled by host
* - 2 represents cell filled by challenger
* Therefore, assuming x is host, the above board will have the following representation: [0, 2, 1, 0, 1, 0, 1, 2, 2] inside the game object
*
* In order to deploy this contract:
* - Create an account called tic.tac.toe
* - Add tic.tac.toe key to your wallet
* - Set the contract on the tic.tac.toe account
*
* How to play the game:
* - Create a game using `create` action, with you as the host and other account as the challenger.
* - The first move needs to be done by the host, use the `move` action to make a move by specifying which row and column to fill.
* - Then ask the challenger to make a move, after that it's back to the host turn again, repeat until the winner is determined.
* - If you want to restart the game, use the `restart` action
* - If you want to clear the game from the database to save up some space after the game has ended, use the `close` action
* @{
*/
class tic_tac_toe : public eosio::contract {
public:
tic_tac_toe( account_name self ):contract(self){}
/**
* @brief Information related to a game
* @abi table games i64
*/
struct game {
static const uint16_t board_width = 3;
static const uint16_t board_height = board_width;
game() {
initialize_board();
}
account_name challenger;
account_name host;
account_name turn; // = account name of host/ challenger
account_name winner = N(none); // = none/ draw/ name of host/ name of challenger
std::vector<uint8_t> board;
// Initialize board with empty cell
void initialize_board() {
board = std::vector<uint8_t>(board_width * board_height, 0);
}
// Reset game
void reset_game() {
initialize_board();
turn = host;
winner = N(none);
}
auto primary_key() const { return challenger; }
EOSLIB_SERIALIZE( game, (challenger)(host)(turn)(winner)(board))
};
/**
* @brief The table definition, used to store existing games and their current state
*/
typedef eosio::multi_index< N(games), game> games;
/// @abi action
/// Create a new game
void create(const account_name& challenger, const account_name& host);
/// @abi action
/// Restart a game
/// @param by the account who wants to restart the game
void restart(const account_name& challenger, const account_name& host, const account_name& by);
/// @abi action
/// Close an existing game, and remove it from storage
void close(const account_name& challenger, const account_name& host);
/// @abi action
/// Make movement
/// @param by the account who wants to make the move
void move(const account_name& challenger, const account_name& host, const account_name& by, const uint16_t& row, const uint16_t& column);
};
/// @}
#endif //EOSIO_HELLO_WORLD_TIC_TAC_TOE_HPP
- Create and edit src/tic_tac_toe.cpp
/**
* @file
* @copyright defined in eos/LICENSE.txt
*/
#include <contracts/tic_tac_toe.hpp>
using namespace eosio;
/**
* @brief Check if cell is empty
* @param cell - value of the cell (should be either 0, 1, or 2)
* @return true if cell is empty
*/
bool is_empty_cell(const uint8_t& cell) {
return cell == 0;
}
/**
* @brief Check for valid movement
* @detail Movement is considered valid if it is inside the board and done on empty cell
* @param row - the row of movement made by the player
* @param column - the column of movement made by the player
* @param board - the board on which the movement is being made
* @return true if movement is valid
*/
bool is_valid_movement(const uint16_t& row, const uint16_t& column, const vector<uint8_t>& board) {
uint32_t movement_location = row * tic_tac_toe::game::board_width + column;
bool is_valid = movement_location < board.size() && is_empty_cell(board[movement_location]);
return is_valid;
}
/**
* @brief Get winner of the game
* @detail Winner of the game is the first player who made three consecutive aligned movement
* @param current_game - the game which we want to determine the winner of
* @return winner of the game (can be either none/ draw/ account name of host/ account name of challenger)
*/
account_name get_winner(const tic_tac_toe::game& current_game) {
auto& board = current_game.board;
bool is_board_full = true;
// Use bitwise AND operator to determine the consecutive values of each column, row and diagonal
// Since 3 == 0b11, 2 == 0b10, 1 = 0b01, 0 = 0b00
vector<uint32_t> consecutive_column(tic_tac_toe::game::board_width, 3 );
vector<uint32_t> consecutive_row(tic_tac_toe::game::board_height, 3 );
uint32_t consecutive_diagonal_backslash = 3;
uint32_t consecutive_diagonal_slash = 3;
for (uint32_t i = 0; i < board.size(); i++) {
is_board_full &= is_empty_cell(board[i]);
uint16_t row = uint16_t(i / tic_tac_toe::game::board_width);
uint16_t column = uint16_t(i % tic_tac_toe::game::board_width);
// Calculate consecutive row and column value
consecutive_row[column] = consecutive_row[column] & board[i];
consecutive_column[row] = consecutive_column[row] & board[i];
// Calculate consecutive diagonal \ value
if (row == column) {
consecutive_diagonal_backslash = consecutive_diagonal_backslash & board[i];
}
// Calculate consecutive diagonal / value
if ( row + column == tic_tac_toe::game::board_width - 1) {
consecutive_diagonal_slash = consecutive_diagonal_slash & board[i];
}
}
// Inspect the value of all consecutive row, column, and diagonal and determine winner
vector<uint32_t> aggregate = { consecutive_diagonal_backslash, consecutive_diagonal_slash };
aggregate.insert(aggregate.end(), consecutive_column.begin(), consecutive_column.end());
aggregate.insert(aggregate.end(), consecutive_row.begin(), consecutive_row.end());
for (auto value: aggregate) {
if (value == 1) {
return current_game.host;
} else if (value == 2) {
return current_game.challenger;
}
}
// Draw if the board is full, otherwise the winner is not determined yet
return is_board_full ? N(draw) : N(none);
}
/**
* @brief Apply create action
*/
void tic_tac_toe::create(const account_name& challenger, const account_name& host) {
require_auth(host);
eosio_assert(challenger != host, "challenger shouldn't be the same as host");
// Check if game already exists
games existing_host_games(_self, host);
auto itr = existing_host_games.find( challenger );
eosio_assert(itr == existing_host_games.end(), "game already exists");
existing_host_games.emplace(host, [&]( auto& g ) {
g.challenger = challenger;
g.host = host;
g.turn = host;
});
}
/**
* @brief Apply restart action
*/
void tic_tac_toe::restart(const account_name& challenger, const account_name& host, const account_name& by) {
require_auth(by);
// Check if game exists
games existing_host_games(_self, host);
auto itr = existing_host_games.find( challenger );
eosio_assert(itr != existing_host_games.end(), "game doesn't exists");
// Check if this game belongs to the action sender
eosio_assert(by == itr->host || by == itr->challenger, "this is not your game!");
// Reset game
existing_host_games.modify(itr, itr->host, []( auto& g ) {
g.reset_game();
});
}
/**
* @brief Apply close action
*/
void tic_tac_toe::close(const account_name& challenger, const account_name& host) {
require_auth(host);
// Check if game exists
games existing_host_games(_self, host);
auto itr = existing_host_games.find( challenger );
eosio_assert(itr != existing_host_games.end(), "game doesn't exists");
// Remove game
existing_host_games.erase(itr);
}
/**
* @brief Apply move action
*/
void tic_tac_toe::move(const account_name& challenger, const account_name& host, const account_name& by, const uint16_t& row, const uint16_t& column ) {
require_auth(by);
// Check if game exists
games existing_host_games(_self, host);
auto itr = existing_host_games.find( challenger );
eosio_assert(itr != existing_host_games.end(), "game doesn't exists");
// Check if this game hasn't ended yet
eosio_assert(itr->winner == N(none), "the game has ended!");
// Check if this game belongs to the action sender
eosio_assert(by == itr->host || by == itr->challenger, "this is not your game!");
// Check if this is the action sender's turn
eosio_assert(by == itr->turn, "it's not your turn yet!");
// Check if user makes a valid movement
eosio_assert(is_valid_movement(row, column, itr->board), "not a valid movement!");
// Fill the cell, 1 for host, 2 for challenger
const uint8_t cell_value = itr->turn == itr->host ? 1 : 2;
const auto turn = itr->turn == itr->host ? itr->challenger : itr->host;
existing_host_games.modify(itr, itr->host, [&]( auto& g ) {
g.board[row * tic_tac_toe::game::board_width + column] = cell_value;
g.turn = turn;
g.winner = get_winner(g);
});
}
EOSIO_ABI( tic_tac_toe, (create)(restart)(close)(move))
- Modify CMakeLists.txt
# target tic tac toe
add_eosio_wasm_library(tic_tac_toe CONTRACT SOURCES src/tic_tac_toe.cpp)
target_eosio_wasm_compile_definitions(tic_tac_toe PUBLIC -DBOOST_DISABLE_ASSERTS -DBOOST_EXCEPTION_DISABLE)
target_eosio_wasm_include_directories(tic_tac_toe
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>)
# $<INSTALL_INTERFACE:include>)
target_eosio_wasm_link_libraries(tic_tac_toe eosio libc++ libc musl Boost)
add_eosio_wasm_abi(tic_tac_toe HEADERS include/contracts/tic_tac_toe.hpp)
Building both contracts
> cd build
> rm -rf *
> mkdir install && cmake -DCMAKE_INSTALL_PREFIX=install ..
> make contracts
Scanning dependencies of target hello_link
[ 8%] Building LLVM bitcode src/hello.cpp.bc
[ 16%] Linking LLVM bitcode executable lib/hello.bc
[ 25%] Generating textual assembly hello.s
[ 33%] Generating WAST wast/hello.wast
[ 41%] Generating WASM wast/hello.wast
[ 41%] Built target hello_link
Scanning dependencies of target hello_abi_gen
[ 50%] Generating ABI abi/hello.abi
2018-09-20T13:03:36.315 thread-0 abi_generator.hpp:68 ricardian_contracts ] Warning, no ricardian clauses found for
[ 50%] Built target hello_abi_gen
Scanning dependencies of target hello
Target hello output wasm to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/bin
Target hello outputllvm bytecode to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/lib
Target hello output wast and asm to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/code
Target hello output abi to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/abi
[ 50%] Built target hello
Scanning dependencies of target tic_tac_toe_link
[ 58%] Building LLVM bitcode src/tic_tac_toe.cpp.bc
[ 66%] Linking LLVM bitcode executable lib/tic_tac_toe.bc
[ 75%] Generating textual assembly tic_tac_toe.s
[ 83%] Generating WAST wast/tic_tac_toe.wast
[ 91%] Generating WASM wast/tic_tac_toe.wast
[ 91%] Built target tic_tac_toe_link
Scanning dependencies of target tic_tac_toe_abi_gen
[100%] Generating ABI abi/tic_tac_toe.abi
2018-09-20T13:03:39.947 thread-0 abi_generator.hpp:68 ricardian_contracts ] Warning, no ricardian clauses found for
[100%] Built target tic_tac_toe_abi_gen
Scanning dependencies of target tic_tac_toe
Target tic_tac_toe output wasm to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/bin
Target tic_tac_toe outputllvm bytecode to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/lib
Target tic_tac_toe output wast and asm to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/code
Target tic_tac_toe output abi to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/abi
[100%] Built target tic_tac_toe
Scanning dependencies of target contracts
[100%] Built target contracts
Installing both contracts
> make hello_install
> make tic_tac_toe_install
Use from Clion
Clion is a C++ IDE with built in support for CMake projects and more. Open the previous project from Clion. Clion will create build dependancies based on our CMakeLists.txt
Targets and dependancies in Clion
Next step
The next step will provide features to test and deploy contracts to an eosio contract.
Congratulations @copernix! You received a personal award!
You can view your badges on your Steem Board and compare to others on the Steem Ranking
Do not miss the last post from @steemitboard:
Vote for @Steemitboard as a witness to get one more award and increased upvotes!
Congratulations @copernix! You received a personal award!
Click here to view your Board