CMake FetchContent 명령어

|

FetchContent

FetchContentCMake 3.11에 새로 추가된 명령어입니다. 대부분의 언어들은 외부 라이브러리들을 사용하기 위해 라이브러리 설치 또는 종속성 추가를 쉽게 할 수 있는 기능을 제공하고 있습니다. 예를 들면, Java의 maven이나 gradle, Python의 pip 등입니다. 하지만, C++에는 그런 기능이 없었기 때문에 외부 라이브러리를 사용하기 위해서는 아주 까다로운 설정이 필요했었습니다.

이제 FetchContent 명령어를 통해 CMake에서도 외부 라이브러리들을 쉽게 설치하고 사용할 수 있게 되었습니다.

ExternalProject과 차이

CMake 3.11 버전 이전에도 ExternalProject 명령어를 이용해서 외부 라이브러리를 사용할 수 있었습니다. 하지만, ExternalProject 명령어는 빌드 타임에 외부 라이브러리를 가져오지만, FetchContent는 CMake 실행 시점에 외부 라이브러리를 가져온다는 점에서 차이가 있습니다.

FetchContent 사용 예제

CMakeLists.txt 파일에 다음 명령어를 이용해서 외부 라이브러리를 가져올 수 있습니다. 여기서는 nlohnmann json를 예시로 작성했습니다.

include(FetchContent)
FetchContent_Declare(json
        GIT_REPOSITORY https://github.com/nlohmann/json
        GIT_TAG v3.10.5
        GIT_PROGRESS TRUE
        GIT_SHALLOW TRUE)

FetchContent_MakeAvailable(json)

CMakeLists.txt 전체 코드

cmake_minimum_required(VERSION 3.21)
project(nhlomann_json_example)

set(CMAKE_CXX_STANDARD 14)

include(FetchContent)
FetchContent_Declare(json
        GIT_REPOSITORY https://github.com/nlohmann/json
        GIT_PROGRESS TRUE
        GIT_SHALLOW TRUE
        GIT_TAG v3.10.5)

FetchContent_MakeAvailable(json)

add_executable(nhlomann_json_example main.cpp)

target_link_libraries(nhlomann_json_example
        PRIVATE nlohmann_json::nlohmann_json)

main.cpp

그 이후에는 아래와 같이 nlohmann/json.hpp 파일 등을 include해서 사용할 수 있습니다.

#include <iostream>
#include <nlohmann/json.hpp>

int main() {
    std::cout << "Hello, World!" << std::endl;

    // ...

    return 0;
}

Windows에서 Ubuntu로 원격 접속하기

|

Remote PC (Windows -> Ubuntu)

Windows에서 Ubuntu PC에 원격 접속할 수 있도록 설정하는 방법입니다. 환경은 Windows 10Ubuntu 20.04 입니다.

xrdp 설치

sudo apt update
sudo apt install xrdp
sudo systemctl enable --now xrdp
sudo ufw allow from any to any port 3389 proto tcp

그 이후 Windows의 원격데스크탑 접속을 이용해서 리눅스에 접속하면 되고, 다이얼로그 창이 뜨면, Xorg Session, 계정, 패스워드를 입력하면 됩니다.

만약 접속시 Black Screen 만 나오는 경우

/etc/xrdp/startwm.sh 파일을 수정합니다.

sudo gedit /etc/xrdp/startwm.sh

그리고 여기에 아래 내용을 입력합니다.

unset DBUS_SESSION_BUS_ADDRESS
unset XDG_RUNTIME_DIR
. $HOME/.profile

startwm.sh 전체 파일 내용

#!/bin/sh
# xrdp X session start script (c) 2015, 2017 mirabilos
# published under The MirOS Licence

if test -r /etc/profile; then
	. /etc/profile
fi

if test -r /etc/default/locale; then
	. /etc/default/locale
	test -z "${LANG+x}" || export LANG
	test -z "${LANGUAGE+x}" || export LANGUAGE
	test -z "${LC_ADDRESS+x}" || export LC_ADDRESS
	test -z "${LC_ALL+x}" || export LC_ALL
	test -z "${LC_COLLATE+x}" || export LC_COLLATE
	test -z "${LC_CTYPE+x}" || export LC_CTYPE
	test -z "${LC_IDENTIFICATION+x}" || export LC_IDENTIFICATION
	test -z "${LC_MEASUREMENT+x}" || export LC_MEASUREMENT
	test -z "${LC_MESSAGES+x}" || export LC_MESSAGES
	test -z "${LC_MONETARY+x}" || export LC_MONETARY
	test -z "${LC_NAME+x}" || export LC_NAME
	test -z "${LC_NUMERIC+x}" || export LC_NUMERIC
	test -z "${LC_PAPER+x}" || export LC_PAPER
	test -z "${LC_TELEPHONE+x}" || export LC_TELEPHONE
	test -z "${LC_TIME+x}" || export LC_TIME
	test -z "${LOCPATH+x}" || export LOCPATH
fi

unset DBUS_SESSION_BUS_ADDRESS
unset XDG_RUNTIME_DIR
. $HOME/.profile

if test -r /etc/profile; then
	. /etc/profile
fi

test -x /etc/X11/Xsession && exec /etc/X11/Xsession
exec /bin/sh /etc/X11/Xsession

Flutter Dialog 예제

|

Flutter Dialog

showDialog(
  context: context,
  builder: (BuildContext context) {
    // return object of type Dialog
    return AlertDialog(
      title: Text(node.name),
      content: const Text("Edit node."),
      actions: [
        TextButton(
          child: const Text("Ok"),
          onPressed: () {
            // TODO
            setState(() {
              node.name = node.name + '+';
            });
            Navigator.pop(context);
          },
        ),
      ],
    );
  },
);
</pre>

Python Streaming 서버 예제 (Flask와 Redis 사용)

|

Streaming 서버 예제 (Flask와 Redis 사용)

브라우저에서 http://localhost:8001/message에 접속하면 데이터가 스트리밍 되는 것을 확인할 수 있습니다.

예제 코드

import json

import flask
import redis
from flask import Flask

app = Flask(__name__)
app.debug = True


def stream_message(channel):
    r = redis.Redis()
    p = r.pubsub()
    p.subscribe(channel)
    for message in p.listen():
        if message['type'] == 'message':
            yield 'data: ' + json.dumps(message['data'].decode()) + '\n\n'


@app.route('/message', methods=['GET'])
def get_messages():
    return flask.Response(
        flask.stream_with_context(stream_message('snowdeer_channel')),
        mimetype='text/event-stream'
    )


if __name__ == '__main__':
    app.run(port=8001, use_reloader=False)

publisher.py

import redis
import datetime

r = redis.Redis(host="localhost", port=6379, db=0)

msg = f"[{datetime.datetime.now()}] hello, snowdeer +___+"
r.publish(channel="snowdeer_channel",
          message=msg)

print(f"Message is sent !!\n{msg}")

Python asyncio 및 Coroutine 예제

|

asyncio 및 Coroutine 예제

asyncioselect와 동일한 방식으로 동작하는 이벤트 루프 모듈입니다. asyncio로 이벤트 루프를 생성하고, 어플리케이션은 특정 이벤트가 발생했을 때 호출할 함수를 등록합니다. 이러한 유형의 함수를 코루틴(Coroutine)이라고 합니다. 코루틴은 호출한 쪽에 제어를 되돌려 줄 수 있는 특별한 형태의 함수로 호출한 측에서 이벤트 루프를 계속 실행할 수 있게 합니다.

코루틴은 yield 명령어를 이용해서 호출한 측에 제어권을 돌려주는 제너레이트와 동일하게 동작합니다.

간단한 예제

import asyncio
import time


async def hello() -> str:
    print('hello, snowdeer')

    for i in range(0, 5):
        print(f'hello({i})')
        time.sleep(1)

    return 'snowdeer'


hello_coroutine = hello()

print('* #1')
print(hello_coroutine)

print('* #2')
event_loop = asyncio.get_event_loop()
try:
    print("waiting event loop ...")
    result = event_loop.run_until_complete(hello_coroutine)
finally:
    event_loop.close()

print(f'result: {result}')

결과는 다음과 같습니다.

* #1
<coroutine object hello at 0x100845c40>
* #2
waiting event loop ...
hello, snowdeer
hello(0)
hello(1)
hello(2)
hello(3)
hello(4)
result: snowdeer

Coroutine에서 다른 Coroutine 호출하는 예제

import asyncio
import time


async def get_name() -> str:
    print('get_name() is called')

    for i in range(0, 3):
        print(f'get_name({i}) ...')
        time.sleep(1)

    return 'snowdeer'


async def hello() -> str:
    print('hello() is called')

    name = await get_name()

    for i in range(0, 3):
        print(f'hello, {name} ({i}) ...')
        time.sleep(1)

    return name


hello_coroutine = hello()

print('* #1')
print(hello_coroutine)

print('* #2')
event_loop = asyncio.get_event_loop()
try:
    print("waiting event loop ...")
    result = event_loop.run_until_complete(hello_coroutine)
finally:
    event_loop.close()

print(f'result: {result}')
* #1
<coroutine object hello at 0x1054c1c40>
* #2
waiting event loop ...
hello() is called
get_name() is called
get_name(0) ...
get_name(1) ...
get_name(2) ...
hello, snowdeer (0) ...
hello, snowdeer (1) ...
hello, snowdeer (2) ...
result: snowdeer

name = await get_name() 코드에서 await 키워드는 get_name()이라는 코루틴을 이벤트 루프에 등록하고 제어권을 이벤트 루프에 넘깁니다. 이벤트 루프는 제어권을 받아서 get_name() 코루틴을 실행한 다음 작업이 완료되면 이벤트 루프가 다시 기존의 hello() 코루틴 실행을 이어갑니다.

await의 또 다른 예제

import asyncio
import time


async def loop1():
    print('loop1() is called')

    for i in range(0, 10):
        print(f'loop1({i}) ...')
        await time.sleep(1)


async def loop2():
    print('loop2() is called')

    for i in range(0, 10):
        print(f'loop2({i}) ...')
        await time.sleep(1)


event_loop = asyncio.get_event_loop()
try:
    print("waiting event loop ...")
    result = event_loop.run_until_complete(
        asyncio.gather(
            loop1(),
            loop2(),
        )
    )
finally:
    event_loop.close()

와 같은 코드를 실행하면 다음과 같은 오류가 발생합니다.

waiting event loop ...
loop1() is called
loop1(0) ...
loop2() is called
loop2(0) ...
Traceback (most recent call last):
  File "/Users/snowdeer/Workspace/snowdeer/python_scalability/asyncio_example.py", line 24, in <module>
    result = event_loop.run_until_complete(
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
    return future.result()
  File "/Users/snowdeer/Workspace/snowdeer/python_scalability/asyncio_example.py", line 10, in loop1
    await time.sleep(1)
TypeError: object NoneType can't be used in 'await' expression

따라서 이런 경우는 time.sleep()가 아니라 asyncio.sleep()를 사용해야 합니다. asyncio.sleep()time.sleep()와 달리 비동기 방식이기 때문에 지정된 시간까지 다른 일을 처리할 수 있습니다.

import asyncio
import time


async def loop1():
    print('loop1() is called')

    for i in range(0, 10):
        print(f'loop1({i}) ...')
        await asyncio.sleep(1)


async def loop2():
    print('loop2() is called')

    for i in range(0, 10):
        print(f'loop2({i}) ...')
        await asyncio.sleep(1)


event_loop = asyncio.get_event_loop()
try:
    print("waiting event loop ...")
    result = event_loop.run_until_complete(
        asyncio.gather(
            loop1(),
            loop2(),
        )
    )
finally:
    event_loop.close()