<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Antigravity on Worldwide gwangyi</title><link>https://gwangyi.github.io/tags/antigravity/</link><description>Recent content in Antigravity on Worldwide gwangyi</description><generator>Hugo -- gohugo.io</generator><language>ko-KR</language><lastBuildDate>Sat, 19 Sep 2026 20:43:00 +0900</lastBuildDate><atom:link href="https://gwangyi.github.io/tags/antigravity/index.xml" rel="self" type="application/rss+xml"/><item><title>PDF 회전 도구 만들기 (feat. agy)</title><link>https://gwangyi.github.io/posts/pdf-rotate/</link><pubDate>Sat, 19 Sep 2026 20:43:00 +0900</pubDate><guid>https://gwangyi.github.io/posts/pdf-rotate/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;최근 LLM의 발달은 정말로 눈부시다. 통번역 분야에서는, 물론 전문 통번역사의 레벨에 이르지는 못하지만, 상당히 많은 저수준 수요를 이미 대체하기 시작했고 &lt;a href="https://cursor.com/"&gt;Cursor&lt;/a&gt;를 시작으로 SW 개발자들의 업무를 굉장히 많이 - 때로는 전부 - 도와주는 툴들도 널리 사용되고 있다.&lt;/p&gt;
&lt;p&gt;일반적으로 가장 고성능 LLM이라고 하면 &lt;a href="https://openai.com/about/"&gt;OpenAI&lt;/a&gt;사의 &lt;a href="https://chatgpt.com/"&gt;ChatGPT&lt;/a&gt;, &lt;a href="https://www.anthropic.com/"&gt;Anthropic&lt;/a&gt;사의 &lt;a href="https://claude.ai/"&gt;Claude&lt;/a&gt;, 그리고 &lt;a href="https://about.google/company-info/"&gt;Google&lt;/a&gt;의 &lt;a href="https://gemini.google.com/"&gt;Gemini&lt;/a&gt; 3개를 꼽는다. 각각 &lt;a href="https://chatgpt.com/codex/"&gt;Codex&lt;/a&gt;, &lt;a href="https://claude.com/product/claude-code"&gt;Claude Code&lt;/a&gt;, &lt;a href="https://geminicli.com/"&gt;Gemini CLI&lt;/a&gt;라는 개발용 도구를 제공하며, 최근 &lt;a href="https://about.google/company-info/"&gt;Google&lt;/a&gt;은 &lt;a href="https://antigravity.google/"&gt;Antigravity&lt;/a&gt;라는 도구를 발표하며 개인 사용자는 &lt;a href="https://geminicli.com/"&gt;Gemini CLI&lt;/a&gt; 대신 &lt;a href="https://antigravity.google/"&gt;Antigravity&lt;/a&gt;를 사용하도록 하고 있다. 이 중에서 나는 &lt;a href="https://gemini.google/subscriptions/"&gt;Google AI Pro&lt;/a&gt; 요금제를 사용하고 있기 때문에 &lt;a href="https://antigravity.google/"&gt;Antigravity&lt;/a&gt;를 종종 쓰면서 취미생활이나 공부 등에 활용중이다. 사실 &lt;a href="https://gemini.google.com/"&gt;Gemini&lt;/a&gt;는 최근 세 모델 중 최약체를 맡고 있는 중이라 못미더워 하는 사람들도 많을 것이다. 이 요금제는 Google 서비스에서 사용할 수 있는 스토리지를 5 TB까지 제공해주는데, &lt;a href="https://photos.google.com/"&gt;Google Photo&lt;/a&gt;를 십수년 전부터 꾸준히 사용해오던 나로서는 사진 용량이 무료 용량 15 GB는 애저녁에 넘겼고 이제는 전체 스토리지 사용량이 250 GB를 넘어버린지라 어차피 저장공간에 돈을 쓴다면 조금 더 써서 이거도 해야지, 같은 느낌으로 이용하고 있다.&lt;/p&gt;
&lt;p&gt;아일랜드 생활을 정리하기 전에 네달 가량 한국에서 생활했었는데, 급하게 인쇄할 문서가 꽤 돼서 쓰고 버릴 용도로 삼성 프린터 중 제일 쌌던 &lt;a href="https://www.samsungsvc.co.kr/download/view?code=SL-J1680"&gt;SL-J1680&lt;/a&gt; 프린터를 샀다. 잉크젯이고 스캐너도 있는 복합기인데 몇만원 수준으로 살 수 있으니 가성비는 괜찮았다. 다만 문제가, 윈도우즈용 드라이버만 제공하는데 당시 업무용 맥북에서 쓰려면 꼼수가 좀 필요했었다. 이와 관련된 이야기는 다른 포스트에서 한번 더 다룰 수 있을 것 같다. 그리고 한가지 별 것 아니지만 신경쓰이는 문제가 있었는데, 하단부의 인쇄불가영역이 좀 넓은 문제가 있었다. 주민등록등본을 떼면 아래 바코드 부분까지 간신히 나오고 그 밑의 문구는 인쇄가 안되는 정도였다. 대부분의 문서는 위에 중요한 정보가 모여있고 아래는 여백이거나 중요하지 않은 꼬릿말 정도라 큰 문제는 안됐는데 문제는 만들기 도안에서 터졌다.&lt;/p&gt;
&lt;p&gt;만들기 도안은 가로 방향으로 만들어져 있었는데, 이걸 인쇄하면 자동으로 왼쪽이 아래쪽으로 가는 방향으로 돌려져서 인쇄된다. 즉, 가장 왼쪽 일부가 잘릴 수 있는 것이다. 근데 도안 자체는 오른쪽이 아래로 가는 것을 상정하고 만들어져서 도안이 약간 왼쪽으로 치우쳐져 있어서 빈번하게 잘려 나오게 되었다. 잘린 부분 때문에 아귀가 안 맞거나 모양새가 이상해지는 문제가 생기니 한번은 마음에 안든다고 애가 울고 난리를 친 적까지 있었다. 크기 조정을 해 봐도 문서의 좌상단 기준으로 늘어나고 줄어들다 보니 여전히 좌측 끝단이 잘리는 문제는 해결이 안되니 머리가 아팠다. 그래서 결국 인터넷 5분검색으로 어도비에서 제공하는 PDF 편집 온라인 툴이 이걸 돌릴 수 있다는걸 알고서 필요할 때마다 한번씩 사용해서 문서를 세로 방향으로 재정렬하는 방식으로 잘리는 영역을 반대로 돌리곤 했다.&lt;/p&gt;
&lt;p&gt;그런데 아무래도 내 개인적인 파일을 타사 클라우드에 올린다는 것 자체가 그닥 맘에 들지 않았다. 그래도 자주 쓰는 것도 아니고 가끔가다 한번씩 사용하는데는 큰 문제가 없어서 그냥 쓰고 있었는데 오늘 &amp;lsquo;그 정도로 간단하고 안 중요한 도구라면 &lt;a href="https://aistudio.google.com/vibe-code"&gt;바이브코딩&lt;/a&gt;으로 만들어 쓰면 되지 않나?&amp;rsquo; 하는 생각이 스치고 지나갔다. 그래서 해봤다.&lt;/p&gt;
&lt;h2 id="prompt"&gt;Prompt&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://medium.com/@augustozz/the-i-built-this-with-one-prompt-trend-myth-or-method-3c31929800d3"&gt;원 샷 프롬프트&lt;/a&gt;로 한방에 만들어내는 이야기가 많지만, 개인적으로는 선호하지 않는다. 내가 원하지 않는 방향으로 완성되어버린 코드를 내 취향으로 바꾸는 데는 꽤나 큰 비용이 들기 때문이다. 대략적으로 어떤 기능을 어떤 단계로 더해 나갈지를 머릿속에 그린 뒤에 하나씩 확인하면서 진행하는 방식을 좀 더 선호하는 편이다.&lt;/p&gt;
&lt;p&gt;그래서 나는 내가 만들고 싶은 도구를 5단계 정도로 나눠서 구현해보기로 했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;단순 웹브라우저 기반 PDF 뷰어: 서버 없이 브라우저에서 모든 일이 진행되며, 클릭하면 PDF파일을 선택할 수 있다. 드래그 앤 드롭으로 PDF 파일을 드롭해도 된다. 그렇게 PDF파일을 지정하면 이 파일을 분석해서 각 페이지를 보여주는 단순 PDF 뷰어이다.&lt;/li&gt;
&lt;li&gt;페이지를 제외하는 기능: 사실 당장 제외가 이루어지는 것은 아니고, 각 페이지별 우상단에 X 버튼을 두고서 누르면 지워진 것처럼 회색으로 만드는 기능을 추가한다.&lt;/li&gt;
&lt;li&gt;페이지를 클릭하면 시계방향으로 90도 돌아가는 기능&lt;/li&gt;
&lt;li&gt;2, 3에서 수정한 내용 그대로 PDF로 저장하는 기능&lt;/li&gt;
&lt;li&gt;4와 동일하지만 그대로 인쇄하는 기능&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;실제로 사용한 프롬프트는 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write a pdf viewer tool in HTML and JS. It should accept drag and drop and browsing. Use tailcss to make it responsive. No other features but showing each page.&lt;/li&gt;
&lt;li&gt;Add (x) button on top right corner of each page. If it clicked, it grays out that page.&lt;/li&gt;
&lt;li&gt;If user clicks a page, rotate it 90 degree clockwise.&lt;/li&gt;
&lt;li&gt;Add a header; shows the title of pdf file on top. At the right end there is a save button. If the user clicks it user will get a copy of pdf file with changes the user made. It includes gray-out (dropping that page) and rotation of the page.&lt;/li&gt;
&lt;li&gt;Add print button.&lt;/li&gt;
&lt;li&gt;Each PDF pages are not fit into the whole printable area. Also there is header and footer from the browser. Print should preserve PDF&amp;rsquo;s shape.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;비록 5년정도 영어로 밥벌이를 하긴 했지만 난 기본적으로 영어못함인간이기 때문에 문법이라거나 이상한 부분이 많이 있을 것이다. 이따위로 이상하게 영작하더라도 찰떡같이 알아듣더라 정도로 이해해주면 감사하겠다. 참고로 개인적인 느낌이지만 영어로 설명하는 편이 모델이 잘 알아먹는듯한 느낌이 있다. 말도 좀 더 정돈되는 느낌도 있고. 도구 구현단계가 5단계인데 프롬프트가 6개인 이유는, 5번에서 너무 단순하게 언급했더니 프린트 레이아웃이 이상하게 배치돼서 문제점을 지적하고 어떻게 해줬으면 하는지 추가 요청이 들어갔기 때문이다. 이 외에는 생각했던 것 거의 그대로, 가끔은 그 이상으로 깔끔하게 말아줘서 정정해줄 필요도 없었다.&lt;/p&gt;
&lt;p&gt;심지어 이 프로젝트는 도구를 구성하는 모든 구성요소가 - &lt;a href="https://tailwindcss.com/"&gt;tailwind CSS&lt;/a&gt;, &lt;a href="https://mozilla.github.io/pdf.js/"&gt;pdf.js&lt;/a&gt;, &lt;a href="https://pdf-lib.js.org/"&gt;pdf-lib&lt;/a&gt; - 이미 다 준비돼 있고 조립만 하면 되는 상황이라 더 간단하게 만들 수 있었다.&lt;/p&gt;
&lt;h2 id="result"&gt;Result&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://gwangyi.github.io/rotpapier/"&gt;rotpapier&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;간단하고 안 중요한 도구라면 이제 찾아보고 맞나 안맞나 확인하는 것보다 그냥 바이브코딩으로 쪄내는게 더 빠른 시대가 됐다. 간단하게 쪄내기 위해서는 이를 이루는 구성요소들이 충분히 잘 만들어져 있어야 하고 그 구성요소는 이렇게 간단하게 바이브코드 할 수 없기 때문에 &amp;ldquo;걍 바이브코딩 하져?&amp;rdquo; 같은 소리를 언제나 할 수 있는 것은 절대 아니지만, 취미 수준에서는 무시무시한 미래가 찾아오고 있다. 문제는 옛날엔 이렇게 도구를 툭툭 짜보면서 웹기술도 배우고 PDF 파일 포맷도 공부하고 할 수 있었는데 이제는 그걸 하나도 몰라도 도구가 쪄져서 나오니 앞으로 취미생활하며 공부하기는 더 어렵겠다 싶은 생각도 든다.&lt;/p&gt;</description></item><item><title>pyenv를 termux에서 써보자 (2)</title><link>https://gwangyi.github.io/posts/pyenv-in-termux-2/</link><pubDate>Mon, 03 Apr 2023 00:00:00 +0000</pubDate><guid>https://gwangyi.github.io/posts/pyenv-in-termux-2/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;원래는 &lt;a href="https://gwangyi.github.io/posts/pyenv-in-termux/"&gt;이전 포스트&lt;/a&gt;를 작성한 뒤에 바로 이어서 2편을 쓰려고 했는데, 이직과 해외 적응, 기타등등의 여러가지 일들이 있다 보니 아주 잊어버리고 있었다. 그러다 며칠 전에 기념할만한 &lt;a href="https://github.com/gwangyi/gwangyi.github.io/issues/1"&gt;첫 이슈&lt;/a&gt;가 잊고 있었던 글을 다시 기억나게 해 줘서 다시 테스트해본 뒤에 장장 4년의 기간을 뛰어넘어 예고했던 내용들을 다룰 수 있게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://gwangyi.github.io/posts/pyenv-in-termux/"&gt;이전 포스트&lt;/a&gt;에서는 &amp;lsquo;일단&amp;rsquo; &lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;를 설치하고 실행은 해 봤지만 아마 모두들 컴파일에 실패했을 것이다. 이 포스트에서 다룰 내용은, 실패 내용을 하나씩 짚어보며 트러블슈팅을 해볼 것이다.&lt;/p&gt;
&lt;h3 id="tldr"&gt;tl;dr&lt;/h3&gt;
&lt;p&gt;성격 급하신 분을 위한 명령어를 먼저 보여드리자면 다음과 같은 명령으로 빌드할 수 있다. python 버전이 바뀌면 바뀐 버전으로 고쳐줄 필요가 있고, pyenv 설치는 따로 해 줄 필요가 있다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkg in build-essential gdbm libandroid-posix-semaphore libandroid-support &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; libbz2 libcrypt libexpat libffi liblzma libsqlite ncurses ncurses-ui-libs &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; openssl readline zlib xorgproto patchelf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CONFIGURE_OPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--with-system-ffi --with-system-expat --with-ensurepip&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CONFIGURE_OPTS&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; --enable-loadable-sqlite-extensions&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CONFIGURE_OPTS&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; ac_cv_func_shm_open=yes&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CONFIGURE_OPTS&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; ac_cv_func_shm_unlink=yes&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; CONFIGURE_OPTS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget -O - &lt;span class="s2"&gt;&amp;#34;https://api.github.com/repos/termux/termux-packages/contents/packages/python?ref=4f87b638eb93d17b89c499c3ac7d4034d5e1ac5f&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jq &lt;span class="s1"&gt;&amp;#39;.[]|select(.name|endswith(&amp;#34;.patch&amp;#34;))|.download_url&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; xargs wget -O - -q &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sed &lt;span class="s2"&gt;&amp;#34;s%@TERMUX_PREFIX@%&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;%g; s%^+++ [^/ ]*%+++ .%&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;CFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--target=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;gcc -dumpmachine &lt;span class="p"&gt;|&lt;/span&gt; sed &lt;span class="s2"&gt;&amp;#34;s/-android[0-9]\\+/-android&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;getprop ro.build.version.sdk&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; -I&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;/include&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;LDFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-L&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;/lib&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;LN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ln -s&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;LIBCRYPT_LIBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-lcrypt&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;LD_LIBRARY_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.pyenv/versions/3.11.2/lib&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pyenv install -p -v 3.11.2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;patchelf --set-rpath &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.pyenv/versions/3.11.2/lib:&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;/lib &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.pyenv/versions/3.11.2/bin/python3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="goal"&gt;Goal&lt;/h2&gt;
&lt;p&gt;이 문서의 목적은, &lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;를 이용해 이 포스팅 당시의 최신 &lt;a href="https://github.com/python/cpython"&gt;CPython&lt;/a&gt; 버젼인 3.11.2 버전을 최대한 &lt;a href="https://termux.com/"&gt;Termux&lt;/a&gt;에서 지원하는 바이너리 버전의 Python 패키지와 비슷하게 소스로부터 빌드하는 것이다. 문서를 잘 따라가면, 마지막에는 실행 가능한 Python 바이너리를 얻을 수 있을 것이다.&lt;/p&gt;
&lt;h3 id="non-goal"&gt;Non-goal&lt;/h3&gt;
&lt;p&gt;모든 버전의 Python을 빌드할 수 있도록 하는 것은 이 문서의 범위를 벗어난다. 물론 트러블슈팅 과정을 적절히 잘 변형시켜 적용한다면 다른 버전의 Python, 더 나아가서 CPython을 제외한 다른 구현체를 빌드해 볼 수도 있겠지만 그 부분은 여러분의 숙제로 남기도록 하겠다.&lt;/p&gt;
&lt;h2 id="configure_opts"&gt;&lt;code&gt;CONFIGURE_OPTS&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;아마 제일 먼저 만날 에러는 &lt;code&gt;getrandom()&lt;/code&gt; 함수를 찾을 수 없다는 에러이겠지만, 일단 그걸 다루기 전에 먼저 &lt;code&gt;CONFIGURE_OPTS&lt;/code&gt;를 살펴보는 것이 좋겠다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;는 Python 소스 코드를 내려받고 빌드하는 과정을 &lt;a href="https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md"&gt;python-build&lt;/a&gt;라는 플러그인을 통해 처리하고 있다. 문서를 잘 읽어보면, &lt;a href="https://gwangyi.github.io/posts/pyenv-in-termux/"&gt;이전 포스팅&lt;/a&gt;에서 다뤘던 &lt;a href="https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md#applying-patches-to-python-before-compiling"&gt;patch&lt;/a&gt; 넣는 법도 찾아볼 수 있다. 우리가 지금 보려고 하는 부분은 그 윗 문단인 &lt;a href="https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md#special-environment-variables"&gt;Special environment variables&lt;/a&gt; 부분이다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/termux/termux-packages/tree/master/packages/python"&gt;Termux의 Python 패키지&lt;/a&gt;를 보면 &lt;a href="https://github.com/termux/termux-packages/blob/master/packages/python/build.sh"&gt;&lt;code&gt;build.sh&lt;/code&gt;&lt;/a&gt; 파일이 보인다. 여기서 &lt;code&gt;TERMUX_PKG_EXTRA_CONFIGURE_ARGS&lt;/code&gt;에 들어가 있는 옵션들은 패키지 빌드 과정에서 &lt;code&gt;./configure&lt;/code&gt; 명령에 패러미터로 전달되는 옵션들이다. 잘 보면 &lt;code&gt;--&lt;/code&gt;로 시작하는 플래그도 보이고, &lt;code&gt;ac_cv_func_&lt;/code&gt;로 시작하는 &lt;code&gt;configure&lt;/code&gt; 과정에서 체크하는 함수 지원 여부를 강제로 지정하기 위한 옵션들도 보인다. 이런 명령을 &lt;a href="https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md"&gt;python-build&lt;/a&gt;가 &lt;code&gt;configure&lt;/code&gt;에게 전달하게 할 때 쓰는 환경변수가 &lt;code&gt;CONFIGURE_OPTS&lt;/code&gt;이다.&lt;/p&gt;
&lt;p&gt;일단 &lt;code&gt;ac_cv_func_&lt;/code&gt; 종류는 강제하기에는 조금 찝찝하니, 일단 플래그 종류만 뽑아서 넘겨주도록 하자.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;CONFIGURE_OPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--with-system-ffi --with-system-expat --without-ensurepip --enable-loadable-sqlite-extensions&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="setting-target-to-precise-android-api-level"&gt;Setting Target to precise Android API level&lt;/h2&gt;
&lt;p&gt;컴파일을 해 보면 이제 다음과 같은 에러를 만나게 될 것이다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Python/bootstrap_hash.c:116:17: error: call to undeclared function &amp;#39;getrandom&amp;#39;; ISO C99 and later do not support implicit function declarations [-Werror,-Wimplicit-function-declaration]
n = getrandom(dest, n, flags);
^
Python/bootstrap_hash.c:120:17: error: call to undeclared function &amp;#39;getrandom&amp;#39;; ISO C99 and later do not support implicit function declarations [-Werror,-Wimplicit-function-declaration]
n = getrandom(dest, n, flags);
^
2 errors generated.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이런 일이 안 생기게 하려고 만드는게 &lt;code&gt;./configure&lt;/code&gt; 스크립트인데 &lt;a href="https://termux.com/"&gt;Termux&lt;/a&gt; 환경에서는 짜잔, 그런데 그게 실제로 일어났습니다!&lt;/p&gt;
&lt;p&gt;왜 이런 일이 생기냐 하면, &lt;a href="https://android.googlesource.com/platform/prebuilts/ndk/+/dev/platform/sysroot/usr/include/sys/random.h#67"&gt;&lt;code&gt;getrandom()&lt;/code&gt;&lt;/a&gt; 함수가 정의된 데를 보면 &lt;code&gt;#if __ANDROID_API__ &amp;gt;= 28&lt;/code&gt;으로 둘러쌓여 있기 때문이다. 그리고 여러분의 핸드폰은 아마도 Android API 28 이상을 돌리고 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://termux.com/"&gt;Termux&lt;/a&gt; Issue 중에 이 문제를 다루고 있는 이슈가 여러 개가 이미 있다. 예를 들면 &lt;a href="https://github.com/termux/termux-packages/issues/2469"&gt;이 것&lt;/a&gt;이 바로 그 중 하나인데, &lt;a href="https://termux.com/"&gt;Termux&lt;/a&gt; 메인테이너들은 이 API level이 Termux 앱 자체가 지원하는 최소 API level인 24를 갖도록 하는 것을 방침으로 삼은 듯하다. 그러다보니 컴파일러의 타겟은 API level 24가 되어 24 이후 API들이 컴파일 과정에서 사라지는 것이 원인이다.&lt;/p&gt;
&lt;p&gt;그런데 왜 &lt;code&gt;./configure&lt;/code&gt;에서는 지원하는 것으로 판정이 되었는가? 하는 의문이 들 것이다. 이건 &lt;code&gt;./configure&lt;/code&gt;에서 함수 지원 여부를 확인하는 방법에서 생기는 문제점인데, 함수의 존재 여부를 판단하는데 있어 해당하는 심볼의 존재 여부만 체크하기 때문이다. 우리가 만난 &lt;code&gt;getrandom()&lt;/code&gt; 함수를 예를 들면, 정상적으로 쓸 때는 &lt;code&gt;#include &amp;lt;sys/random.h&amp;gt;&lt;/code&gt;로 헤더파일을 불러와서 prototype을 가져오지만 &lt;code&gt;./configure&lt;/code&gt;에서는 구체적인 prototype에는 관심이 없고 적절한 헤더파일이 어느 것인지를 체크하지 않기 때문에 대충 &lt;code&gt;char getrandom()&lt;/code&gt;으로 정의한 다음에 컴파일 되는지만 체크하는 것이다. 그러면 우리 시스템은 일단 최신 API를 지원하기 때문에 심볼이 있어 컴파일은 됐지만 실제 코드에서는 prototype 정의가 배제되어서 컴파일 오류가 발생하는 것이다.&lt;/p&gt;
&lt;p&gt;다행히 이 &lt;code&gt;__ANDROID_API__&lt;/code&gt;는 컴파일러에 전달하는 옵션으로 지정해줄 수 있다. 일종의 cross-compile이 되는 셈이라 &lt;code&gt;--target&lt;/code&gt; 플래그를 주면 된다. Android의 기본 컴파일러인 clang은 이 플래그로 전달되는 triplet에서부터 Android API level을 추론하도록 되어 있다(&lt;a href="https://android.googlesource.com/platform/ndk/+/refs/heads/ndk-release-r21/docs/BuildSystemMaintainers.md#Target-Selection"&gt;참고&lt;/a&gt;). 다음 명령어를 통해서 현재 기본값으로 사용되고 있는 target이 무엇인지 확인해볼 수 있다(&lt;a href="https://gcc.gnu.org/onlinedocs/gcc/Developer-Options.html"&gt;참고&lt;/a&gt;).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gcc -dumpmachine
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# aarch64-unknown-linux-android24&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;내 핸드폰에서는 위와 같은 결과가 나온다. 여기서 첫 번째는 CPU architecture, 두 번째는 vendor, 세 번째와 네 번째는 합쳐서 OS를 의미한다. 때때로 OS는 두 부분으로 나뉘어지기도 하는데 Termux clang이 바로 그런 케이스가 되겠다. 여기서 네 번째 부분으로부터 Android API level을 가져온다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gcc -dumpmachine &lt;span class="p"&gt;|&lt;/span&gt; sed &lt;span class="s2"&gt;&amp;#34;s/-android[0-9]\\+/-android&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;getprop ro.build.version.sdk&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# aarch64-unknown-linux-android33&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;이걸 이제 컴파일러에 전달하면 된다. 전통적인 &lt;code&gt;CFLAGS&lt;/code&gt; 환경변수를 사용할 수 있다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget -O - &lt;span class="s2"&gt;&amp;#34;https://api.github.com/repos/termux/termux-packages/contents/packages/python?ref=4f87b638eb93d17b89c499c3ac7d4034d5e1ac5f&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jq &lt;span class="s1"&gt;&amp;#39;.[]|select(.name|endswith(&amp;#34;.patch&amp;#34;))|.download_url&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; xargs wget -O - -q &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sed &lt;span class="s2"&gt;&amp;#34;s%@TERMUX_PREFIX@%&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;%g; s%^+++ [^/ ]*%+++ .%&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;CFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--target=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;gcc -dumpmachine &lt;span class="p"&gt;|&lt;/span&gt; sed &lt;span class="s2"&gt;&amp;#34;s/-android[0-9]\\+/-android&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;getprop ro.build.version.sdk&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pyenv install -p -v 3.11.2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;이제 새로운 에러를 만날 수 있다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ln: failed to create hard link &amp;#39;libpython3.11.so&amp;#39; =&amp;gt; &amp;#39;libpython3.11.so.1.0&amp;#39;: Permission denied
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="soft-link-vs-hard-link"&gt;Soft-link vs Hard-link&lt;/h2&gt;
&lt;p&gt;TLDR; Hard-link 대신 Soft-link를 사용하도록 &lt;code&gt;LN=&amp;quot;ln -s&amp;quot;&lt;/code&gt;를 주면 된다.&lt;/p&gt;
&lt;p&gt;일반적인 Linux File System에는 두 가지 종류의 link가 있다. File system 메타데이터 차원에서 동일한 파일 내용을 가리키는 두 개의 파일 엔트리로 구현되는 hard link와 다른 파일을 가리키는 특수한 파일로서의 soft link가 그 것이다. Hard link는 원본과 링크 둘 사이에 차이점이 없어 일반적인 파일로 인식되며 둘 중 어느 하나가 지워져도 다른 하나가 동작하지만, Soft link는 원본과 링크는 구별되며, 원본이 지워지면 링크도 동작하지 않는 차이점이 있다.&lt;/p&gt;
&lt;p&gt;Hard link는 &lt;a href="https://man7.org/linux/man-pages/man2/link.2.html"&gt;link system call&lt;/a&gt;을 통해 생성하는데, Soft link는 &lt;a href="https://man7.org/linux/man-pages/man2/symlink.2.html"&gt;symlink system call&lt;/a&gt;으로 생성한다. 그런데 &lt;a href="https://code.google.com/archive/p/android-developer-preview/issues/3150"&gt;An droid M부터는 link syscall이 차단되었다&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Makefile&lt;/code&gt; 내용을 잘 살펴보면, 링크 생성을 위해 &lt;code&gt;LN&lt;/code&gt; 환경변수를 사용하는 것을 볼 수 있다. 이것 역시 &lt;code&gt;configure&lt;/code&gt; 스크립트에서 받아 넣어주는 부분이므로, &lt;code&gt;pyenv install&lt;/code&gt; 명령을 줄 때 환경변수에 보태 주면 링크를 위해 &lt;code&gt;ln&lt;/code&gt; 대신 soft link를 만들어주는 &lt;code&gt;ln -s&lt;/code&gt; 명령을 사용하게 할 수 있다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget -O - &lt;span class="s2"&gt;&amp;#34;https://api.github.com/repos/termux/termux-packages/contents/packages/python?ref=4f87b638eb93d17b89c499c3ac7d4034d5e1ac5f&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jq &lt;span class="s1"&gt;&amp;#39;.[]|select(.name|endswith(&amp;#34;.patch&amp;#34;))|.download_url&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; xargs wget -O - -q &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sed &lt;span class="s2"&gt;&amp;#34;s%@TERMUX_PREFIX@%&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;%g; s%^+++ [^/ ]*%+++ .%&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;CFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--target=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;gcc -dumpmachine &lt;span class="p"&gt;|&lt;/span&gt; sed &lt;span class="s2"&gt;&amp;#34;s/-android[0-9]\\+/-android&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;getprop ro.build.version.sdk&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;LN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ln -s&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pyenv install -p -v 3.11.2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;이러면 일단 빌드는 되는데, 반쪽짜리로 빌드가 된다. 예를 들면 &lt;code&gt;readline&lt;/code&gt; 라이브러리가 정상적으로 링크되지 않아서 프롬프트에서 화살표 키를 인식하지 못한다거나, &lt;code&gt;ctypes&lt;/code&gt;를 사용할 수 없다거나 하는 일이 생긴다. 왜 그런가 하면, 로그를 잘 살펴보면 다음과 같은 에러를 발견할 수 있을 것이다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;...
*** WARNING: renaming &amp;#34;_ctypes&amp;#34; since importing it failed: dlopen failed: cannot locate symbol &amp;#34;ffi_type_pointer&amp;#34; referenced by &amp;#34;/data/data/com.termux/files/usr/tmp/python-build.20230330154219.32391/Python-3.11.2/build/lib.linux-aarch64-3.11/_ctypes.cpython-311.so&amp;#34;...
The necessary bits to build these optional modules were not found:
_crypt _curses _curses_panel
_posixshmem _tkinter nis
readline spwd
To find the necessary bits, look in setup.py in detect_modules() for the module&amp;#39;s name.
Following modules built successfully but were removed because they could not be imported:
_ctypes
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;참고로 여기서부터는 &lt;em&gt;일단은&lt;/em&gt; 빌드가 완료되었기 때문에 파이썬 소스트리는 자동으로 삭제되어 내용을 볼 수 없다. 꼼수로 다시 &lt;code&gt;LN=&lt;/code&gt; 부분을 지우면 빌드에 실패하면서 소스트리가 남고, &lt;code&gt;Makefile&lt;/code&gt;을 수정해서 &lt;code&gt;LN=ln -s&lt;/code&gt;로 고쳐준 다음 &lt;code&gt;make&lt;/code&gt; 명령을 통해 테스트를 해볼 수 있다.&lt;/p&gt;
&lt;h2 id="readline-and-curses"&gt;readline and curses&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;readline&lt;/code&gt;과 &lt;code&gt;curses&lt;/code&gt;는 프롬프트로 쓰는 인터프리터에게 매우 중요한 라이브러리들이다. 이게 없으면 방향키로 커서를 옮길 수도 없고 히스토리를 사용할 수도 없다&amp;hellip;&lt;/p&gt;
&lt;p&gt;메시지에 나온 대로 &lt;code&gt;setup.py&lt;/code&gt;의 &lt;code&gt;detect_modules()&lt;/code&gt;를 살펴보자. 참고로 &lt;code&gt;setup.py&lt;/code&gt;는 3.12부터는 &lt;a href="https://github.com/python/cpython/commit/81dca70d704d0834d8c30580e648a973250b2973"&gt;삭제될 예정&lt;/a&gt;이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Determine if readline is already linked against curses or tinfo.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sysconfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_config_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HAVE_LIBREADLINE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sysconfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_config_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;WITH_EDITLINE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;readline_lib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;edit&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;readline_lib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;readline&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;do_readline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compiler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_library_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lib_dirs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;readline_lib&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;CROSS_COMPILING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;run_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; -d &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; | grep &amp;#39;(NEEDED)&amp;#39; &amp;gt; &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sysconfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_config_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;READELF&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;do_readline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tmpfile&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;위 코드는 &lt;code&gt;detect_readline_curses()&lt;/code&gt;의 일부이다. &lt;code&gt;lib_dirs&lt;/code&gt;가 살짝 의심스럽기 때문에 이걸 찾아가 보면 다음 부분이 나온다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;CROSS_COMPILING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lib_dirs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compiler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;library_dirs&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;system_lib_dirs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inc_dirs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compiler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;include_dirs&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;system_include_dirs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;system_lib_dirs&lt;/code&gt;, &lt;code&gt;system_include_dirs&lt;/code&gt;는 크로스 컴파일이 아닌 이상 하드코딩된 &lt;code&gt;/usr/include&lt;/code&gt;나 &lt;code&gt;/lib&lt;/code&gt; 등을 참조하고 있기 때문에 독특한 prefix를 사용하는 Termux 에서는 잘못된 디렉토리를 참조하게 된다.&lt;/p&gt;
&lt;p&gt;그리고 compiler의 &lt;code&gt;library_dirs&lt;/code&gt;는 &lt;code&gt;add_ldflags_cppflags()&lt;/code&gt;에서 확장된다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_ldflags_cppflags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Add paths specified in the environment variables LDFLAGS and&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# CPPFLAGS for header and library files.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# We must get the values from the Makefile and not the environment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# directly since an inconsistently reproducible issue comes up where&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# the environment variable is not set even though the value were passed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# into configure and stored in the Makefile (issue found on OS X 10.3).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;env_var&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dir_list&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;LDFLAGS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-R&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compiler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runtime_library_dirs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;LDFLAGS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-L&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compiler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;library_dirs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CPPFLAGS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-I&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compiler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;include_dirs&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;env_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sysconfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_config_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env_var&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;env_val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;dirs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;append&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# To prevent argparse from raising an exception about any&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# options in env_val that it mistakes for known option, we&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# strip out all double dashes and any dashes followed by a&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# character that is not for the option we are dealing with.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Please note that order of the regex is important! We must&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# strip out double-dashes first so that we don&amp;#39;t end up with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# substituting &amp;#34;--Long&amp;#34; to &amp;#34;-Long&amp;#34; and thus lead to &amp;#34;ong&amp;#34; being&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# used for a library directory.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;env_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;(^|\s+)-(-|(?!&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;))&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;arg_name&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env_val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_known_args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env_val&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;add_dir_to_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dir_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;a href="https://docs.python.org/3/library/sysconfig.html#sysconfig.get_config_var"&gt;&lt;code&gt;sysconfig.get_config_var()&lt;/code&gt;&lt;/a&gt;는 실행중인 Python interpreter가 컴파일 될 당시의 구성을 참고할 때 쓸 수 있는 함수다. 보통은 바이너리 확장을 컴파일 할 때 Python interpreter 본체와 환경 구성을 맞추기 위해서 사용하는 것이다. 우리가 Python을 빌드할 때 prefix 정보를 이 부분으로는 주지 않았기 때문에 바이너리 확장이 하나도 제대로 빌드가 안되는 불상사가 발생한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;setup.py&lt;/code&gt;는 python 코드이기 때문에, 잘 이해가 안되는 부분에서 &lt;code&gt;import pdb; pdb.set_trace()&lt;/code&gt;로 디버거를 붙여 확인해볼 수 있다.&lt;/p&gt;
&lt;p&gt;보아하니 이 디렉토리들을 &lt;code&gt;CPPFLAGS&lt;/code&gt;, &lt;code&gt;LDFLAGS&lt;/code&gt;에 주어진 &lt;code&gt;-Ixxx&lt;/code&gt;, &lt;code&gt;-Lxxx&lt;/code&gt; 플래그로 받아오는 것으로 보인다. 이제 이 옵션까지 줘서 Python을 빌드해 보자.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget -O - &lt;span class="s2"&gt;&amp;#34;https://api.github.com/repos/termux/termux-packages/contents/packages/python?ref=4f87b638eb93d17b89c499c3ac7d4034d5e1ac5f&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jq &lt;span class="s1"&gt;&amp;#39;.[]|select(.name|endswith(&amp;#34;.patch&amp;#34;))|.download_url&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; xargs wget -O - -q &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sed &lt;span class="s2"&gt;&amp;#34;s%@TERMUX_PREFIX@%&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;%g; s%^+++ [^/ ]*%+++ .%&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;CFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--target=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;gcc -dumpmachine &lt;span class="p"&gt;|&lt;/span&gt; sed &lt;span class="s2"&gt;&amp;#34;s/-android[0-9]\\+/-android&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;getprop ro.build.version.sdk&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; -I&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;/include&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;LDFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-L&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;/lib&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;LN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ln -s&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pyenv install -p -v 3.11.2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;이제 빌드되지 않는 모듈은 다음과 같다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;The necessary bits to build these optional modules were not found:
_crypt _posixshmem _tkinter
nis spwd
To find the necessary bits, look in setup.py in detect_modules() for the module&amp;#39;s name.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;여기서 다시 빌드 로그의 &lt;code&gt;configure&lt;/code&gt; 부분을 보면 다음 부분을 찾을 수 있다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;checking for stdlib extension module _multiprocessing... yes
checking for stdlib extension module _posixshmem... missing
checking for stdlib extension module fcntl... yes
checking for stdlib extension module mmap... yes
checking for stdlib extension module _socket... yes
checking for stdlib extension module grp... yes
checking for stdlib extension module ossaudiodev... yes
checking for stdlib extension module pwd... yes
checking for stdlib extension module resource... yes
checking for stdlib extension module _scproxy... n/a
checking for stdlib extension module spwd... missing
checking for stdlib extension module syslog... yes
checking for stdlib extension module termios... yes
checking for stdlib extension module pyexpat... yes
checking for stdlib extension module _elementtree... yes
checking for stdlib extension module _md5... yes
checking for stdlib extension module _sha1... yes
checking for stdlib extension module _sha256... yes
checking for stdlib extension module _sha512... yes
checking for stdlib extension module _sha3... yes
checking for stdlib extension module _blake2... yes
checking for stdlib extension module _crypt... missing
checking for stdlib extension module _decimal... yes
checking for stdlib extension module _gdbm... yes
checking for stdlib extension module nis... missing
checking for stdlib extension module _sqlite3... yes
checking for stdlib extension module _tkinter... missing
checking for stdlib extension module _uuid... yes
checking for stdlib extension module zlib... yes
checking for stdlib extension module _bz2... yes
checking for stdlib extension module _lzma... yes
checking for stdlib extension module _ssl... yes
checking for stdlib extension module _hashlib... yes
checking for stdlib extension module _testcapi... yes
checking for stdlib extension module _testclinic... yes
checking for stdlib extension module _testinternalcapi... yes
checking for stdlib extension module _testbuffer... yes
checking for stdlib extension module _testimportmultiple... yes
checking for stdlib extension module _testmultiphase... yes
checking for stdlib extension module _xxtestfuzz... yes
checking for stdlib extension module _ctypes_test... yes
checking for stdlib extension module xxlimited... yes
checking for stdlib extension module xxlimited_35... yes
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;보다시피 &lt;code&gt;missing&lt;/code&gt;으로 되어 있는 모듈들이 위에 나오는 것들과 겹치는 것을 알 수 있다. &lt;code&gt;configure&lt;/code&gt; 스크립트를 찾아보면 무슨 조건으로 해당 모듈이 &lt;code&gt;missing&lt;/code&gt; 취급이 됐는지 확인할 수 있으므로 한번 잘 찾아보자.&lt;/p&gt;
&lt;h2 id="_posixshmem-spwd-nis"&gt;_posixshmem, spwd, nis&lt;/h2&gt;
&lt;p&gt;이 모듈들은 결론부터 말하자면 빌드할 수 없다. &lt;code&gt;_posixshmem&lt;/code&gt;은 &lt;code&gt;shm_open()&lt;/code&gt;, &lt;code&gt;shm_unlink()&lt;/code&gt; 두 시스템 콜에 &lt;a href="https://github.com/python/cpython/blob/v3.11.2/configure#L22877"&gt;의존하고 있는데&lt;/a&gt; Android는 정책적으로 이 &lt;a href="https://android.googlesource.com/platform/ndk/+/4e159d95ebf23b5f72bb707b0cb1518ef96b3d03/docs/system/libc/SYSV-IPC.TXT"&gt;시스템 콜을 지원하지 않는다&lt;/a&gt;. 비슷하게 &lt;code&gt;spwd&lt;/code&gt;는 &lt;code&gt;getspent()&lt;/code&gt;, &lt;code&gt;getspnam()&lt;/code&gt;에 &lt;a href="https://github.com/python/cpython/blob/v3.11.2/configure#L24504"&gt;의존하고 있는데&lt;/a&gt;, Termux에서는 빌드가 안 된다. 그리고 있어도 별 의미가 없을 것 같은데, shadow passwd는 Android에서 쓸 일이 거의 없을 것이다. &lt;code&gt;nis&lt;/code&gt; 모듈은 Solaris OS의 yellow page와 관련된 모듈이다. 마찬가지로 빌드가 안되는데, &lt;code&gt;yp_match()&lt;/code&gt;에 의존하고 이건 Termux에서 빌드가 안된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;_posixshmem&lt;/code&gt;의 경우는 조금 특이한데, termux package build script를 보면 강제로 &lt;code&gt;ac_cv_func_&lt;/code&gt; 패러미터를 통해 &lt;code&gt;yes&lt;/code&gt;로 만들고 넘어간다. 왜 이렇게 하냐면, 패치를 통해 &lt;code&gt;shm_open()&lt;/code&gt;, &lt;code&gt;shm_unlink()&lt;/code&gt; 함수를 구현해서 집어넣기 때문이다.&lt;/p&gt;
&lt;h2 id="_crypt-_tkinter"&gt;_crypt, _tkinter&lt;/h2&gt;
&lt;p&gt;두 모듈은 플래그가 덜 주어져서 빌드가 안된 모듈이다. &lt;code&gt;_crypt&lt;/code&gt;는 &lt;code&gt;-lcrypt&lt;/code&gt; 옵션이 빠져서 생긴 문제라서 해결이 비교적 간단하다. 사실 원래는 &lt;code&gt;pkg-config&lt;/code&gt;에서 알아서 찾아주어야 하는 플래그인데 제대로 동작하지 않는 듯하다&amp;hellip; &lt;code&gt;LIBCRYPT_LIBS&lt;/code&gt;에 주어서 빌드할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;_tkinter&lt;/code&gt;는 &lt;code&gt;X11/X.h&lt;/code&gt; 헤더파일이 누락됐기 때문이다. 이건 &lt;code&gt;xorgproto&lt;/code&gt; 패키지에 있으니 설치해주자.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkg in build-essential gdbm libandroid-posix-semaphore libandroid-support &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; libbz2 libcrypt libexpat libffi liblzma libsqlite ncurses ncurses-ui-libs &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; openssl readline zlib xorgproto
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CONFIGURE_OPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--with-system-ffi --with-system-expat --with-ensurepip&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CONFIGURE_OPTS&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; --enable-loadable-sqlite-extensions&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CONFIGURE_OPTS&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; ac_cv_func_shm_open=yes&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CONFIGURE_OPTS&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; ac_cv_func_shm_unlink=yes&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; CONFIGURE_OPTS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget -O - &lt;span class="s2"&gt;&amp;#34;https://api.github.com/repos/termux/termux-packages/contents/packages/python?ref=4f87b638eb93d17b89c499c3ac7d4034d5e1ac5f&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jq &lt;span class="s1"&gt;&amp;#39;.[]|select(.name|endswith(&amp;#34;.patch&amp;#34;))|.download_url&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; xargs wget -O - -q &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sed &lt;span class="s2"&gt;&amp;#34;s%@TERMUX_PREFIX@%&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;%g; s%^+++ [^/ ]*%+++ .%&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;CFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--target=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;gcc -dumpmachine &lt;span class="p"&gt;|&lt;/span&gt; sed &lt;span class="s2"&gt;&amp;#34;s/-android[0-9]\\+/-android&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;getprop ro.build.version.sdk&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; -I&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;/include&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;LDFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-L&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;/lib&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;LN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ln -s&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;LIBCRYPT_LIBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-lcrypt&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pyenv install -p -v 3.11.2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;짜잔, 이제 빌드가 완료되었다! &lt;code&gt;pyenv shell 3.11.2&lt;/code&gt; 명령으로 활성화해서 한번 테스트 해보자.&lt;/p&gt;
&lt;p&gt;그런데, 여기서 끝이 아니다. 만약 termux의 python 패키지를 설치했었다면 지금 설치를 실패할 것이고, 추후에 설치하게 된다면 예상치 못한 부분에서 이상한 에러가 날 가능성이 있다. 이건 libpython 라이브러리가 꼬이기 때문에 생기는 문제다.&lt;/p&gt;
&lt;h2 id="runpath"&gt;RUNPATH&lt;/h2&gt;
&lt;p&gt;그런데, 이미 termux에서 python을 설치해 사용중이었다면 다음과 같은 에러를 만날 수 있다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; File &amp;#34;/data/data/com.termux/files/home/.pyenv/versions/3.11.2/lib/python3.11/sysconfig.py&amp;#34;, line 531, in _init_posix
_temp = __import__(name, globals(), locals(), [&amp;#39;build_time_vars&amp;#39;], 0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ModuleNotFoundError: No module named &amp;#39;_sysconfigdata__linux_&amp;#39;
error: failed to install pip via get-pip.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;결론부터 말하자면, 지금 pyenv로 빌드한 libpython3 라이브러리 대신 이미 깔려 있는 termux의 libpython3이 링크되어 들어가면서 생기는 문제다. termux의 python은 &lt;code&gt;sys.implementation&lt;/code&gt;에 &lt;code&gt;_multiarch&lt;/code&gt; 속성이 없다. 하지만 우리가 방금 빌드한 python에는 이게 있어서 꼬이는 문제이다. 확인해보고 싶으면 직접 &lt;code&gt;$PREFIX/tmp/python-build.*/Python-3.11.2&lt;/code&gt;에 들어가서 &lt;code&gt;make install&lt;/code&gt;으로 &lt;code&gt;$HOME/.pyenv/versions/3.11.2/&lt;/code&gt;에 설치한 다음 &lt;code&gt;$HOME/.pyenv/versions/3.11.2/bin/python3&lt;/code&gt;을 실행시킨 뒤 &lt;code&gt;/proc/self/maps&lt;/code&gt; 내용을 읽어보자.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;ls -d &lt;span class="nv"&gt;$PREFIX&lt;/span&gt;/tmp/python-build.*/Python-3.11.2 &lt;span class="p"&gt;|&lt;/span&gt; tail -n 1&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;make install
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.pyenv/versions/3.11.2/bin/python3 -c &lt;span class="s1"&gt;&amp;#39;print(open(&amp;#34;/proc/self/maps&amp;#34;).read())&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep libpython3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ... /data/data/com.termux/files/usr/lib/libpython3.11.so.1.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;아마 위와 같이 &lt;code&gt;$PREFIX/lib&lt;/code&gt; 안에 있는 &lt;code&gt;libpython3.11.so&lt;/code&gt; 를 가리키고 있으면 당첨이다. 이건 Termux의 특수성 때문에 일어나는 일인데, Termux는 표준 Linux 디렉토리를 가지고 있지 않기(사실은 못하기) 때문에 라이브러리 패스를 강제로 지정하고 있기 때문이다. &lt;code&gt;RUNPATH&lt;/code&gt;라고 하는 ELF 포맷 항목을 &lt;code&gt;$PREFIX/lib&lt;/code&gt;으로 강제 지정하는 코드가 어딘가에 들어 있는 듯 한데, 어디인지 찾지는 못했다. 확인해보려면 &lt;code&gt;objdump&lt;/code&gt; 명령으로 확인해볼 수 있다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;int main() { return 0; }&amp;#34;&lt;/span&gt; &amp;gt; test.c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gcc test.c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;objdump -x a.out &lt;span class="p"&gt;|&lt;/span&gt; grep RUNPATH
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# RUNPATH /data/data/com.termux/files/usr/lib&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;이 RUNPATH가 하는 일은 &lt;a href="https://man7.org/linux/man-pages/man8/ld.so.8.html"&gt;ld.so&lt;/a&gt; ELF loader가 shared object 파일을 로드할 때 찾아볼 디렉토리를 실행파일 안에 새겨놓는 역할을 한다. &lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt;와 비슷한데, 이건 환경변수가 아니라 실행파일 안에 들어가 있다는 것이 차이점이다. Termux의 기묘한 디렉토리 구조를 큰 문제 없이 주입하려면 &lt;em&gt;조용히&lt;/em&gt; 이걸 삽입하는게 필요한 모양이다.&lt;/p&gt;
&lt;p&gt;이게 골치아픈 점은, 저 RUNPATH를 덮어쓰는 일반적인 방법인 &lt;code&gt;-Wl,-rpath=&lt;/code&gt; 플래그를 지정해줘도 해결이 안된다는 것이다. 예를 들어 &lt;code&gt;-Wl,-rpath=$HOME/.pyenv/versions/3.11.2/lib&lt;/code&gt;을 지정해주면 다음처럼 들어가게 된다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; RUNPATH /data/data/com.termux/files/usr/lib:/data/data/com.termux/files/home/.pyenv/versions/3.11.2/lib
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;대부분의 PATH들이 그렇듯이 RUNPATH도 앞에서부터 찾기 때문에 같은 버전의 python이 이미 있으면 system의 libpython을 로딩하게 되는 불상사가 생긴다.&lt;/p&gt;
&lt;p&gt;그래서 나는 다음과 같이 해결을 보았다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt;를 주어서 &lt;code&gt;RUNPATH&lt;/code&gt;를 덮어씌운다. &lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt;가 우선순위가 높다.&lt;/li&gt;
&lt;li&gt;빌드가 끝난 뒤 &lt;a href="https://github.com/NixOS/patchelf"&gt;&lt;code&gt;patchelf&lt;/code&gt;&lt;/a&gt; 명령으로 &lt;code&gt;RUNPATH&lt;/code&gt; 항목을 고친다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;좀 더 깔끔한 방법이 있을 것 같은데.. 지금 시점에서는 잘 모르겠다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkg in build-essential gdbm libandroid-posix-semaphore libandroid-support &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; libbz2 libcrypt libexpat libffi liblzma libsqlite ncurses ncurses-ui-libs &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; openssl readline zlib xorgproto patchelf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CONFIGURE_OPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--with-system-ffi --with-system-expat --with-ensurepip&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CONFIGURE_OPTS&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; --enable-loadable-sqlite-extensions&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CONFIGURE_OPTS&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; ac_cv_func_shm_open=yes&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CONFIGURE_OPTS&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; ac_cv_func_shm_unlink=yes&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; CONFIGURE_OPTS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget -O - &lt;span class="s2"&gt;&amp;#34;https://api.github.com/repos/termux/termux-packages/contents/packages/python?ref=4f87b638eb93d17b89c499c3ac7d4034d5e1ac5f&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jq &lt;span class="s1"&gt;&amp;#39;.[]|select(.name|endswith(&amp;#34;.patch&amp;#34;))|.download_url&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; xargs wget -O - -q &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sed &lt;span class="s2"&gt;&amp;#34;s%@TERMUX_PREFIX@%&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;%g; s%^+++ [^/ ]*%+++ .%&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;CFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--target=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;gcc -dumpmachine &lt;span class="p"&gt;|&lt;/span&gt; sed &lt;span class="s2"&gt;&amp;#34;s/-android[0-9]\\+/-android&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;getprop ro.build.version.sdk&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; -I&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;/include&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;LDFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-L&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;/lib&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;LN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ln -s&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;LIBCRYPT_LIBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-lcrypt&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;LD_LIBRARY_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.pyenv/versions/3.11.2/lib&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pyenv install -p -v 3.11.2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;patchelf --set-rpath &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.pyenv/versions/3.11.2/lib:&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;/lib &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.pyenv/versions/3.11.2/bin/python3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;이제 진짜 끝! python을 termux에서 직접 빌드해서 사용할 수 있게 해 보았다.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Termux는 여러가지 제약사항으로 인해 일반적인 Linux와 많이 다른 형태를 가지고 있는 환경이다. 이번 프로젝트를 통해서, 대표적인 오픈소스 프로젝트인 python을 Termux 환경 하에서 빌드해보면서 디렉토리 구조, &lt;code&gt;configure&lt;/code&gt; 스크립트의 동작 방식, 안드로이드의 몇 가지 제약들, 나아가서 Linux ELF loader까지 많은 요소들을 살펴볼 수 있었다.&lt;/p&gt;</description></item><item><title>Go와 Raspberry Pi로 온습도계 만들기</title><link>https://gwangyi.github.io/posts/go-rpi-thermometer/</link><pubDate>Sun, 09 May 2021 22:00:00 +0200</pubDate><guid>https://gwangyi.github.io/posts/go-rpi-thermometer/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;처음 지금 살고 있는 집에 들어왔을 때는 환기에 대한 상당한 오개념을 가지고 있었다. 한국에서 살 때는
공기가 답답할 때나 한번씩 환기하면 충분했었다. 추운 겨울에는 난방 효율을 위해서 웬만하면 환기 없이
겨울을 보내곤 했었다. 하지만 그건 오산이었다.&lt;/p&gt;
&lt;p&gt;독일의 창문은 &lt;a href="https://en.wikipedia.org/wiki/Window#Tilt_and_turn"&gt;틸트 앤 턴&lt;/a&gt;이라는 방식의 창호를 많이 쓴다. 우리나라에서는 미닫이
창문이 흔하기 때문에 보기 쉽지는 않은데, 회사 사무실이나 도서관 같은 중앙 공조 시스템으로 환기를
하는 건물에서는 틸트 방식으로 여는 창문을 종종 볼 수 있다.&lt;/p&gt;
&lt;figure&gt;&lt;a href="https://commons.wikimedia.org/wiki/File:Bleekman_zimmer.jpg"&gt;&lt;img src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/84/Bleekman_zimmer.jpg/512px-Bleekman_zimmer.jpg"
alt="Bleekman zimmer"&gt;&lt;/a&gt;&lt;figcaption&gt;
&lt;p&gt;Bleekman, &lt;a href="https://creativecommons.org/licenses/by/3.0"&gt;CC BY 3.0&lt;/a&gt;, via Wikimedia Commons&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;위 사진은 위키미디어에서 검색한 어떤 독일 사람의 방 사진인데, 창문이 세 조각으로 되어 있고 중간
창문이 틸트 형식으로 열려 있는 것을 볼 수 있다. 손잡이가 위쪽 방향으로 돌려져 있는 것을 볼 수
있는데, 이걸 절반만 돌리면 여닫이 창문으로 바뀌는 마술같은 창문이 &lt;a href="https://en.wikipedia.org/wiki/Window#Tilt_and_turn"&gt;틸트 앤 턴&lt;/a&gt; 방식
창문이 되겠다.&lt;/p&gt;
&lt;p&gt;월세 계약서에 환기를 자주 하는 것이 세입자의 의무라고 되어 있어서, 저 창문을 틸트 형태로만 하루에
한번 열어서 환기를 시켰다. 이 정도만 해도 한국에서 살던 때에 비하면 매우 자주 환기를 한 셈이라 환기
때문에 어떤 문제를 겪을 거라곤 상상도 못 했었다.&lt;/p&gt;
&lt;p&gt;그러나 며칠이 지나자, 창문 위쪽에 거뭇한 무언가가 나타나기 시작했다. 바로, 곰팡이였다. 급하게 곰팡이
제거제를 사다가 뿌리고 닦고 해도 일주일쯤 지나면 다시 나타나서 우리를 괴롭혔다.&lt;/p&gt;
&lt;p&gt;또 하나의 문제는, 자꾸 창문 틀 아래쪽에 물이 고이는 문제였다. 낮에는 생기지 않는데 자고 일어나면
창문마다 물이 고여있어 매일 닦아줘야했다. 많이 고이면 마루바닥에까지 흐르는 바람에 자국까지 남고&amp;hellip;&lt;/p&gt;
&lt;p&gt;나중에 알게 된 사실인데, 그렇게 열어가지고는 제대로 환기가 되질 않았던 것이 문제였다. 벽지도 없이
페인트로 마감이 되어 있는지라 집의 습기가 제대로 제거가 되지 않았고, 추운 밤이 되면 습하고 더운 집
안 공기가 차가운 창문과 만나 응결이 됐던 거였다. 빨래도 건조기도 없고 날아갈까봐 발코니에서 말리지도
못해서 집 안에서 말리다보니 집 안 습도가 스페이스엑스마냥 저 하늘 높이 치솟는건 정해진 수순이었다.
그래서 결국은 창문을 완전히 열어서 아침 저녁으로 10분씩 환기시키기 시작했는데, 그제서야 습기가 좀
잡혔다.&lt;/p&gt;
&lt;p&gt;그래서 내린 결론은, 집에 온습도계가 좀 있어야겠다는 것이었다. 기왕 하는거 온습도 센서를 사다가 REST
API 비슷하게 만들어서 웹 인터페이스로 볼 수 있게 만들고, 수집해서 모니터링용 그래프도 만들고, Alert
도 만들면 뭔가 SRE가 사는 집 같은 느낌이 들지 않을까?&lt;/p&gt;
&lt;h2 id="goal"&gt;Goal&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;온습도를 확인할 수 있는 REST API 비슷한 것을 제공하는 HTTP 서버를 만들자.&lt;/li&gt;
&lt;li&gt;만드는 김에 &lt;a href="https://golang.org/"&gt;Golang&lt;/a&gt;으로 작성해보자.&lt;/li&gt;
&lt;li&gt;만드는 데 돈이 너무 비싸게 들면 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="non-goal"&gt;Non-goal&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;이번 시간에는 API 서버만을 만들어보도록 하자. UI, 수집, Alert은 다음 시간에 만들도록 하자.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://raspberrypi.org/"&gt;Raspberry Pi&lt;/a&gt;는 오픈소스 싱글 보드 컴퓨터이다. 원래 홈 서버용으로 쓰려고 사놓고 &lt;a href="https://kodi.tv/"&gt;Kodi&lt;/a&gt;
를 통해서 아이들 넷플릭스 머신으로 쓰고 있는 중이긴 하지만, 라즈베리파이는 여러 개의 GPIO 포트를
가지고 있어서 저수준 제어도 어느 정도 가능하다.&lt;/p&gt;
&lt;figure&gt;&lt;a href="https://pinout.xyz"&gt;&lt;img src="https://raw.githubusercontent.com/Gadgetoid/Pinout.xyz/master/resources/raspberry-pi-pinout.png"
alt="Raspberry Pi Pinout"&gt;&lt;/a&gt;&lt;figcaption&gt;
&lt;p&gt;Raspberry Pi Pinout from &lt;a href="https://pinout.xyz"&gt;pinout.xyz&lt;/a&gt;&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href="https://components101.com/sensors/dht11-temperature-sensor"&gt;DHTxx&lt;/a&gt; 센서는 온도와 습도를 측정한 뒤 디지털 신호로 내보내주는 디지털 온습도 센서이다. 센서
자체에는 단자가 4개 나와있지만 그 중 하나는 안 쓰는 단자고, Vcc와 GND 단자를 제외하면 하나의 단자로
측정한 온습도 정보를 전달한다. 내부적으로는 &lt;a href="https://ko.wikipedia.org/wiki/%EC%84%9C%EB%AF%B8%EC%8A%A4%ED%84%B0"&gt;Thermistor&lt;/a&gt;에서 나온 아날로그 신호를 DAC을
통해 디지털로 변환해서 내보내준다고 한다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://golang.org/"&gt;Golang&lt;/a&gt;은 GC언어이기 때문에 non-deterministic한 GC delay가 발생할 수 있다. 이는 gpio의 신호
폭을 재거나 하는 시간에 예민한 동작에 방해가 될 가능성이 있다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.airspayce.com/mikem/bcm2835/"&gt;bcm2835&lt;/a&gt;는 &lt;a href="https://raspberrypi.org/"&gt;Raspberry Pi&lt;/a&gt;에서 사용하는 SoC 이름이지만 이를 다루기 쉽게 싸 놓은
라이브러리의 이름이기도 하다. 이 라이브러리는 C 언어로 작성되어 있으며 &lt;a href="https://raspberrypi.org/"&gt;Raspberry Pi&lt;/a&gt; 에서 무리
없이 잘 작동한다. 다만 주의해야할 점이 있는데, 이 라이브러리는 특정 조건이 만족되면 &lt;code&gt;/dev/mem&lt;/code&gt;
장치를 통해 직접 물리 메모리에 접근하려고 시도한다. 루트 권한으로(EUID == 0) 실행되고 있거나
&lt;code&gt;CAP_SYS_RAWIO&lt;/code&gt; kernel capability가 부여되어 있을 때가 그 조건인데, 이 조건을 만족하지 못하면 대신
&lt;code&gt;/dev/gpiomem&lt;/code&gt; 장치에 접근을 시도하며 이 때는 GPIO 기능만을 사용할 수 있다. 주의해야 할 부분은
해당 조건을 만족하면서도 &lt;code&gt;/dev/mem&lt;/code&gt;에 접근할 권한이 없을 때이다. 예를 들면 fakeroot 등으로 euid를
속였거나 Docker로 실행할 때가 문제가 된다. 우리는 GPIO만 관심이 있기 때문에 간단히 uid를 0이 아닌
값을 사용하는 것으로 얼마든지 피할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.json.org/"&gt;JSON&lt;/a&gt;은 비정형의 키-값 구조를 저장하기에 적절한 표현 방법으로, 일반 텍스트 형태를 하고 있어
사람 눈으로 확인하기 편리하고, 웹으로 표시할 때 JS 엔진이 무리 없이 간단하게 변환 가능하기 때문에
널리 쓰이는 표현방법이다. 사용시 주의할 점은 JS와는 달리 키값이 항상 쌍따옴표(&lt;code&gt;&amp;quot;&lt;/code&gt;)로 감싸져 있어야
하고 맨 마지막 값 뒤에 콤마(&lt;code&gt;,&lt;/code&gt;)가 있으면 안된다는 점과 주석이 지원되지 않는다는 점이다. 가끔 주석도
지원하는 해석기가 있지만 표준은 아니다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://golang.org/cmd/cgo/"&gt;cgo&lt;/a&gt; 패키지는 일종의 &lt;a href="https://en.wikipedia.org/wiki/Foreign_function_interface"&gt;FFI&lt;/a&gt;로, &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt; 코드 내에서 C 코드를 호출할 수 있게 해주는
기능이다. 사용 방법이 무척이나 간단하기 때문에, &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt;을 쓰면서 다른 언어로 작성된
라이브러리와 결합하는 것을 두려워하지 않게 해 준다. 간단한 예시를 들면 다음과 같다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// #include &amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// static void greeting(const char * name) {&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// printf(&amp;#34;Hello, %s!\n&amp;#34;, name);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// }&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;C&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Hello, world!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;cgo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Result:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Hello, world!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Hello, cgo!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;주의할 점은, GC managed runtime인 Go 코드와 unmanaged인 C 코드가 만나는 지점에서 메모리 관리가 잘
처리되지 않으면 다양한 문제가 생길 수 있다는 점이다. &lt;a href="https://golang.org/cmd/cgo/"&gt;cgo&lt;/a&gt; 페이지를 참조하면 좀 더 자세한
설명을 볼 수 있다.&lt;/p&gt;
&lt;h2 id="design"&gt;Design&lt;/h2&gt;
&lt;pre class="mermaid"&gt;
sequenceDiagram
participant User
participant API Server
participant Sensor
API Server-&gt;&gt;API Server: Initiate GPIO
User-&gt;&gt;API Server: GET /
API Server-&gt;&gt;Sensor: Initiate sensor
alt is succeded
Sensor-&gt;&gt;API Server: Send humidity/temperature
API Server-&gt;&gt;User: Send humidity/temperature
else is failed
Sensor-&gt;&gt;API Server: Send malformed data
API Server-&gt;&gt;User: Send Error
end
&lt;/pre&gt;
&lt;p&gt;구조는 간단하다. 사용자가 HTTP 요청을 보내면 API server는 센서를 깨우고, 센서로부터 받은 정보를
사용자에게 전달한다. 간단하고 효율적인 구현을 위해 &lt;a href="https://www.json.org/"&gt;JSON&lt;/a&gt; 형태로 결과를 인코딩해서 보낸다.&lt;/p&gt;
&lt;p&gt;성공한 경우에는 다음 형태로 결과를 출력한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;humidity&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;40.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;temperature&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;20.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;실패한 경우에는 다음처럼 에러를 인코딩해 보낸다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;err&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Something went wrong&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="source"&gt;Source&lt;/h3&gt;
&lt;p&gt;tl;dr을 좋아하는 사람을 위해 소스코드를 깃헙에 올려 두었다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/gwangyi/gondogye"&gt;https://github.com/gwangyi/gondogye&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이 문서에서 다룬 내용에 보태서 Dockerfile이 추가되어 있다. 다음 포스팅에서는 이걸 Docker container
로 만드는 것을 다룰 예정이다.&lt;/p&gt;
&lt;h3 id="dht1122"&gt;DHT11/22&lt;/h3&gt;
&lt;p&gt;나는 &lt;a href="https://www.amazon.de/gp/product/B07V61GKVQ/"&gt;DHT11&lt;/a&gt; 센서를 독일 아마존에서 구입했다. 사실 잘 모르고 인터넷에서 라즈베리 파이로 온도
측정하는 예제를 찾다가 아무거나 얻어걸린 것을 산 건데, DHTxx센서는 DHT11과 DHT22 두 종류가 있었다.&lt;/p&gt;
&lt;p&gt;DHT22가 DHT11보다 좀 더 정밀하고 좀 더 넓은 온·습도 영역을 측정할 수 있지만, DHT11이 좀 더 빠르게
측정할 수 있고 좀 더 촘촘하게 측정하는 것이 가능하다고 한다.&lt;/p&gt;
&lt;p&gt;둘 다 생긴 것도 비슷하고 사용하는 프로토콜도 같다. 아무거나 사면 되지만 신경쓸 부분이 하나 있다.
원래 DHTxx 센서는 단자가 4개 있는데, 그 중 3개만 사용하며 GPIO에 연결할 때는 풀업 저항을 달아야
한다. 그게 귀찮으면 내가 산 것처럼 보드에 붙어 있는 형태의 센서를 사면 된다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://components101.com/asset/sites/default/files/component_datasheet/DHT11-Temperature-Sensor.pdf"&gt;데이터 시트&lt;/a&gt;에 나와 있는 타이밍 다이어그램을 살펴보면,&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://components101.com/asset/sites/default/files/inline-images/DHT11%E2%80%93Temperature-Sensor-Working-waveform.png"&gt;
&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;제일 먼저 HIGH 상태를 유지해준 후 18ms보다 긴 시간동안 LOW를 쓰고 PULL된 상태로 기다린다.&lt;/li&gt;
&lt;li&gt;신호가 LOW로 떨어지면 80ns만큼 유지된 뒤 다시 HIGH에서 80ns를 유지하고 나서 데이터를 전송하기 시
작한다.&lt;/li&gt;
&lt;li&gt;LOW에서 50ns만큼 유지한 뒤 HIGH가 되는데, 0일 때와 1일 때의 펄스 폭이 다르다. 0은 26-28ns, 1은
70ns만큼 유지한다.&lt;/li&gt;
&lt;li&gt;총 40bit가 전송되며, 그 중 마지막 8bit는 checksum 역할을 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;8bit씩 나눠서 봤을 때 checksum은 다음과 같다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;data[4] = (data[0] + data[1] + data[2] + data[3]) mod 256
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="bcm2835"&gt;bcm2835&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.airspayce.com/mikem/bcm2835/"&gt;bcm2835&lt;/a&gt; 라이브러리가 git으로 호스팅되고 있었다면 &lt;a href="https://git-scm.com/book/en/v2/Git-Tools-Submodules"&gt;submodule&lt;/a&gt;으로 추가했을
테지만, 안타깝게도 그렇지가 못했다. 공식 홈페이지에서 &lt;a href="http://www.airspayce.com/mikem/bcm2835/bcm2835-1.68.tar.gz"&gt;최신버전 소스코드&lt;/a&gt;를 먼저
다운받았다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.airspayce.com/mikem/bcm2835/"&gt;bcm2835&lt;/a&gt;는 사실 &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt;으로 작성된 것이 아니라 C로 작성되어있다. 하지만 훌륭한 C FFI
인 &lt;a href="https://golang.org/cmd/cgo/"&gt;cgo&lt;/a&gt;를 사용하면 연결하는게 많이 어렵지 않다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.airspayce.com/mikem/bcm2835/"&gt;bcm2835&lt;/a&gt;에서 우리가 사용하게 될 함수는 그렇게 많지 않다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.airspayce.com/mikem/bcm2835/group__init.html#ga9351fa3ec8eeff4e9d998d3d5d912a4f"&gt;bcm2835_init()&lt;/a&gt;: bcm2835 안에서 사용하는 자료구조를 초기화한다.&lt;/li&gt;
&lt;li&gt;[bcm2835_gpio_fsel()][bcm2835_gpio_fsel]: 지정한 GPIO 핀의 입·출력 방향을 지정한다.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.airspayce.com/mikem/bcm2835/group__gpio.html#ga22f9b05d8edda3ef57cd58728e9c3baa"&gt;bcm2835_gpio_write()&lt;/a&gt;: 지정한 GPIO 핀의 출력값을 바꾼다.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.airspayce.com/mikem/bcm2835/group__gpio.html#ga693becf47034d3b2d8ed0ac4e74de173"&gt;bcm2835_gpio_lev()&lt;/a&gt;: 지정한 GPIO 핀에 입력되는 값을 구한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;fen, ren 등의 에지 디텍터 함수들도 있지만, 이건 사용하니까 라즈베리파이가 죽었다&amp;hellip; 죽는 이유는
정확히는 모르겠지만 아마도 에지 디텍터 관련 레지스터를 어떤 커널 드라이버에서 쓰고 있는데 마음대로
조작했다가 중간에 인터럽트가 걸리면서 뭔가가 망쳐지는 것 같다.&lt;/p&gt;
&lt;h4 id="initiate-sensor"&gt;Initiate Sensor&lt;/h4&gt;
&lt;pre class="mermaid"&gt;
graph
1[Set pin mode as output]
1 --&gt; 2[Set pin to HIGH]
2 --&gt; 3[Wait 500ms]
3 --&gt; 4[Set pin to LOW]
4 --&gt; 5[Wait 20ms]
5 --&gt; 6[Set pin mode as input]
6 --&gt; 7[Wait until LOW]
&lt;/pre&gt;
&lt;p&gt;위에서 다이어그램을 보고 정리했던 순서도의 2번까지에 해당하는 내용이다. 처음에 500ms를 기다리는
이유는 다음에 보낼 신호가 1번에 해당하는 신호임을 확실하게 만들기 위해서이다.&lt;/p&gt;
&lt;h4 id="read-pulse-width"&gt;Read Pulse Width&lt;/h4&gt;
&lt;pre class="mermaid"&gt;
graph
1[Keep timestamp]
1 --&gt; 2[Wait for the change of the level of GPIO pin]
2 --&gt; 3[Calculate delta on the timestamp]
3 --&gt; 4[Update timestamp]
&lt;/pre&gt;
&lt;p&gt;시간을 잴 때는 &lt;a href="https://man7.org/linux/man-pages/man2/gettimeofday.2.html"&gt;gettimeofday()&lt;/a&gt; 함수를 쓰면 us 단위로 현재 시각을 얻어올 수 있다.
2단계에서 무한정 기다리게 되는 일이 생길 수 있으므로 일정 숫자만큼 세고 나면 탈출할 수 있도록
조치하는 것이 필요하다. 이걸 고정된 값으로 하면 코어 주파수에 따라 시간이 들쭉날쭉할 수 있어서, 일단
충분히 큰 수만큼 세어 보면서 시간이 얼마나 걸리는지 잰 다음 원하는 시간(여기서는 100us) 비율로 나눠
그 시간만큼 걸릴만한 숫자를 계산해서 이를 활용한다.&lt;/p&gt;
&lt;h4 id="read-response"&gt;Read Response&lt;/h4&gt;
&lt;p&gt;위에서 작성한 펄스 폭을 재는 함수를 활용하면 간단하게 작성할 수 있다. 읽어야 하는 펄스의 개수는
80개이다. 한 비트에 LOW/HIGH 두 번의 펄스가 있고, 8비트씩 5개 정수가 나오기 때문에 80개가 된다.&lt;/p&gt;
&lt;p&gt;GPIO를 완전히 컨트롤할 수 있는 커널이나 펌웨어 레벨에서 접근하는 것이 아니기 때문에, 약간 밀리거나
시간 축정에 오차가 생기거나 하는 경우를 감안해서 처리할 필요가 있다. 이 프로젝트의 경우에는, 먼저
나오는 LOW 신호의 폭을 따져보고 50us에서 너무 많이 벗어나는 경우(30-70us 범위를 벗어나면) 잘못된
신호로 취급하기로 했다. 그 뒤에 나오는 HIGH 신호는 26~28us의 경우 0이고 70us일 경우 1이므로 50us보다
짧은지 긴지를 기준으로 값을 인식시켰다.&lt;/p&gt;
&lt;h4 id="checksum"&gt;Checksum&lt;/h4&gt;
&lt;p&gt;마지막으로 체크섬 처리를 여기서 했는데, 사실 체크섬은 시간에 영향을 받지 않는 연산이라 대원칙에
따르면 &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt; 쪽에서 처리해도 무관하다. 하지만 C에서 처리한 이유는, 이 체크섬을 C에서 처리하지
않는 경우 &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt; 쪽으로 40비트 정수를 넘겨야 하지만 미리 처리하고 잘라내서 넘길 경우 32비트만
넘기면 되기 때문이다. 40비트를 넘기려면 최소 64비트 정수형 자료형을 써야하지만 8비트를 미리 자르면
32비트로 충분하기 때문에 조금 더 보기에 좋다.&lt;/p&gt;
&lt;h4 id="why-c"&gt;Why C?&lt;/h4&gt;
&lt;p&gt;지금까지 C언어로 작성했는데, 그 이유는 &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt;가 managed 언어이기 때문이다. 동작들이 us단위로
시간을 재면서 진행되어야 하는데, &lt;a href="https://golang.org/cmd/cgo/"&gt;cgo&lt;/a&gt; 브릿지 오버헤드나 &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt;의 GC 등이 예측하지 못한
타이밍에 갑자기 호출되어버리면 시간을 재는 작업이 엉터리가 되어버릴 위험성이 있다. 따라서 직접 GPIO
이용해 센서와 통신하는 부분은 C로 작성하여 예측할 수 없는 문제를 최소화시키도록 했다.&lt;/p&gt;
&lt;p&gt;윗 단계에서 얻어낸 32비트 정수형 자료는 DHT11, DHT22에서 서로 다른 의미를 가지고 있다. 이 것까지
모두 해석해서 반환할 수도 있지만, 이 해석하는 작업은 us 단위로 시간을 따져가며 계산할 필요가 없기
때문에 C API쪽에서 처리하지 않고 그대로 반환한다. 이 값은 &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt; 쪽에서 해석할 것이다.&lt;/p&gt;
&lt;h4 id="dhtdhth"&gt;dht/dht.h&lt;/h4&gt;
&lt;p&gt;위에서 작성한 C로 작성된 함수들의 인터페이스는 대략 다음과 같다.&lt;/p&gt;
&lt;script src="https://gist-it.appspot.com/https://github.com/gwangyi/gondogye/raw/main/dht/dht.h"&gt;&lt;/script&gt;
&lt;p&gt;나중에 이 파일은 &lt;a href="https://golang.org/cmd/cgo/"&gt;cgo&lt;/a&gt;에서 include할 예정이다. 이를 통해 자동으로 브릿지가 생성돼서
&lt;a href="https://golang.org/"&gt;Go&lt;/a&gt; 쪽에서 쉽게 호출이 가능해진다.&lt;/p&gt;
&lt;h3 id="application-server"&gt;Application Server&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://golang.org/"&gt;Go&lt;/a&gt;에는 간단한 http 서버를 제작할 수 있는 라이브러리인 &lt;a href="https://golang.org/pkg/net/http/"&gt;net/http&lt;/a&gt;가 표준
라이브러리에 포함되어 제공된다. 물론 &lt;a href="https://gin-gonic.com/"&gt;gin&lt;/a&gt;과 같이 본격적인 Web server framework가 없는 것은
아니지만, 간단하게 온습도 정보를 제공하는 정도의 API 서버는 &lt;a href="https://golang.org/pkg/net/http/"&gt;net/http&lt;/a&gt;로 충분할 것이다.&lt;/p&gt;
&lt;p&gt;API 서버는 앞에서 작성한 C API를 호출하여 32비트 정수값을 얻어온 뒤에, 실제 연결된 센서 종류에 따라
제대로 된 온습도 정보를 추출해 &lt;a href="https://www.json.org/"&gt;JSON&lt;/a&gt;으로 반환할 예정이다.&lt;/p&gt;
&lt;h4 id="cgo-binding"&gt;CGO Binding&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://golang.org/cmd/cgo/"&gt;cgo&lt;/a&gt; Binding을 작성하는 것은 어렵지 않다. &lt;code&gt;import &amp;quot;C&amp;quot;&lt;/code&gt; 만으로 충분하다. 더해서 사용할
함수들과 구조체, enum 등이 정의된 헤더파일을 주석 형태로 위에 적어주면 완벽하다. 우리의 경우
&lt;a href="https://golang.org/"&gt;Go&lt;/a&gt; 쪽에서 호출해야 하는 함수는 bcm2835 라이브러리에서 초기화 함수인 &lt;code&gt;bcm2835_init()&lt;/code&gt;,
위에서 작성한 DHT API에서 &lt;code&gt;dht_init()&lt;/code&gt;, &lt;code&gt;dht_read()&lt;/code&gt; 를 호출할 예정이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// #cgo CFLAGS: -I.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// #include &amp;lt;bcm2835.h&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// #include &amp;lt;dht.h&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;C&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;첫 줄은 &lt;a href="https://golang.org/cmd/cgo/"&gt;cgo&lt;/a&gt; 컴파일 시 C 코드를 컴파일할 컴파일러에게 전달하는 CFLAGS 옵션을 지정하고 있다.
헤더파일과 go 소스파일, C 코드 모두가 같은 디렉토리에 있기 때문에 이렇게 했다. 특이한 점은 헤더를
include했지만 소스코드는 특별히 처리하지 않았는데, 같은 디렉토리에 있는 C 파일은 자동으로 같이 빌드
해 주기 때문에 지정할 필요가 없기 때문이다. 만약 다른 디렉토리에 있는 코드를 가져오고 싶다면 별도의
빌드 시스템을 이용할 필요가 있다. 이건 의도된 설계인데, 이유가 궁금하면
&lt;a href="https://github.com/golang/go/issues/12953"&gt;관련된 github 이슈&lt;/a&gt;를 참조하기 바란다. 좀 더 자세한 내용은 &lt;a href="https://golang.org/cmd/cgo/"&gt;cgo&lt;/a&gt; 패키지 설명을
참조하면 CFLAGS부터 LDFLAGS, pkg-config 등의 환경을 셋업하는 방법에 대해 찾아볼 수 있다.&lt;/p&gt;
&lt;p&gt;맨 처음엔 자동으로 bcm2835 라이브러리를 초기화시키고 싶으므로 &lt;a href="https://golang.org/doc/effective_go#init"&gt;&lt;code&gt;init()&lt;/code&gt;&lt;/a&gt; 함수를 정의해보자.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;bcm2835_init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;bcm2835_init() has been failed&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;이제 간단한 인터페이스를 정의해보자.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cDHT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;실제 코드에는 디버깅을 위한 로그레벨을 조절하는 함수가 하나 더 있지만, 필요없는 함수이므로 언급하지
않았다.&lt;/p&gt;
&lt;p&gt;이제 이 것을 구현하는 구현체를 만들어볼텐데, 새로운 &lt;code&gt;struct&lt;/code&gt;를 만들지 않고 작성해보자.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;newDHT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;struct_dht_sensor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dht&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;struct_dht_sensor&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dht_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pin&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;dht&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dht&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;struct_dht_sensor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uint32_t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dht_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Unexpected behavior on the DHT sensor at %v&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;주의해서 볼 부분은, C에서 정의된 struct를 접근할 땐 이름의 앞에 &lt;code&gt;struct_&lt;/code&gt;가 붙어있어야 한다는 것과
C의 int형 인자값을 받을 때도 &lt;code&gt;C.int()&lt;/code&gt;로 감싸야 한다는 것 정도가 되겠다. 마찬가지로 &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt;의
정수형으로 반환하고 싶을 때도 감싸줘야한다.&lt;/p&gt;
&lt;h4 id="parse-dht11-response"&gt;Parse DHT11 Response&lt;/h4&gt;
&lt;p&gt;앞에서 정의한 인터페이스는 의도적으로 private으로 정의했다. 실제로 외부에 보여줄 인터페이스는 정확히
온도와 습도 정보를 담은 구조체를 반환하도록 하고 싶기 때문이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Humidity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;float32&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Temperature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;float32&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DHT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dht11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cdht&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cDHT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;NewDHT11&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DHT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;dht11&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cdht&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;newDHT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pin&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dht&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;dht11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cdht&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Humidity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xff&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xff&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;DHT11 센서는 첫 8비트가 습도, 세 번째 8비트가 온도를 나타낸다. 두 번째와 네 번째는 소숫점 이하
수치라고 하는데, 정확히 설명된 자료를 찾지 못했다. &lt;code&gt;Result&lt;/code&gt;를 포인터로 반환하지 않은 이유는, 32비트
실수형 자료 두 개라 용량이 많이 크지 않고 64 비트 환경에서는 심지어 한번에 전달이 가능하기 때문이다.&lt;/p&gt;
&lt;h4 id="build-http-server"&gt;Build HTTP Server&lt;/h4&gt;
&lt;p&gt;간단하게, HTTP 요청이 오면 센싱한 뒤 결과를 JSON으로 인코딩해서 돌려줄 것이다.&lt;/p&gt;
&lt;script src="https://gist-it.appspot.com/https://github.com/gwangyi/gondogye/raw/main/server/server.go"&gt;&lt;/script&gt;
&lt;p&gt;그러고 나면 이 서버를 실행시키는 간단한 main 함수를 작성하면 끝이다.&lt;/p&gt;
&lt;script src="https://gist-it.appspot.com/https://github.com/gwangyi/gondogye/raw/main/main.go"&gt;&lt;/script&gt;
&lt;p&gt;플래그를 사용해 웹서버를 바인드하는 아이피나 포트, 그리고 센서가 연결된 GPIO 포트를 지정할 수 있게
했다.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;이제 C 코드와 &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt; 코드가 적당히 섞인 간단한 API 서버를 만들 수 있게 되었다. 덤으로 bcm2835
라이브러리의 간단한 사용법을 익혔고, 간단한 gpio 활용 기능도 작성할 수 있게 되었다.&lt;/p&gt;
&lt;p&gt;사실 다 만들고 나서 도커로 싸던 중에, 이걸 러스트로 만들었으면 더 재밌었을거라는 생각이 문득 들어
현자타임이 왔었다. 그래도 기왕 만든 것이고 의미가 있을거라 생각해서 작성 과정을 글로 남겨보았는데,
생각보다 너무 길어 분량 조절을 망친 것 같다.&lt;/p&gt;</description></item><item><title>Simple Web Scrapping</title><link>https://gwangyi.github.io/posts/simple-web-scrapping/</link><pubDate>Fri, 12 Mar 2021 22:11:55 +0100</pubDate><guid>https://gwangyi.github.io/posts/simple-web-scrapping/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;인터넷이 세상을 지배한다고 해도 과언이 아닌 시대를 살아가고 있다. 옛날에는 종이로 우편함으로 보내지던 고지서들이 이메일을 거쳐 메신저로 전달되고 있고, 예약이나 질의도 직접 찾아가는 방법 외에도 인터넷을 통해 다양한 방법으로 달성할 수 있는 시대가 되었다.&lt;/p&gt;
&lt;p&gt;기술에 대한 신뢰가 별로 없는 이 독일 땅에서도, 코로나 판데믹이라는 전무후무한 재해로 인해 어쩔 수 없이 비대면 업무 처리를 위한 전산화가 조금씩 도입되고 있는 중이다.&lt;/p&gt;
&lt;p&gt;최근 인후통, 콧물, 코막힘 등의 증상이 있어서 아이들과 함께 근처의 이비인후과에 들렀다. 독일어가 미진한 관계로 예약을 잘못 잡았는지 코로나 검사만 해 주고 돌려보내길래 검사 번호가 붙어 있는 엽서 크기의 접수증 세 장을 들고 털레털레 집으로 돌아왔다. 이제 채취한 검체를 연구소로 보내서 검사한 뒤 결과를 알려주겠다고 했는데, 아날로그를 매우 사랑하는 독일이지만 다행히 이 검사 결과는 웹사이트와 스마트폰 앱을 통해 조회가 가능하므로 정식 결과서를 받기 전에 미리 확인할 수 있다고 했다.&lt;/p&gt;
&lt;p&gt;결과가 나오는데는 최대 3일 정도 걸린다고 했으니 3일 뒤에 확인해보는 방법도 있겠지만, 우리는 한민족, 빨리빨리의 민족이 아닌가. 3시간 간격으로 한번씩 홈페이지에 들어가서 결과를 확인하려니 귀찮기도 하고 재밌는 아이디어가 생각나서 웹 크롤러를 만들어보기로 했다. 귀찮고 반복적인 일은 최대한 자동화하기, 개발자의 황금률이다.&lt;/p&gt;
&lt;h2 id="goal"&gt;Goal&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;이 결과는 길어야 3일 안에는 나온다. 다르게 말하면, 이 웹 스크래퍼는 3일 안에 완성되어야 의미가 있다는 것이다. 아마 3일까지는 안 걸릴 것이기 때문에, 길어야 두어시간, 기대하기로는 몇 분 정도 대충 끄적여서 완성해야 쓸 데가 있다.&lt;/li&gt;
&lt;li&gt;테스트 결과는 간단하게 &lt;code&gt;처리중&lt;/code&gt;, &lt;code&gt;양성&lt;/code&gt;, &lt;code&gt;음성&lt;/code&gt; 셋 중 하나로 나올 것이다. 처리중 상태에서 다른 상태로 바뀌면 즉시 어떤 방법으로든 알람을 보내서 알 수 있도록 하는 것이 목표다.&lt;/li&gt;
&lt;li&gt;혹시 모르지만, 너무 많은 요청을 보내서 결과 조회 서버를 터뜨리거나 내가 차단되어서는 안될 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="non-goals"&gt;Non-goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;실시간으로 결과를 받아 볼 필요는 없다. 물론 가능하다면 좋겠지만, 30초 일찍 알았다고 해서 내가 취할 수 있는 선택지가 바뀌는 부분이 없기 때문이다.
사실 결과를 빨리 받아보고 싶었던 이유는 오늘중으로 소아과를 방문하고 싶었기 때문인데, 이 소아과는 예전에 들렀을 때 병원인 주제에 코로나 의심증상이 조금이라도 있으면 낫고 나서 오라는 이상한 소리를 하면서 돌려보냈었던 곳이기 때문에 결과가 필요했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://developers.google.com/web/tools/chrome-devtools"&gt;크롬 개발자 도구&lt;/a&gt;는 재밌는 기능을 가지고 있다. 특히 웹 스크래핑을 시도할 때 꽤나 유용할만한 기능이 몇 가지 있는데, 대부분 개발자 도구의 &lt;a href="https://developers.google.com/web/tools/chrome-devtools/network"&gt;Network&lt;/a&gt; 탭에서 발견할 수 있다. 기록된 요청에 오른쪽 버튼을 눌러 컨텍스트 메뉴를 열면 &lt;a href="https://developers.google.com/web/tools/chrome-devtools/network/reference#copy"&gt;Copy&lt;/a&gt; 메뉴가 있다. 하위 메뉴를 보면 똑같은 복사인데 엄청 많은 수의 하위 메뉴들이 있는 것을 볼 수 있다. 이 중에 Copy as cURL이 특히 좋은데, 이걸 선택하면 기록된 요청과 동일한 요청을 보내는 &lt;a href="https://curl.se/"&gt;curl&lt;/a&gt; 명령을 클립보드에 넣어준다. 그대로 다른 터미널에 붙여넣고 실행하면, 동일한 요청 헤더와 쿠키가 그대로 보내지기 때문에 간단한 자동화에 매우 큰 도움이 된다. 쿠키가 그대로 보내진다는 부분에서, 세션도 동일한 세션을 쓰기 때문에 별도의 로그인 절차를 따라할 필요가 없다는 것은 덤이다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://stedolan.github.io/jq/"&gt;jq&lt;/a&gt;라는 명령이 있다. 아마도 JSON Query의 줄임말이라고 생각하는데, 간단히 말해서 JSON을 표준 입력으로 받아 자체적인 query 문법을 통해 원하는 요소만 골라내서 보여주는 기능을 하는, 일종의 &lt;a href="https://www.gnu.org/software/sed/manual/sed.html"&gt;sed&lt;/a&gt;나 &lt;a href="https://man7.org/linux/man-pages/man1/grep.1.html"&gt;grep&lt;/a&gt; 같은 툴이다. 이런 비슷한 툴이 html이나 xml에도 있을 것이라고 생각하고 간단히 찾아봤더니 Go로 작성된 &lt;a href="https://github.com/ericchiang/pup"&gt;pup&lt;/a&gt;를 찾을 수 있었다. &lt;a href="https://github.com/ericchiang/pup"&gt;pup&lt;/a&gt;은 &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors"&gt;CSS selector&lt;/a&gt;를 이용해 html 문서를 필터링할 수 있는 프로그램이다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/rahiel/telegram-send"&gt;telegram-send&lt;/a&gt;라는 파이썬 프로그램이 있다. CLI 환경에서 동작하는 봇 클라이언트인데, 설정파일을 생성해 주면 커맨드 라인에서 간단하게 봇을 통해 메시지를 보낼 수 있다. &lt;a href="https://core.telegram.org/bots"&gt;텔레그램 봇&lt;/a&gt;은 &lt;a href="https://telegram.me/BotFather"&gt;BotFather&lt;/a&gt; 봇을 통해서 생성하고 관리할 수 있다. &lt;a href="https://core.telegram.org/bots"&gt;문서&lt;/a&gt;를 보고 생성 방법을 숙지해 두자.&lt;/p&gt;
&lt;h2 id="design"&gt;Design&lt;/h2&gt;
&lt;p&gt;흐름은 대략적으로 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;검사 결과 페이지를 요청한다.&lt;/li&gt;
&lt;li&gt;결과 페이지의 dom 구조를 분석하여 결과 부분을 찾는다.&lt;/li&gt;
&lt;li&gt;양성이든 음성이든 나오면 메시지를 보낸다.&lt;/li&gt;
&lt;li&gt;나오지 않았다면 일정 시간을 기다린 후 1부터 다시 시작한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="querying-the-result-page"&gt;Querying the result page&lt;/h3&gt;
&lt;p&gt;코로나 검사 결과를 조회해볼 수 있는 사이트는 &lt;a href="https://www.doctorbox.de/covid19.jsp"&gt;https://www.doctorbox.de/covid19.jsp&lt;/a&gt;였다. 접속해 보면, 접수번호와 생년월일을 입력해서 코로나 검사 결과를 볼 수 있는 형태로 구성되어 있다.
&lt;figure&gt;
&lt;img src="https://gwangyi.github.io/img/www.doctorbox.de_covid19.jsp.png"/&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;여기에 연월일을 입력하면 검사 결과가 나타난다.
&lt;figure&gt;
&lt;img src="https://gwangyi.github.io/img/www.doctorbox.de_covid19-result.jsp.png"/&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이제 해볼 것은, 위에서 이야기했던 &lt;a href="https://curl.se/"&gt;curl&lt;/a&gt; command를 &lt;a href="https://developers.google.com/web/tools/chrome-devtools/network/reference#copy"&gt;따 보는 것&lt;/a&gt;이다. 따 보면 다음과 비슷하게 나타날 것이다. 민감할 수 있는 정보는 &lt;code&gt;.&lt;/code&gt; 으로 치환했다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl &lt;span class="s1"&gt;&amp;#39;https://www.doctorbox.de/covid19-result.jsp&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Connection: keep-alive&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Cache-Control: max-age=0&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;sec-ch-ua: &amp;#34;Google Chrome&amp;#34;;v=&amp;#34;89&amp;#34;, &amp;#34;Chromium&amp;#34;;v=&amp;#34;89&amp;#34;, &amp;#34;;Not A Brand&amp;#34;;v=&amp;#34;99&amp;#34;&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;sec-ch-ua-mobile: ?0&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Upgrade-Insecure-Requests: 1&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Origin: https://www.doctorbox.de&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Content-Type: application/x-www-form-urlencoded&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Sec-Fetch-Site: same-origin&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Sec-Fetch-Mode: navigate&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Sec-Fetch-User: ?1&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Sec-Fetch-Dest: document&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Referer: https://www.doctorbox.de/covid19.jsp&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,de;q=0.6&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Cookie: JSESSIONID=............................; ........................=.............; site-lang=de&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --data-raw &lt;span class="s1"&gt;&amp;#39;covid19ID=..........&amp;amp;day=..&amp;amp;month=..&amp;amp;year=....&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --compressed
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;위 명령의 결과는 다음과 비슷할 것이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;span class="lnt"&gt;47
&lt;/span&gt;&lt;span class="lnt"&gt;48
&lt;/span&gt;&lt;span class="lnt"&gt;49
&lt;/span&gt;&lt;span class="lnt"&gt;50
&lt;/span&gt;&lt;span class="lnt"&gt;51
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;no-js&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;de&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;x-ua-compatible&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ie=edge&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;&amp;lt;!-- Start Covid --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;covid19-2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;width-xl grid-padding-t-x&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;grid-x&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;grid-x grid-padding-t-m&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;large-8 large-offset-2 medium-10 medium-offset-1 small-12 text-center normal-title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h4&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Auswertung Ihres COVID-19 Testabstriches&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h4&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h5&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Bitte beachten Sie, dass der Online Abruf nur zur Erstinformation dient. Ihren schriftlichen ärztlichen Befund für eventuell notwendige Nachweise erhalten Sie über die DoctorBox App.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h5&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;large-12 medium-12 small-12 text-center negative-title mx-0 my-3 mx-md-5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;text-uppercase&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Ihr Ergebnis ist &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;IN BEARBEITUNG&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;large-6 large-offset-3 medium-10 medium-offset-1 small-12 text-center covid-notification-body&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;&amp;lt;!--
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt; &amp;lt;h4&amp;gt;Sie sind sich nicht sicher, ob Sie Covid19 schon hatten?&amp;lt;/h4&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt; &amp;lt;h5&amp;gt;Finden Sie es mit einem Covid19 &amp;lt;strong&amp;gt;Antikörpertest&amp;lt;/strong&amp;gt; heraus!&amp;lt;/h5&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt; &amp;lt;div class=&amp;#34;mb-5&amp;#34;&amp;gt;&amp;lt;a href=&amp;#34;aerzte.jsp&amp;#34; class=&amp;#34;button&amp;#34; data-localize=&amp;#34;mob-menu-button-1&amp;#34;&amp;gt;Jetzt bestellen&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt; --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;pt-3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h4&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hilfe zu Ihrem Testabstrich&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h4&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h5&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;mx-3 mx-md-0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Sollte ihr Testergebnis nach mehr als &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;3 Werktagen nach Testabstrich&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; nicht vorliegen:&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h5&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ol&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;text-left mx-2 mx-md-5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Schreiben Sie eine E-Mail an covid19@doctorbox.eu&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Inkludieren Sie bitte die Barcodenummer, idealerweise ein Bild des Barcodes sowie Ihr Geburtsdatum&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Wir melden uns schnellstmöglich bei Ihnen zurück&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ol&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;&amp;lt;!-- Ende Covid --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;요즘 유행과는 다르게 API 호출후 스크립트로 렌더링 하는 방식이 아니라 문서 자체에 결과가 찍혀 나오는 타입으로 보인다. 덕분에 HTML 구문 분석이 필요하게 되었다.&lt;/p&gt;
&lt;h3 id="extract-current-status"&gt;Extract current status&lt;/h3&gt;
&lt;p&gt;대강 살펴봤을 때 &lt;code&gt;#covid19-2 h1 span&lt;/code&gt; 을 살펴보면 현재 상황을 알 수 있을 것만 같다. 결과가 이미 나온 접수번호로 테스트 했을 때는, 저 자리에 &lt;code&gt;NEGATIV&lt;/code&gt;가 들어 있는 것을 확인했다. 이제 위에서 찾았던 &lt;a href="https://github.com/ericchiang/pup"&gt;pup&lt;/a&gt;을 이용해서 파싱해보자.&lt;/p&gt;
&lt;p&gt;이 경우 &lt;code&gt;pup &amp;quot;#covid19-2 h1 span text{}&amp;quot;&lt;/code&gt; 명령으로 파이프라이닝을 하면 될 것 같다. 때마침 &lt;a href="https://curl.se/"&gt;curl&lt;/a&gt;도 요청한 내용을 기본적으로는 stdout으로 내 보내 주니 궁합이 잘 맞다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl ... &lt;span class="p"&gt;|&lt;/span&gt; pup &lt;span class="s2"&gt;&amp;#34;#covid19-2 h1 span text{}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;IN BEARBEITUNG
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="check-the-result-and-send-notification"&gt;Check the result and send notification&lt;/h3&gt;
&lt;p&gt;위에서 주어진 접수 번호에 대해 테스트 결과가 어떤지를 얻어올 수 있는 명령어를 만들었다. 이걸 변수에 저장한 뒤 비교해보고 판단해서 메시지를 보내는 것을 어떻게 할지 알아보자.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.gnu.org/software/bash/"&gt;Bash&lt;/a&gt; script에는 [Command substitution][bash-cmd-sub]이라는 기능이 있다. 쉽게 표현하자면 어떤 명령을 실행한 뒤 그 결과값으로 치환되는 식을 사용할 수 있다. 즉, 어떤 명령의 실행 결과를 다른 명령의 인자로 전달하거나 환경변수에 저장할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.gnu.org/software/bash/"&gt;Bash&lt;/a&gt; script로 의미있는 작업을 하려면 아무래도 어떤 식을 평가하거나, 평가한 값을 바탕으로 분기하는 기능이 반드시 있어야 할 것이다. 이 역할을 하는 명령어가 built-in 명령어인 &lt;a href="https://man7.org/linux/man-pages/man1/test.1.html"&gt;test&lt;/a&gt; 와 &lt;a href="https://man7.org/linux/man-pages/man1/bash.1.html"&gt;[[&lt;/a&gt;이다. &lt;a href="https://man7.org/linux/man-pages/man1/test.1.html"&gt;test&lt;/a&gt;는 &lt;a href="https://en.wikipedia.org/wiki/Bourne_shell"&gt;Bourne shell(sh)&lt;/a&gt; 계열 셸이 모두 지원하는 명령어지만 기능이 제한적이거나 복잡하고, &lt;a href="https://man7.org/linux/man-pages/man1/bash.1.html"&gt;[[&lt;/a&gt;는 &lt;a href="https://www.gnu.org/software/bash/"&gt;bash&lt;/a&gt; 기능이라 호환이 안되는 경우도 있지만 좀 더 기능이 편리하다. 여기에 &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;이나 &lt;code&gt;||&lt;/code&gt;으로 명령을 연결하면 간단한 &lt;code&gt;if-else&lt;/code&gt; 느낌으로 쓸 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/rahiel/telegram-send"&gt;telegram-send&lt;/a&gt;는 미리 설정을 해 놓은 다음 실행하면 주어진 인자를 미리 설정한 상대방에게 보내주는 간단한 사용법을 지닌 명령어이다. 같이 조합해보면 다음과 같다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;RESULT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;curl ... &lt;span class="p"&gt;|&lt;/span&gt; pup &lt;span class="s2"&gt;&amp;#34;#covid19-2 h1 span text{}&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$RESULT&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; !&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;IN BEARBEITUNG&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; telegram-send &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$RESULT&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;||&lt;/code&gt; 은 다른 언어에서도 많이 볼 수 있는 논리합 연산자인데, &lt;a href="https://en.wikipedia.org/wiki/Short-circuit_evaluation"&gt;short-circuit evaluation&lt;/a&gt;을 지원한다. 즉, 앞 부분이 성공이면 뒷 부분은 실행하지 않고 성공으로 취급하고, 앞 부분이 실패라면 뒷 부분을 실행한 뒤 뒷 부분의 성공/실패 여부로 결과를 결정한다. 말인즉슨, &lt;code&gt;A || B&lt;/code&gt; 는 &lt;code&gt;if not A then do B&lt;/code&gt;의 의미를 가진다는 것이다. 마찬가지로 &lt;code&gt;A &amp;amp;&amp;amp; B&lt;/code&gt; 는 &lt;code&gt;if A then do B&lt;/code&gt;가 될 것이다.&lt;/p&gt;
&lt;p&gt;이 사실을 이용해서 해석해 보자면, &lt;code&gt;$RESULT&lt;/code&gt;가 &lt;code&gt;IN BEARBEITUNG&lt;/code&gt;이 아니라면 &lt;code&gt;telegram-send&lt;/code&gt;를 수행하고, &lt;code&gt;telegram-send&lt;/code&gt;가 성공하면 종료하라는 뜻이 되겠다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/rahiel/telegram-send"&gt;telegram-send&lt;/a&gt;로 메시지를 보내려면 미리 설정을 해야 되는데, 다음처럼 하면 간단하게 설정이 가능하다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ telegram-send -c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Talk with the BotFather on Telegram &lt;span class="o"&gt;(&lt;/span&gt;https://telegram.me/BotFather&lt;span class="o"&gt;)&lt;/span&gt;, create a bot and insert the token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;❯ 0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ012345678
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Connected with telegram_bot.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Please add telegram_bot on Telegram &lt;span class="o"&gt;(&lt;/span&gt;https://telegram.me/telegram_bot&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;and send it the password: &lt;span class="c1"&gt;####&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Congratulations gwangyi!
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;telegram-send is now ready &lt;span class="k"&gt;for&lt;/span&gt; use!
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;처음 묻는 부분에서는 &lt;a href="https://telegram.me/BotFather"&gt;BotFather&lt;/a&gt;를 통해 새로 생성한 봇의 토큰을 넣으면 된다. 토큰은 탈취당한 경우 다른 사람이 만들어 둔 봇으로 보내지는 메시지를 탈취하거나 봇으로 위장해서 메시지를 보낼 수 있기 때문에 안전하게 보관해야 한다.&lt;/p&gt;
&lt;p&gt;입력하게 되면 앞에서 생성한 봇의 정보를 간단하게 보여준 뒤 숫자로 된 비밀번호 5자리를 보여준다. 지정한 봇을 텔레그램에서 친구추가 한 뒤 비밀번호 5자리를 입력하면 연결이 완료된다.&lt;/p&gt;
&lt;p&gt;다음 명령어를 통해 제대로 설정되었는지 확인할 수 있다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ telegram-send &lt;span class="s2"&gt;&amp;#34;Hello&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;명령을 수행한 뒤 텔레그램에서 지정된 봇으로부터 메시지를 수신한 것을 확인하면 다음으로 넘어갈 수 있다.&lt;/p&gt;
&lt;h3 id="make-a-loop"&gt;Make a loop&lt;/h3&gt;
&lt;p&gt;이제 할 일은, 결과가 나올 때까지 반복하는 일이다. 복잡하게 구성하면 머리만 아파지니 간단하게 무한루프를 만든 다음 조건이 맞으면 나오도록 만들자.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt; true&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;RESULT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;curl ... &lt;span class="p"&gt;|&lt;/span&gt; pup &lt;span class="s2"&gt;&amp;#34;#covid19-2 h1 span text{}&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$RESULT&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;IN BEARBEITUNG&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; telegram-send &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$RESULT&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;while&lt;/code&gt; 명령은 C언어에 있는 &lt;code&gt;while&lt;/code&gt;과 비슷하게 특정 조건을 만족하는 동안 루프를 돌게 하는데 쓸 수 있다. 다만 두 가지 부분에서 C 언어와 다른데,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;중괄호 대신 &lt;code&gt;do ... done&lt;/code&gt; 으로 블록을 감싼다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;while&lt;/code&gt; 뒤에 오는 명령을 실행시킨 뒤 종료 코드가 &lt;code&gt;0&lt;/code&gt;일 때 내용을 수행하고 &lt;code&gt;0&lt;/code&gt;이 아닐 때 밖으로 나간다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;1은 단순히 문법적인 이유이고, 2는 아마도 일반적인 경우 에러 없이 종료된 경우 종료 코드가 &lt;code&gt;0&lt;/code&gt;인 것이 셸에서 통용되는 원칙이므로 저렇게 한 것 같다. 재미있는 점은 &lt;code&gt;true&lt;/code&gt;는 일반적인 배포판에서 &lt;code&gt;/bin/true&lt;/code&gt; 에 있는 독립적인 실행파일로, 실행하면 종료코드 &lt;code&gt;0&lt;/code&gt;으로 종료하는 기능만 있는 바이너리라는 것이다. 그러면 &lt;code&gt;while&lt;/code&gt; 조건이 항상 만족될테니, 무한루프를 간단히 작성할 수 있다.&lt;/p&gt;
&lt;h3 id="full-script-without-private-info"&gt;Full script without private info&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt; true&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;RESULT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;curl &lt;span class="s1"&gt;&amp;#39;https://www.doctorbox.de/covid19-result.jsp&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Connection: keep-alive&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Cache-Control: max-age=0&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;sec-ch-ua: &amp;#34;Google Chrome&amp;#34;;v=&amp;#34;89&amp;#34;, &amp;#34;Chromium&amp;#34;;v=&amp;#34;89&amp;#34;, &amp;#34;;Not A Brand&amp;#34;;v=&amp;#34;99&amp;#34;&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;sec-ch-ua-mobile: ?0&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Upgrade-Insecure-Requests: 1&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Origin: https://www.doctorbox.de&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Content-Type: application/x-www-form-urlencoded&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Sec-Fetch-Site: same-origin&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Sec-Fetch-Mode: navigate&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Sec-Fetch-User: ?1&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Sec-Fetch-Dest: document&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Referer: https://www.doctorbox.de/covid19.jsp&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,de;q=0.6&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Cookie: JSESSIONID=............................; ........................=.............; site-lang=de&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --data-raw &lt;span class="s1"&gt;&amp;#39;covid19ID=..........&amp;amp;day=..&amp;amp;month=..&amp;amp;year=....&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --compressed 2&amp;gt; /dev/null &lt;span class="p"&gt;|&lt;/span&gt; pup &lt;span class="s1"&gt;&amp;#39;#covid19-2 h1 span text{}&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; -n &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$RESULT&lt;/span&gt;&lt;span class="s2"&gt; &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; date
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$RESULT&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; !&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;IN BEARBEITUNG&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; telegram-send &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$RESULT&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sleep &lt;span class="m"&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;세상엔 다양한 워크플로우가 있고, 웬만한 워크플로우 구성 요소들은 나만 필요한게 아니라 다른 누군가도 필요하다. 그리고 그런 경우 대부분은 그 누군가가 만들어둔 것이 있다.&lt;/p&gt;
&lt;p&gt;매우 특수한 상황이 아니라면 그런 워크플로우들을 잘 결합해보면 간단한 자동화 프로그램을 만드는 것이 어렵지 않다는 것을 알 수 있는 소 프로젝트였다.&lt;/p&gt;</description></item><item><title>Starting With Hugo</title><link>https://gwangyi.github.io/posts/starting-with-hugo/</link><pubDate>Sun, 31 Jan 2021 18:55:00 +0100</pubDate><guid>https://gwangyi.github.io/posts/starting-with-hugo/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;예전에 야심찬 목표를 가지고 블로그를 만든 적이 있었다. 어딘가 기시감이 느껴지는 시작이긴 하지만&amp;hellip; 신기하고 새로운 것을 배워보고자 하는 마음에, 남들이 다들 하는 &lt;a href="https://www.ruby-lang.org/"&gt;Ruby&lt;/a&gt;로 된 &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt; 기반이 아니라 &lt;a href="https://nodejs.org/"&gt;NodeJS&lt;/a&gt; 기반 &lt;a href="https://11ty.io/"&gt;11ty&lt;/a&gt;를 써보기로 하고는 야심차게 시작했었다.&lt;/p&gt;
&lt;p&gt;하지만 육아와 이직준비, 기타등등의 여러가지 이유로 인해서 얼마 안 하다가 관리를 포기해버렸고, 다시 보려고 봤더니 static site generator와 그 프로젝트 구성, 플러그인, 내용물 등등이 이리저리 얽히고 섥혀서 어마어마한 의존성과 그로 인한 보안 구멍 등등 신경쓸 것들이 너무 많아진 것을 알게 되었다.&lt;/p&gt;
&lt;p&gt;그 와중에 예전에 작성했던 &lt;a href="https://github.com/gwangyi/eleventy-plugin-yamldata"&gt;플러그인&lt;/a&gt;은 어느샌가 &lt;a href="https://11ty.io/"&gt;11ty&lt;/a&gt; 자체가 제공하는 기능으로 들어가있질 않나, 총체적 난국이었다. 좋아, 그럼 이제 &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt;를 만든 회사에도 왔겠다, &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt; 공부도 해볼겸 &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt;로 작성된 유명한 static site generator인 &lt;a href="https://gohugo.io/"&gt;hugo&lt;/a&gt;로 다시 갈아엎어보자! 하는 마음으로 &lt;a href="https://gohugo.io/"&gt;hugo&lt;/a&gt;를 알아보기 시작했다.&lt;/p&gt;
&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;Static Site Generator에 대한 내용은 &lt;a href="https://gwangyi.github.io/posts/eleventy-blog/"&gt;이전 블로그 포스팅&lt;/a&gt;에서 대략적으로 다뤘으므로, 여기서는 &lt;a href="https://gohugo.io/"&gt;hugo&lt;/a&gt;와 &lt;a href="https://11ty.io/"&gt;11ty&lt;/a&gt;의 차이점에 대해서 가볍게 언급하고 지나가고자 한다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://11ty.io/"&gt;11ty&lt;/a&gt;는 앞 문단에서도 가볍게 언급하고 지나갔다시피, 블로그 프로젝트가 곧 node 프로젝트이며, 사용한 &lt;a href="https://11ty.io/"&gt;11ty&lt;/a&gt; 자체가 디펜던시로 물려있고 각 플러그인들도 마찬가지로 물려들어간다. 이건 뭘 의미하냐면, 블로그 프로젝트와 &lt;a href="https://11ty.io/"&gt;11ty&lt;/a&gt; 프레임워크, 그리고 플러그인이 하나의 큰 프로그램으로 묶여서 함께 구성된다는 것을 의미한다. 장점으로는 &lt;a href="https://11ty.io/"&gt;11ty&lt;/a&gt; 내부 구조가 투명히 보이기 때문에 플러그인의 제작 자유도가 높고 컨텐츠도 부분적으로 js로 작성이 가능한 만큼 표현력이 좋았다.&lt;/p&gt;
&lt;p&gt;그에 비해 &lt;a href="https://gohugo.io/"&gt;hugo&lt;/a&gt;는, &lt;a href="https://gohugo.io/"&gt;hugo&lt;/a&gt;가 블로그 프로젝트에 직접적으로 물려있지 않다. 블로그 프로젝트는 &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt; 프로젝트가 아니기 때문에 일반적인 &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt; 프로젝트의 방법으로 빌드되거나 하진 않는다. &lt;a href="https://11ty.io/"&gt;11ty&lt;/a&gt;가 프레임워크 같은 느낌이라면 &lt;a href="https://gohugo.io/"&gt;hugo&lt;/a&gt;는 말 그대로 Site Generator로써 동작한다.&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://gohugo.io/getting-started/quick-start/"&gt;아주 직관적인 튜토리얼&lt;/a&gt;이 공식 사이트에 올라와 있다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://gohugo.io/"&gt;hugo&lt;/a&gt;는 네이티브 바이너리를 생성하는 &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt; 언어로 작성된 프로그램 답게, 미리 빌드된 바이너리를 바로 &lt;a href="https://github.com/gohugoio/hugo/releases"&gt;배포&lt;/a&gt;하고 있다. 맞는 OS와 CPU를 찾아서 다운받아 풀면 간단하게 실행파일을 얻을 수 있다. 몇몇 고급 기능(ex. SCSS)은 기본 빌드에서는 빠져있고 extended 빌드를 받아야 된다는 점을 잊으면 안된다.&lt;/p&gt;
&lt;h2 id="theme"&gt;Theme&lt;/h2&gt;
&lt;p&gt;공식 사이트에 &lt;a href="https://themes.gohugo.io/"&gt;아주 다양한 테마&lt;/a&gt;들이 올라와 있다. 이 중에서 마음에 드는 것을 아무거나 고르자. 일반적으로, 테마들은 GitHub에서 배포되고 있으며 README에 어떻게 테마를 설정해줘야 하는지, 그리고 기본적인 설정파일의 골격은 어떻게 하면 되는지 안내되어 있으므로 참고해서 내 사이트에 맞게 수정해서 쓰면 된다.&lt;/p&gt;
&lt;p&gt;참고로 이 사이트는 &lt;a href="https://github.com/LukasJoswiak/etch"&gt;etch&lt;/a&gt; 테마를 사용하고 있다. JS 디펜던시를 최소화하고 심플하며 dark 모드를 지원하는 것을 골랐다. 거기에 약간의 커스터마이즈가 더 붙어 있다.&lt;/p&gt;
&lt;p&gt;테마에서 재미있는 점은, 테마를 사용하지 않고도 사이트를 구성할 수 있다는 점이다. 먼저 프로젝트가 자체적으로 가지고 있는 레이아웃이나 숏코드 등을 뒤져본 뒤에 지정된 테마들을 &lt;a href="https://gohugo.io/templates/lookup-order/"&gt;둘러보면서 찾도록&lt;/a&gt; 되어 있다. 즉, 여러 개의-0개를 포함하여- 테마들을 동시에 적용할 수 있다는 것이다! 물론 우선순위가 있어서 무작성 섞여 나오지는 않지만 일부 기능만 가진 테마를 다른 테마와 겹쳐 사용하는 것이 가능하다. 예를 들면 이 블로그가 사용하고 있는 &lt;a href="https://github.com/mochaaP/hugo-shortcode-mdi"&gt;mdi&lt;/a&gt;테마가 그런 종류의 테마로, material design icons 프로젝트에서 제공하는 아이콘을 삽입하는 숏코드만 제공하고 있다.&lt;/p&gt;
&lt;h2 id="how-to-post"&gt;How to Post&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://gohugo.io/getting-started/quick-start/"&gt;QuickStart&lt;/a&gt; 문서에 있는 것처럼 &lt;code&gt;hugo new posts/my-first-post.md&lt;/code&gt; 명령의 형태로 새로운 포스트를 등록할 수 있다. 일반적인 SSG들과 비슷하게 &lt;a href="https://gohugo.io/content-management/front-matter/"&gt;front matter 헤더&lt;/a&gt;가 맨 앞에 위치한다. 지원되는 포맷은 yaml 외에도 toml, json 등도 가능하다.&lt;/p&gt;
&lt;h2 id="publishing-as-github-pages-via-github-actions"&gt;Publishing as GitHub Pages via GitHub Actions&lt;/h2&gt;
&lt;p&gt;그동안 추가된 &lt;a href="https://github.com/features/actions"&gt;GitHub Actions&lt;/a&gt;는 서드파티 CI 툴 없이도 간편하게 CI 툴을 사용할 수 있게 해준다. 예전 블로그는 &lt;a href="https://travis-ci.org/"&gt;Travis CI&lt;/a&gt;를 이용해서 &lt;a href="https://github.com/gwangyi/gwangyi.github.io/blob/source/.travis.yml"&gt;블로그 빌드를 구성&lt;/a&gt;했었는데 이번에는 그 유명한 &lt;a href="https://github.com/features/actions"&gt;GitHub Actions&lt;/a&gt;를 써보기로 했다.&lt;/p&gt;
&lt;p&gt;이미 &lt;a href="https://github.com/features/actions"&gt;GitHub Actions&lt;/a&gt;가 설정되지 않은 리포지토리라면, 상단 탭의 Actions를 누르면 추천하는 &lt;a href="https://docs.github.com/en/actions/learn-github-actions/introduction-to-github-actions#workflows"&gt;Workflow&lt;/a&gt;들을 리스트로 보여준다. 이 중 하나를 눌러서 필요한 정보를 대충 채워넣으면 간단하게 CI를 구성할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/features/actions"&gt;GitHub Actions&lt;/a&gt;는 대략적으로 설명하면, 미리 &lt;a href="https://docs.github.com/en/actions/learn-github-actions/introduction-to-github-actions#workflows"&gt;Workflow&lt;/a&gt;를 구성해 놓으면 &lt;a href="https://docs.github.com/en/actions/learn-github-actions/introduction-to-github-actions#workflows"&gt;Workflow&lt;/a&gt;에서 정의한 트리거가 발동될 때 미리 정의된 &lt;a href="https://docs.github.com/en/actions/learn-github-actions/introduction-to-github-actions#actions"&gt;Action&lt;/a&gt;이 순서대로 실행되는 서비스라고 볼 수 있겠다. 다른 CI툴과 달리 기본이 되는 이미지는 아무 이미지나 사용할 수 없는 것 같고, 미리 제공되는 몇 개의 이미지 중에서 골라야 한다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://gohugo.io/"&gt;hugo&lt;/a&gt;가 유명한 툴인만큼, 미리 정의된 템플릿은 GitHub에서 제공해주지 않지만 누군가가 &lt;a href="https://github.com/peaceiris/actions-hugo"&gt;action&lt;/a&gt;을 만들어서 올려두었다. &lt;a href="https://github.com/mochaaP/hugo-shortcode-mdi"&gt;mdi&lt;/a&gt; 테마는 단순 clone만으로 동작하지 않고 추가 작업이 필요하기 때문에 액션을 추가해 줄 필요가 있었다.&lt;/p&gt;
&lt;script src="https://gist-it.appspot.com/https://github.com/gwangyi/gwangyi.github.io/raw/main/.github/workflows/gh-pages.yml"&gt;&lt;/script&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://11ty.io/"&gt;11ty&lt;/a&gt;는 당시 나에게 여러모로 좋은 블로깅 툴이었던 것은 맞았다. 하지만 시간이 지나고 이것저것 환경이 바뀐 뒤에는, 역시 여유가 없을 땐 사람들이 많이 쓰는 것을 쓰는게 정신건강상 최고라는 것을 다시 한번 느꼈다.&lt;/p&gt;</description></item><item><title>pyenv를 termux에서 써보자 (1)</title><link>https://gwangyi.github.io/posts/pyenv-in-termux/</link><pubDate>Sat, 12 Jan 2019 00:00:00 +0000</pubDate><guid>https://gwangyi.github.io/posts/pyenv-in-termux/</guid><description>&lt;p&gt;&lt;strong&gt;수정&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;오타 수정: &lt;code&gt;xarg&lt;/code&gt; -&amp;gt; &lt;code&gt;xargs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;필요한 패키지 설치 항목 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://termux.com/"&gt;Termux&lt;/a&gt;는 훌륭한 유사-리눅스 환경을 안드로이드 장치 위에서 제공해준다. 단순한 유틸리티 뿐만 아니라, 컴파일러와 오픈소스 라이브러리의 소스코드까지도 함께 제공받을 수 있다. 그러한 배경에서, 안드로이드 장치 위에서 파이썬을 설치해서 다양한 장난감을 만들어 보는 것 또한 즐거운 일일 것이다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://termux.com/"&gt;Termux&lt;/a&gt;는 컴파일러를 제공하는 것처럼 파이썬 또한 미리 빌드한 바이너리 형태로 제공하고 있다. 하지만 설치할 수 있는 파이썬 패키지는 2.x중에서 하나, 3.x중에서 하나 뿐으로 그나마도 교체해가면서 쓰기에는 불편하기 짝이 없다. 일반적인 경우에야 하나만 있어도 별 일이 없겠지만, 우리는 항상 만의 하나를 가정하고 살아야 하므로 파이썬 버전 관리자를 사용해 보도록 하자. 이 포스팅에서 다루고자 하는 파이썬 버전 관리자는 &lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;로, 파이썬 버전 뿐만 아니라 &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;Virtualenv&lt;/a&gt;까지도 관리해줄 수 있어 매우 편리하다.&lt;/p&gt;
&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;어떤 프로그래밍 언어의 구현체가 있다면, 그 또한 하나의 프로그램이며, 버그나 불편함 등으로 인하여 정기적으로 수정되고 재배포되는 것은 필언적이다. 심지어 C 언어와 같은 역사가 오래된 언어들은, 느슨한 형태의 표준 명세를 컴파일러를 만드는 회사마다 조금씩 다른 형태로 구현해버려 특정 컴파일러를 염두해 두고 작성한 소스코드는 다른 컴파일러에서는 컴파일이 안되거나 오동작까지 하기도 한다.&lt;/p&gt;
&lt;p&gt;파이썬은 기준이 되는 구현체가 있고 &lt;a href="https://en.wikipedia.org/wiki/Benevolent_dictator_for_life"&gt;BDFL&lt;/a&gt; - 지금은 사임했지만 - 에 의해 중앙집권적으로 표준이 관리되어 왔기 때문에 앞서 언급한 C와 같은 파편화는 없지만, 그럼에도 불구하고 다양한 feature가 추가되는 과정에서 호환이 깨지거나, 기준 구현체가 아닌 독특한 구현체를 필요로 할 경우가 종종 있을 수 있다.&lt;/p&gt;
&lt;p&gt;거기에 더해서, &lt;a href="https://nodejs.org/en/"&gt;node.js&lt;/a&gt;와는 달리 기본적으로 모든 패키지는 전역적인 형태로 설치되며, 사용자 단위로 설치한다 해도 해당 사용자 입장에서는 전역적으로 설치되기 때문에 패키지들의 버전 관리가 어려운 점도 있다. 이를 해결하기 위해 &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;Virtualenv&lt;/a&gt; 프로젝트도 있으나, virtualenv들을 관리하고 activate/deactivate 하는 작업이 지난하고 귀찮다는 단점이 있다.&lt;/p&gt;
&lt;p&gt;이전부터 버전간 호환성이 나쁘기로 유명했던 &lt;a href="https://ruby-lang.org/"&gt;ruby&lt;/a&gt;의 경우, 꽤 빠른 시기부터 다양한 버전의 &lt;a href="https://ruby-lang.org/"&gt;ruby&lt;/a&gt; 구현체를 설치하고 이를 교체해가며 쓸 수 있는 프로젝트들이 제안되어 왔다. &lt;a href="https://rvm.io/"&gt;RVM&lt;/a&gt;이 매우 유명하며, 여기로부터 영향을 받아 &lt;a href="https://nodejs.org/en/"&gt;node.js&lt;/a&gt; 진영에서는 &lt;a href="https://github.com/creationix/nvm"&gt;nvm&lt;/a&gt;이 만들어졌다. &lt;a href="https://rvm.io/"&gt;RVM&lt;/a&gt;의 &lt;a href="https://github.com/rbenv/rbenv/wiki/Why-rbenv%3F"&gt;한계&lt;/a&gt;를 개선하기 위한 &lt;a href="https://github.com/rbenv/rbenv"&gt;rbenv&lt;/a&gt; 프로젝트도 있다.&lt;/p&gt;
&lt;p&gt;이번 포스팅에서 다루고자 하는 &lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;는 이름으로부터 &lt;a href="https://github.com/rbenv/rbenv"&gt;rbenv&lt;/a&gt;로부터 영향을 받은 프로젝트임을 알 수 있다. &lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;는 기본적으로 다양한 파이썬 구현체를 설치하는 과정을 자동화하여 루트 권한 없이도 간단하게 다양한 구현체의 파이썬을 설치할 수 있게 해 주고, 사용자 전역, 디렉토리, 셸 세션 단위로 사용할 파이썬 버전을 지정해줄 수 있는 기능을 갖고 있다. 또한 간단한 확장을 설치하면 &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt;도 다른 파이썬 버전들을 다룰때와 같이 간단하게 생성, 활성화, 비활성화 시킬 수 있어 편리함을 증진시켜준다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;와 비슷한 용도로 사용할 수 있는 &lt;a href="https://github.com/pypa/pipenv"&gt;pipenv&lt;/a&gt;라는 프로젝트도 있다. &lt;a href="https://github.com/pypa/pipenv"&gt;Pipenv&lt;/a&gt;는 &lt;a href="https://nodejs.org/en/"&gt;node.js&lt;/a&gt;와 비슷하게 프로젝트 단위 - 실질적으로는 디렉토리 단위 - 로 가상환경을 생성하고 관리할 수 있게 해 준다. 버전도 지정이 가능하지만, 설치되지 않은 파이썬 버전을 지정한 경우 &lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;를 통해 버전 관리를 하게 된다. 또, &lt;a href="https://github.com/pypa/pipenv"&gt;pipenv&lt;/a&gt;는 shim 관리를 해주지 않아 &lt;a href="https://github.com/pypa/pipenv"&gt;pipenv&lt;/a&gt;를 통해 스크립트를 실행해야하는 차이도 있다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;가 좋은 툴인 것은 확실하지만, &lt;a href="https://termux.com/"&gt;Termux&lt;/a&gt; 위에서 사용할 때는 몇 가지 문제가 발생한다. 표준 리눅스 환경을 가정하고 빌드 스크립트가 구성되어 있어, 파이썬 설치시 다양한 문제점이 발생한다. 게다가, 안드로이드에서 기본적으로 설치되어 있는 &lt;a href="https://busybox.net/"&gt;BusyBox&lt;/a&gt;는 표준 GNU 유틸리티와 미묘한 차이가 존재하여, 빌드 스크립트의 오동작이 잦아 여러 문제점을 일으켜 빌드하는 과정이 순탄치가 못하다.&lt;/p&gt;
&lt;p&gt;이번 포스팅의 목표는, &lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;를 &lt;a href="https://termux.com/"&gt;Termux&lt;/a&gt; 위에 설치한 뒤 최신 CPython 구현체를 설치하여 실행해 보는 것을 목표로 한다.&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;는 다양한 방법으로 설치해볼 수 있지만, 이 중에서 가장 편리한 것은 &lt;a href="https://github.com/pyenv/pyenv-installer"&gt;pyenv-installer&lt;/a&gt;를 사용하는 것이다. 그냥 스크립트만 실행하면 &lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt; 설치가 한방에 끝난다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer &lt;span class="p"&gt;|&lt;/span&gt; bash
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;관리자 권한이 필요 없고, 관리자 권한으로 실행하지 않기를 강력하게 추천하는 바이다. 실행이 끝나면, &lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;를 정상적으로 사용하기 위한 추가 작업의 내용을 간단하게 설명해주므로 따라서 설정해주도록 하자. &lt;a href="https://www.gnu.org/software/bash/"&gt;bash&lt;/a&gt;를 사용중이라면 &lt;code&gt;$HOME/.bashrc&lt;/code&gt;에, &lt;a href="https://www.zsh.org/"&gt;zsh&lt;/a&gt;를 사용중이라면 &lt;code&gt;$HOME/.zshrc&lt;/code&gt;에 &lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt; 명령을 등록하는 명령을 추가하자. 다음과 비슷한 코드를 입력해야 할 것이다. 만약 &lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt; 버전이 많이 달라져서 안내된 명령이 아래 명령과 다르다면, 안내된 대로 수정하는 것이 좋다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;~/.pyenv/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;pyenv init -&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;pyenv virtualenv-init -&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;.bashrc&lt;/code&gt; 파일을 수정했으므로 셸을 다시 열도록 하자.&lt;/p&gt;
&lt;p&gt;추후에 &lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;를 업그레이드 할 일이 생기면 다음 명령으로 업그레이드할 수 있다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pyenv update
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="install-python"&gt;Install Python&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt; 설치는 간단하지만, 이를 통해 파이썬을 빌드하는 것은 간단하지 않다. 앞에서 말했듯 &lt;a href="https://termux.com/"&gt;Termux&lt;/a&gt; 환경은 완전한 리눅스 환경이 아니라서 하드코딩된 디렉토리 패스와 같은 것들이 빌드 과정에서 문제를 지속적으로 일으킨다. 그러나 &lt;a href="https://termux.com/"&gt;Termux&lt;/a&gt;에 파이썬이 포팅되지 않은 것이 아니므로, &lt;a href="https://termux.com/"&gt;Termux&lt;/a&gt;의 패키지를 어떻게 관리하고 있는지 먼저 찾아볼 필요가 있다.&lt;/p&gt;
&lt;h3 id="install-dependencies"&gt;Install dependencies&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://termux.com/"&gt;Termux&lt;/a&gt;의 패키지는 &lt;a href="https://github.com/termux/termux-packages"&gt;termux-packages&lt;/a&gt; 리포지터리를 통하여 관리되고 있다. 해당 깃헙 리포지터리에 가 보면 현재 &lt;a href="https://termux.com/"&gt;Termux&lt;/a&gt;에서 지원하는 다양한 패키지들에 대한 패치와 빌드 스크립트를 확인할 수 있다. 그 중에 &lt;a href="https://github.com/termux/termux-packages/tree/master/packages/python"&gt;파이썬 패키지&lt;/a&gt;를 보면 &lt;a href="https://termux.com/"&gt;Termux&lt;/a&gt; 환경에서 파이썬을 빌드하기 위해 필요한 부분들을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;먼저 &lt;code&gt;TERMUX_PKG_DEPENDS&lt;/code&gt; 항목을 보면 termux의 python 패키지가 의존하는 패키지 목록이 나온다. &lt;a href="https://github.com/termux/termux-packages/pull/4071"&gt;termux 정책 변화&lt;/a&gt;로 인해 별도의 &lt;code&gt;-dev&lt;/code&gt; 패키지가 없어졌기 때문에, 목록에 있는 패키지를 먼저 설치해주자.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkg in build-essential gdbm libandroid-posix-semaphore libandroid-support libbz2 libcrypt libexpat libffi liblzma libsqlite ncurses ncurses-ui-libs openssl readline zlib
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="obtain-patch"&gt;Obtain Patch&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;는 빌드 과정이 모두 자동화되어 있어 중간에 소스코드를 수정할 시간은 없다. 대신, &lt;a href="http://savannah.gnu.org/projects/patch/"&gt;patch&lt;/a&gt; 명령을 이용하여 패치를 적용하는 기능을 가지고 있다. 일반적인 &lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;를 이용한 파이썬 설치는 &lt;code&gt;pyenv install 3.11.2&lt;/code&gt; 와 같이 진행되지만, 패치를 먹이는 작업을 함께 수행한다면 다음과 같이 명령하면 된다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat python.patch &lt;span class="p"&gt;|&lt;/span&gt; pyenv install -p 3.11.2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;-p&lt;/code&gt; 옵션을 주게 되면, 표준 입력으로부터 패치를 받아 적용하는 과정을 추가적으로 거치게 된다. 이 때 &lt;a href="http://savannah.gnu.org/projects/patch/"&gt;patch&lt;/a&gt; 명령에는 &lt;code&gt;-p 0&lt;/code&gt; 옵션으로 전달되기 때문에 패치 파일 내부의 파일 주소를 적절하게 조정해 줄 필요가 있다. 이전에는 환경변수로 &lt;a href="http://savannah.gnu.org/projects/patch/"&gt;patch&lt;/a&gt;의 옵션을 지정할 수 있었던 것 같지만, 지금은 그렇지 않다.&lt;/p&gt;
&lt;p&gt;여기까지 내용을 요약하면 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/termux/termux-packages"&gt;termux-packages&lt;/a&gt;의 &lt;a href="https://github.com/termux/termux-packages/tree/master/packages/python"&gt;파이썬 패키지&lt;/a&gt;의 패치 목록을 취득&lt;/li&gt;
&lt;li&gt;취득한 패치 목록의 내용을 모두 이어붙임&lt;/li&gt;
&lt;li&gt;이어붙인 패치를 &lt;code&gt;pyenv install -p&lt;/code&gt; 명령의 표준 입력으로 인가&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이를 위해 필요한 것은&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;리포지터리의 디렉토리 내용 조회&lt;/li&gt;
&lt;li&gt;조회된 내용으로부터 패치 내용 취득&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;두 가지 기능이 필요할 것이다. 각각은 다음과 같이 얻어낼 수 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;GitHub API 이용 &lt;a href="https://developer.github.com/v3/repos/contents/#get-contents"&gt;Contents/Get contents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;1의 결과는 json 형태이므로 이를 &lt;a href="https://stedolan.github.io/jq/"&gt;jq&lt;/a&gt;로 파싱&lt;/li&gt;
&lt;li&gt;결과를 &lt;a href="http://man7.org/linux/man-pages/man1/xargs.1.html"&gt;xargs&lt;/a&gt;에 전달하여 각각 내려받은 후 표준 출력으로 내보내기&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;http 다운로드를 위해서는 &lt;a href="https://curl.haxx.se/"&gt;curl&lt;/a&gt;이나 &lt;a href="https://www.gnu.org/software/wget/"&gt;wget&lt;/a&gt;을 쓸 수 있을텐데, 개인적으로 &lt;a href="https://www.gnu.org/software/wget/"&gt;wget&lt;/a&gt;이 더 편하므로 &lt;code&gt;pkg in wget&lt;/code&gt;을 통해 설치하도록 하자. wget은 &lt;a href="https://busybox.net/"&gt;BusyBox&lt;/a&gt;에 내장되어 있기는 하나, 내장된 버전은 https를 처리하지 못하므로 따로 설치하는 것이 좋다.
&lt;a href="https://stedolan.github.io/jq/"&gt;jq&lt;/a&gt;는 &lt;a href="https://termux.com/"&gt;Termux&lt;/a&gt;에서 미리 빌드된 패키지로 설치 가능하다. &lt;code&gt;pkg in jq&lt;/code&gt; 로 설치해두자.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkg in wget jq
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;먼저 json 파일의 구조를 살펴보기 위해 다음과 같이 실행해보자.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget -O - https://api.github.com/repos/termux/termux-packages/contents/packages/python -q
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;특정 커밋이나 태그, 브랜치를 지정하고 싶으면 python 뒤에 &lt;code&gt;?ref=...&lt;/code&gt; 형태로 지정하면 된다. 이 때 결과물은 다음과 비슷할 것이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Lib-subprocess.py.patch&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;packages/python/Lib-subprocess.py.patch&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;sha&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bea4bbf23358e4b6d64a59f659b206d3201c06e2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;size&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;663&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://api.github.com/repos/termux/termux-packages/contents/packages/python/Lib-subprocess.py.patch?ref=master&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;html_url&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://github.com/termux/termux-packages/blob/master/packages/python/Lib-subprocess.py.patch&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;git_url&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://api.github.com/repos/termux/termux-packages/git/blobs/bea4bbf23358e4b6d64a59f659b206d3201c06e2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;download_url&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://raw.githubusercontent.com/termux/termux-packages/master/packages/python/Lib-subprocess.py.patch&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;_links&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;self&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://api.github.com/repos/termux/termux-packages/contents/packages/python/Lib-subprocess.py.patch?ref=master&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;git&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://api.github.com/repos/termux/termux-packages/git/blobs/bea4bbf23358e4b6d64a59f659b206d3201c06e2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;html&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://github.com/termux/termux-packages/blob/master/packages/python/Lib-subprocess.py.patch&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;디렉토리이기 때문에 배열 형태로 반환되며, 각 아이템은 디렉토리에 있는 파일들에 대한 정보가 들어있다. 여기서 &lt;code&gt;name&lt;/code&gt; 속성이 파일 이름이며, &lt;code&gt;download_url&lt;/code&gt; 속성의 값으로 요청하면 파일의 내용을 얻어올 수 있다. 패치를 모두 엮기 위해서는,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;이 &lt;code&gt;.patch&lt;/code&gt;로 끝나는 아이템들을 골라낸 뒤&lt;/li&gt;
&lt;li&gt;아이템에서 &lt;code&gt;download_url&lt;/code&gt; 을 추출한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;실제 &lt;a href="https://github.com/termux/termux-packages"&gt;termux 패키지 빌드 스크립트&lt;/a&gt;를 보면 &lt;code&gt;.patch&lt;/code&gt; 파일과 &lt;code&gt;.patch32|64&lt;/code&gt; 파일을 코어 bit수에 맞추어 선택하여 모두 사용하지만, 파이썬에는 &lt;code&gt;.patch&lt;/code&gt; 파일만 있어 이것만 사용하였다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://stedolan.github.io/jq/"&gt;jq&lt;/a&gt;의 쿼리를 작성하려면,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;모든 아이템에 대해 반복하기 위해 첫 번째는 &lt;code&gt;.[]&lt;/code&gt;이 된다.&lt;/li&gt;
&lt;li&gt;이 중에 특정 조건을 만족하는 아이템만 뽑아야 하므로 &lt;code&gt;|&lt;/code&gt;로 &lt;code&gt;select()&lt;/code&gt;를 연결해야 된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;select()&lt;/code&gt;의 조건은 &lt;code&gt;name&lt;/code&gt; 속성을 추출한 뒤 &lt;code&gt;endswith()&lt;/code&gt;로 맨 뒷 부분을 확인해야 한다. 즉, &lt;code&gt;.name|endswith(&amp;quot;.patch&amp;quot;)&lt;/code&gt;가 조건이 된다.&lt;/li&gt;
&lt;li&gt;필터링 한 후에는 그 안에서 &lt;code&gt;download_url&lt;/code&gt;을 추출해야 하므로 &lt;code&gt;|&lt;/code&gt;로 &lt;code&gt;.download_url&lt;/code&gt;을 연결하면 최종적으로 쿼리가 완성된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;완성된 최종 쿼리는 다음과 같다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget -O - https://api.github.com/repos/termux/termux-packages/contents/packages/python -q &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jq &lt;span class="s1"&gt;&amp;#39;.[]|select(.name|endswith(&amp;#34;.patch&amp;#34;))|.download_url&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;결과는 다음과 같이 나올 것이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;https://raw.githubusercontent.com/termux/termux-packages/master/packages/python/Lib-subprocess.py.patch&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;https://raw.githubusercontent.com/termux/termux-packages/master/packages/python/Lib-tmpfile.py.patch&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;https://raw.githubusercontent.com/termux/termux-packages/master/packages/python/Modules-socketmodule.c.patch&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;https://raw.githubusercontent.com/termux/termux-packages/master/packages/python/_cursesmodule.c.patch&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;https://raw.githubusercontent.com/termux/termux-packages/master/packages/python/configure.patch&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;https://raw.githubusercontent.com/termux/termux-packages/master/packages/python/cryptmodule.c.patch&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;https://raw.githubusercontent.com/termux/termux-packages/master/packages/python/fix-paths.patch&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;https://raw.githubusercontent.com/termux/termux-packages/master/packages/python/setup.py.patch&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;이제 이 각각의 url으로 wget을 실행해주면 연속해서 표준 출력으로 출력할 수 있다. &lt;a href="http://man7.org/linux/man-pages/man1/xargs.1.html"&gt;xargs&lt;/a&gt;를 이용하자.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget -O - https://api.github.com/repos/termux/termux-packages/contents/packages/python &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jq &lt;span class="s1"&gt;&amp;#39;.[]|select(.name|endswith(&amp;#34;.patch&amp;#34;))|.download_url&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; xargs wget -O - -q
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;결과는 다음과 비슷하게 나올 것이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;diff -u -r ../Python-3.7.1/Lib/subprocess.py ./Lib/subprocess.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;--- ../Python-3.7.1/Lib/subprocess.py 2018-10-20 06:04:19.000000000 +0000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+++ ./Lib/subprocess.py 2018-10-20 20:17:50.157206343 +0000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -1389,9 +1389,7 @@
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; args = list(args)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if shell:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- # On Android the default shell is at &amp;#39;/system/bin/sh&amp;#39;.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- unix_shell = (&amp;#39;/system/bin/sh&amp;#39; if
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- hasattr(sys, &amp;#39;getandroidapilevel&amp;#39;) else &amp;#39;/bin/sh&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;여기서 문제는, 패치 적용시 &lt;code&gt;@TERMUX_PREFIX@&lt;/code&gt;와 같은 교체해야하는 스트링이 조금 포함되어 있다는 것과, Termux에서 사용하는 패치는 &lt;code&gt;-p 1&lt;/code&gt; 옵션을 상정하고 만들어졌다는 점이다. 이를 해결하기 위해서는 &lt;a href="https://www.gnu.org/software/sed/"&gt;sed&lt;/a&gt; 를 사용하면 편리하다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget -O - https://api.github.com/repos/termux/termux-packages/contents/packages/python &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jq &lt;span class="s1"&gt;&amp;#39;.[]|select(.name|endswith(&amp;#34;.patch&amp;#34;))|.download_url&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; xargs wget -O - -q &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sed &lt;span class="s2"&gt;&amp;#34;s%@TERMUX_PREFIX@%&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;%g; s%^+++ [^/ ]*%+++ .%&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;빌드 스크립트에는 &lt;code&gt;@TERMUX_HOME@&lt;/code&gt; 등의 치환자가 더 있지만, 파이썬에서는 사용하지 않기 때문에 &lt;code&gt;@TERMUX_PREFIX@&lt;/code&gt;만 변경했고, 바뀌어야되는 값은 환경변수 &lt;code&gt;PREFIX&lt;/code&gt;이다. 그리고 패치의 대상 파일 경로는 &lt;code&gt;+++&lt;/code&gt;으로 시작하는 행에 나타나므로, 첫 번째 디렉토리 부분을 &lt;code&gt;./&lt;/code&gt;으로 교체하여 &lt;code&gt;-p 1&lt;/code&gt; 옵션을 흉내내었다.
이제 이 것을 &lt;code&gt;pyenv install -p&lt;/code&gt;에 집어넣어 설치해보면, 뭔가가 진행되다 실패하는 것을 볼 수 있다. 패치에 실패하는 것은 &lt;a href="https://busybox.net/"&gt;BusyBox&lt;/a&gt;의 &lt;code&gt;patch&lt;/code&gt; 애플릿의 기능이 딸려서이므로, &lt;code&gt;pkg in patch&lt;/code&gt;를 통해 제대로 된 GNU patch 툴을 설치한 뒤에 재시도하면 된다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget -O - &lt;span class="s2"&gt;&amp;#34;https://api.github.com/repos/termux/termux-packages/contents/packages/python?ref=4f87b638eb93d17b89c499c3ac7d4034d5e1ac5f&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jq &lt;span class="s1"&gt;&amp;#39;.[]|select(.name|endswith(&amp;#34;.patch&amp;#34;))|.download_url&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; xargs wget -O - -q &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sed &lt;span class="s2"&gt;&amp;#34;s%@TERMUX_PREFIX@%&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt;%g; s%^+++ [^/ ]*%+++ .%&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pyenv install -p -v 3.11.2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;참고로 &lt;code&gt;pyenv install&lt;/code&gt;에 &lt;code&gt;-v&lt;/code&gt; 옵션을 주면 빌드 과정을 볼 수 있다. 일반적인 경우는 굳이 쓸 필요가 없지만, 우리처럼 제대로 안되는 환경에서 어거지로 돌려야 되는 경우에는 에러 메시지를 바로바로 볼 수 있게 해 두는 편이 문제 파악하기에 조금 더 편리하므로 계속 넣도록 하자. 물론 &lt;code&gt;-v&lt;/code&gt; 옵션을 주지 않았다고 해서 로그를 볼 수 없는 것은 아니다. temp 디렉토리를 찾아보면 &lt;code&gt;python-build&lt;/code&gt;로 시작하는 디렉토리와 로그 파일을 발견할 수 있다.&lt;/p&gt;
&lt;p&gt;그리고 &lt;code&gt;ref=&lt;/code&gt; 뒤에 오는 해시값은 커밋의 해시값이다. 지금 넣어놓은 값은 &lt;a href="https://github.com/termux/termux-packages/tree/4f87b638eb93d17b89c499c3ac7d4034d5e1ac5f"&gt;&lt;code&gt;python: Bump to 3.11.2&lt;/code&gt;&lt;/a&gt; 커밋인데, 추후에 새로운 python 버전이 나와도 안정적으로 빌드할 수 있게 하기 위해 붙여두었다. 다른 버전의 python을 빌드하고 싶다면 떼거나 적절한 해시값으로 변경하도록 하자.&lt;/p&gt;
&lt;p&gt;이제부터는 플래그를 설정하고, 요구되는 라이브러리를 찾고, 빌드 과정의 소소한 문제들을 찾아가면서 파이썬을 설치할 수 있게 고쳐갈 것이다.&lt;/p&gt;
&lt;h2 id="future-work"&gt;Future work&lt;/h2&gt;
&lt;p&gt;설치를 다 못했는데 여기서 자르니 매우 찝찝하지만, 분량이 너무 커진 관계로 나머지는 다음 포스팅에 담도록 하겠다.&lt;/p&gt;
&lt;p&gt;다음 포스팅에서는 컴파일·링크에 필요한 플래그를 지정하고, 빌드에 필요한 라이브러리 패키지들을 소개하고, 파일 시스템의 한계로 인한 버그를 잡아보도록 하겠다.&lt;/p&gt;</description></item><item><title>CreateRemoteThread로 DLL Injection하기</title><link>https://gwangyi.github.io/posts/dll-injection-by-createremotethread/</link><pubDate>Sat, 15 Dec 2018 01:03:00 +0000</pubDate><guid>https://gwangyi.github.io/posts/dll-injection-by-createremotethread/</guid><description>&lt;p&gt;&lt;strong&gt;ChangeLog&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2018/12/19: DllMain 내용 보강&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;우리가 컴퓨터를 사용하는 이유는, 컴퓨터를 통해 내가 직면한 문제를 해결하는데에 그 근본이 있을 것이다. 그 문제는 암산으로 해결하기엔 너무 복잡한 수학 문제의 답을 구하는 것일 수도 있고, 손으로 한땀 한땀 하기에는 너무나 귀찮은 일을 자동화하는 것일 수도 있고, 심심함을 해소하려는 것일 수도 있다.
컴퓨터를 통해 문제를 해결한다는 것은, 그 문제를 해결할 수 있는 프로그램을 구해서 적절한 사용을 통해 프로그램이 문제를 해결하도록 하는 것이다. 문제를 해결하는데는 하나의 프로그램으로 충분할 수도 있지만, 여러 개의 프로그램을 같이 이용해야 하거나 프로그램을 이용해 또 다른 프로그램을 만들어 해결해야 할 때도 있다.
때론 기존의 프로그램을 거의 그대로 사용하면서 필요한 기능 일부만 추가하면 간단히 해결할 수 있을 때도 많다. 오픈소스 프로그램이라면 그럴 때는 소스코드를 내려받아서 필요한 부분을 패치해 쓰면 되지만, 세상 만사가 어찌 그리 쉬울까. 우리가 만나는 대부분의 프로그램은 클로즈드 소스 형태로 배포되는 경우가 많아 일이 그렇게 쉽게만 풀리지는 않는다.&lt;/p&gt;
&lt;p&gt;대상 프로그램에 내가 원하는 코드를 주입해서 그 실행 흐름을 바꾸는 일은 해킹에 해당되는 일이다. 해커들은 온갖 방법을 동원해서 대상이 내가 원하는 일을 &lt;em&gt;실수로&lt;/em&gt; 실행해주기를 바란다. 그리고 프로그램을 개발하는 개발자들은, 자신이 모르는 방법을 통해 예상하지 못한 일을 하는 프로그램을 매우 싫어한다. 그래서 개발자는 이런 것을 가능하게 하는 부분을 최대한 많이 찾아 막아버리고, 해커는 그걸 어떻게든 피해서 원하는 코드를 우겨넣고야 마는 전쟁이 지금까지도 계속되고 있는 것이다.&lt;/p&gt;
&lt;p&gt;이 포스팅에서는 DLL Injection이라고 불리는 기법을 소개해보려고 한다. 그 중에서도 특별히 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createremotethread"&gt;&lt;code&gt;CreateRemoteThread()&lt;/code&gt;&lt;/a&gt; API를 이용해서 DLL Injection을 수행하는 과정을 소개하도록 하겠다. DLL Injection을 통해 여러 가지 재미있는 일을 해볼 수 있을 것이다.&lt;/p&gt;
&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;옛날 옛적 DOS시절에는 프로세스간 메모리 영역이 분리되어있지 않았다. 즉, 서로 다른 프로세스의 메모리는 서로 다른 주소를 가지고 배치되었고, 같은 메모리 주소라면 실제로 같은 물리적인 메모리를 참조할 수 있었다. 이 때는 그냥 &amp;lsquo;램 상주&amp;rsquo; 형태로 유틸리티를 메모리에 올려놓고, 특정 인터럽트가 발생하면 유틸리티의 코드가 실행되도록 설정해놓으면 대상 프로그램이 한참 실행되다가 원하는 유틸리티 프로그램의 코드를 실행하도록 할 수 있었다. 그러면 유틸리티는 내가 원하는 동작을 대상 프로그램의 메모리에다 대고 이것 저것을 한 뒤에 빠져나오면 뭐든 할 수 있었던 시절이었다. 그 시절에 게임 좀 해본 사람은 다들 알 Game Wizard 같은 유틸리티가 그런 유틸리티에 속했다.&lt;/p&gt;
&lt;p&gt;하지만 윈도우즈가 널리 쓰이게 되면서, 프로세스간 메모리 영역이 분리되기 시작하자 예전과 같은 단순한 방법으로는 재미를 보기 어려워졌다. 단순히 아무 메모리 주소나 잡는다고 해서 다른 프로세스의 메모리 영역이 보이지 않게 된 것이다. 그렇다고 해서 다른 프로세스의 메모리에 아주 접근할 수 없게 해버리면 여러가지 문제가 있기 때문에 몇 가지의 뒷구멍이 존재했고, 이 것을 이용해 예전과 같은 재미있는 일들을 어느 정도 할 수 있었다.&lt;/p&gt;
&lt;h3 id="debugging-api"&gt;Debugging API&lt;/h3&gt;
&lt;p&gt;아무리 윈도우즈가 됐고 프로세스 메모리를 분리한다고 해도, 디버깅이라는 작업은 예전이나 지금이나 가능해야 했다. 만약 프로세스끼리 전혀 접근이 되지 않는다면, 디버깅을 위해선 커널을 붙들던지 CPU에 디버깅용 하드웨어를 직접 갖다붙이던지 해야 간신히 할 수 있을 것이다. 그래서 윈도우즈에서는 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/debug/debugging-functions"&gt;Debugging API&lt;/a&gt;를 제공하여 디버거 프로세스가 디버기 프로세스를 쥐고 흔들 수 있게 해 두었다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/windows/desktop/debug/debugging-functions"&gt;Debugging API&lt;/a&gt;가 많은 것을 할 수 있지만 몇 가지 확실한 한계가 있다. 일단 디버거가 붙어있는지 여부를 디버기 입장에서 확인하기가 쉽고, 많은 양의 코드를 집어넣는 것이 이것만으로는 복잡한 감이 있다.&lt;/p&gt;
&lt;p&gt;그럼에도 불구하고, 기본적인 메모리 읽기/쓰기 동작은 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/debug/debugging-functions"&gt;Debugging API&lt;/a&gt;에서 제공하는 것이 간단하기 때문에 밑에 소개할 다른 방법에서도 가져다 쓸 것이다.&lt;/p&gt;
&lt;h3 id="registry"&gt;Registry&lt;/h3&gt;
&lt;p&gt;윈도우즈에는 어플리케이션이 실행될 때 자동으로 특정 DLL이 로딩되게 하는 기능이 들어 있다. 김 빠질 정도로 간단하게 원하는 실행 코드가 들어 있는 DLL을 삽입시킬 수 있지만, 이 것도 단점이 있다. 시스템 전체에 걸쳐서 어떤 기능을 추가하거나 감시하는 정상적인 프로그램을 위한 기능이지만, 일반적으로 평소엔 사용하지 않는 기능이고, 우리와 같이 &lt;em&gt;재밌는 일&lt;/em&gt; 을 하기 위해 이 기능을 이용하면 위험하기 때문에, 웬만한 백신 프로그램은 저 항목을 보이는 즉시 지워버릴 것이다. 게다가, 최신 윈도우즈에서는 서명된 DLL만 들어갈 수 있기 때문에, 아무 DLL에 해당하는 우리의 재밌는 DLL은 거부되어 버릴 것이 분명하다.&lt;/p&gt;
&lt;p&gt;이에 해당되는 레지스트리 주소는 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs&lt;/code&gt;: user32.dll이 로딩될 때 함께 로딩됨&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\AppCertDLLs&lt;/code&gt;: 새로운 프로세스가 실행될 때 로딩됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;참고로 일반적인 시스템에는 저런 곳에 뭔가가 들어있지 않기 때문에 저런게 있다면 뭔가 문제가 생겼다고 생각해도 크게 틀리지 않다.&lt;/p&gt;
&lt;h3 id="createremotethread"&gt;CreateRemoteThread&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createremotethread"&gt;&lt;code&gt;CreateRemoteThread()&lt;/code&gt;&lt;/a&gt; 함수는 스레드를 생성하는 함수인데, 저 함수를 호출하는 프로세스가 아닌 다른 프로세스에 스레드를 생성하는 기능을 가지고 있다. 여기서 재미있는 점은 DLL을 읽어서 실행가능한 영역에 적재하는 함수인 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibrarya"&gt;&lt;code&gt;LoadLibrary()&lt;/code&gt;&lt;/a&gt; 함수가 스레드 프로시저와 비슷한 모양을 가지고 있다는 것이다. &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createremotethread"&gt;&lt;code&gt;CreateRemoteThread()&lt;/code&gt;&lt;/a&gt;를 활용하는 DLL injection은, 바로 이 점을 이용해서 원하는 DLL을 적재하는 스레드를 생성시켜 대상 프로세스에 나의 코드를 주입하는 방법이다.&lt;/p&gt;
&lt;h3 id="hook"&gt;Hook&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-setwindowshookexa"&gt;&lt;code&gt;SetWindowsHookEx()&lt;/code&gt;&lt;/a&gt; 함수를 이용하면 좀 더 깔끔하게 API를 상대 프로세스에 주입하는 것이 가능하다. 메시지 루프가 돌 때 이 함수를 통해 집어넣은 훅이 동작하게끔 해주는 함수인데, 그 동작을 위해서 OS에서 직접 훅을 박아넣어 주기 때문에 웬만하면 무리없이 동작할 수 있다.
다만 메시지 루프가 돌아야 동작을 한다는 점 때문에, 메시지 루프가 없으면 소용이 없다는 문제가 있다. 그 외에도 주입 시점을 지정하기 어렵다거나, 이것저것 가리지 않고 훅이 박혀버린다거나 하는 단점이 있어 개인적으로 선호하지 않는다.&lt;/p&gt;
&lt;h2 id="mechanism"&gt;Mechanism&lt;/h2&gt;
&lt;p&gt;스레드를 실행하기 위해 준비해야되는 프로시저는 다음과 같은 프로토타입을 가져야 한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;DWORD&lt;/span&gt; &lt;span class="n"&gt;WINAPI&lt;/span&gt; &lt;span class="nf"&gt;ThreadProc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_In_&lt;/span&gt; &lt;span class="n"&gt;LPVOID&lt;/span&gt; &lt;span class="n"&gt;lpParameter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;그리고 우리가 호출하기 원하는 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibrarya"&gt;&lt;code&gt;LoadLibrary()&lt;/code&gt;&lt;/a&gt; 함수는 다음과 같이 생겨있다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;HMODULE&lt;/span&gt; &lt;span class="nf"&gt;LoadLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;LPCTSTR&lt;/span&gt; &lt;span class="n"&gt;lpLibFileName&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;리턴 값은 일단 무시하고, Win32API이므로 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibrarya"&gt;&lt;code&gt;LoadLibrary()&lt;/code&gt;&lt;/a&gt; 함수도 저기엔 생략되어 있지만 calling convension은 &lt;code&gt;__stdcall&lt;/code&gt; 형식을 따른다. 그리고 패러미터로는 동일하게 포인터 형식의 패러미터 하나만을 갖는다. 그렇다면 스레드를 생성하면서 그 시작 주소를 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibrarya"&gt;&lt;code&gt;LoadLibrary()&lt;/code&gt;&lt;/a&gt;로 지정하면 원하는 DLL을 로드할 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;이 때 패러미터로 원하는 DLL 파일의 위치를 지정해주어야 하는데, 무턱대고 스트링을 패러미터로 주게 되면 반드시 실패하게 된다. 원인은 간단한데, 스레드를 생성할 때 패러미터를 넘겨주기는 하는데 주소값만 넘어가기 때문이다. 같은 프로세스상에서 작업하고 있다면 메모리 주소공간이 동일하니 부모 스레드에서 보고 있는 주소와 생성된 스레드에서 보는 주소가 같은 물리적 위치를 가리키고 있을 것이기 때문에 문제가 되지 않지만, 지금처럼 다른 프로세스에 스레드를 생성하게 된다면 부모 스레드가 보고 있는 주소값을 대상 프로세스에서 생성된 스레드가 접근해 보면 전혀 엉뚱한 값이 써져 있는 사태가 벌어지게 된다.&lt;/p&gt;
&lt;p&gt;이를 해결하려면 두 가지 방법이 있는데, 하나는 대상 프로세스의 메모리 어딘가에 써진 문자열을 이용하는 방법이 되겠다. 예를 들어 모든 윈도우즈 프로그램은 &lt;code&gt;kernel32.dll&lt;/code&gt; 파일을 불러와야 하기 때문에 프로그램의 어딘가에는 &lt;code&gt;kernel32.dll&lt;/code&gt; 이라는 스트링이 박혀 있을 것이다. 집어넣으려는 dll 파일의 이름이 &lt;code&gt;ernel32.dll&lt;/code&gt;이라면, &lt;code&gt;kernel32.dll&lt;/code&gt;이 써진 주소에서 한 바이트만 밀어서 넘겨주게 되면, &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibrarya"&gt;&lt;code&gt;LoadLibrary()&lt;/code&gt;&lt;/a&gt; 함수의 패러미터로 &lt;code&gt;ernel32.dll&lt;/code&gt;을 전달할 수 있을 것이다.
다른 한 가지는 대상 프로세스 내에 메모리를 &lt;a href="https://msdn.microsoft.com/ko-kr/library/windows/desktop/aa366890(v=vs.85).aspx"&gt;&lt;code&gt;VirtualAllocEx()&lt;/code&gt;&lt;/a&gt;를 이용해 할당받은 뒤, 그 메모리에 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/debug/debugging-functions"&gt;Debugging API&lt;/a&gt;중 하나인 &lt;a href="https://msdn.microsoft.com/en-us/library/ms681674(v=VS.85).aspx"&gt;&lt;code&gt;WriteProcessMemory()&lt;/code&gt;&lt;/a&gt;를 이용해 원하는 DLL의 주소를 써넣어서 쓰는 것이다. 좀 더 유연한 대응을 할 수 있지만, &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/debug/debugging-functions"&gt;Debugging API&lt;/a&gt;를 사용해야 하기 때문에 권한의 문제가 있다. 하지만 후자가 일반적인 경우 더 단순하므로 후자를 택하도록 하자.&lt;/p&gt;
&lt;p&gt;위의 문제를 해결하는 것을 제대로 이해했다면 여기서 의문점이 하나 생길텐데, &lt;strong&gt;대상 프로세스의 LoadLibrary 주소는 어떻게 알 수 있는가?&lt;/strong&gt; 하는 문제가 생긴다. 여기서 윈도 DLL의 특징이 하나 나타나는데, 바로 &lt;em&gt;선호하는 Base Address&lt;/em&gt;라는 특성이다.
윈도의 DLL은 성능 최적화를 위해서 선호하는 Base Address라는 것이 존재한다. 특별한 일이 없다면 DLL이 로드될 때는 내가 미리 지정한 주소에 로드되도록 하는 것인데, 이는 base address가 변경되면 DLL을 불러올 때 절대주소로 참조한 주소들을 전부 고쳐 써야 정상 동작을 하게 될 것이기 때문에 반드시 고쳐쓰는 작업이 들어가게 되고, 이는 엄청난 오버헤드로 작용한다. 거기에 특별히, 몇몇 핵심 DLL은 커널과도 연관성이 있어 주소가 바뀌면 절대로 안되는 DLL들이 있다. &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibrarya"&gt;&lt;code&gt;LoadLibrary()&lt;/code&gt;&lt;/a&gt;는 바로 그 DLL중 하나인 &lt;code&gt;kernel32.dll&lt;/code&gt;의 함수이기 때문에 내 프로세스에 있는 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibrarya"&gt;&lt;code&gt;LoadLibrary()&lt;/code&gt;&lt;/a&gt; 함수의 주소와 대상 프로세스에 있는 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibrarya"&gt;&lt;code&gt;LoadLibrary()&lt;/code&gt;&lt;/a&gt; 함수의 주소는 동일하다고 봐도 무방하기 때문에 패러미터를 준비하는 것보다 훨씬 쉽다.&lt;/p&gt;
&lt;p&gt;무사히 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibrarya"&gt;&lt;code&gt;LoadLibrary()&lt;/code&gt;&lt;/a&gt;가 호출되었으면 DLL의 &lt;code&gt;DllMain()&lt;/code&gt; 함수가 호출된다. 이를 이용해 내가 원하는 작업들을 해주면, &lt;em&gt;재밌는&lt;/em&gt; 일을 하기 위한 준비가 끝난다.&lt;/p&gt;
&lt;h2 id="x86_64wow64-problem"&gt;x86_64/WOW64 Problem&lt;/h2&gt;
&lt;p&gt;앞에서 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibrarya"&gt;&lt;code&gt;LoadLibrary()&lt;/code&gt;&lt;/a&gt;의 주소가 프로세스마다 모두 동일하다고 주장했는데, 사실 그렇지가 못하다. 물론 32bit OS에서는 저 것이 참이지만, 64bit OS부터는 미묘한 문제가 있어 신경쓸 필요가 있다.
윈도는 특이하게도, 64bit OS상에서도 32bit executable을 실행하는데 문제가 거의 없다. 리눅스의 경우 i386용 라이브러리들을 깔아주지 않으면 x64 어플리케이션만 작동하고 i386은 작동할 수 없는데 비해, 윈도는 i386용 라이브러리들이 이미 OS에 다 포함되어 있어 특별한 조작 없이 i386 바이너리를 실행할 수 있다.
별 문제 없이 i386과 x86_64 바이너리가 잘 동작하기 때문에 놓치기 쉬운데, DLL이 실행가능한 코드를 담고 있기 때문에 작은 문제가 하나 생긴다. i386과 x64가 아무리 호환이 된다고 해도 그냥 단순히 섞어놓을 수 있는 것은 아니어서, x64용 DLL과 i386용 DLL이 서로 교차사용이 불가능하다. 즉, x64용 바이너리에서 실행중인 i386 바이너리의 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibrarya"&gt;&lt;code&gt;LoadLibrary()&lt;/code&gt;&lt;/a&gt;의 주소를 얻는 일이 단순하게 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-getprocaddress"&gt;&lt;code&gt;GetProcAddress()&lt;/code&gt;&lt;/a&gt; 만으로 해결되지 않는다.&lt;/p&gt;
&lt;p&gt;이를 해결하기 위해서는 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/psapi/nf-psapi-enumprocessmodulesex"&gt;&lt;code&gt;EnumProcessModulesEx()&lt;/code&gt;&lt;/a&gt; 함수를 통해 &lt;code&gt;kernel32.dll&lt;/code&gt;의 Base Address를 얻어내고, &lt;code&gt;kernel32.dll&lt;/code&gt;에서 PE 파일을 분석해 Export Address Table로부터 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibrarya"&gt;&lt;code&gt;LoadLibrary()&lt;/code&gt;&lt;/a&gt;의 시작 주소를 얻어와야 할 필요가 있다. 이걸 다 다루면 포스팅이 너무 길어지므로, 다음으로 미루도록 하겠다.&lt;/p&gt;
&lt;h2 id="result"&gt;Result&lt;/h2&gt;
&lt;p&gt;수 년 전에 짰던 코드인데, 요즘도 큰 문제 없이 동작하는 코드이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;hProc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;OpenProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PROCESS_ALL_ACCESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dwPID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hProc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;MessageBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hWnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Failed to inject DLL!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Cannot open the process&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MB_ICONERROR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;pRemoteDll&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;VirtualAllocEx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hProc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;MAX_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MEM_COMMIT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PAGE_READWRITE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;pRemoteDll&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;CloseHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hProc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;MessageBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hWnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Failed to inject DLL!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Cannot allocate memory in target process&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MB_ICONERROR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;WriteProcessMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hProc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pRemoteDll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;szDllName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;szDllName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;VirtualFreeEx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hProc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pRemoteDll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MEM_RELEASE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;CloseHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hProc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;MessageBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hWnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Failed to inject DLL!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Cannot write memory in target process&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MB_ICONERROR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;hKernel32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;LoadLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;KERNEL32.DLL&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;pfnLoadLibrary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LPTHREAD_START_ROUTINE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;GetProcAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hKernel32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;LoadLibraryA&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;hThread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateRemoteThread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hProc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pfnLoadLibrary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pRemoteDll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;hThread&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;VirtualFreeEx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hProc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pRemoteDll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MEM_RELEASE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;CloseHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hProc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;FreeLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hKernel32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;MessageBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hWnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Failed to inject DLL!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Cannot create remote thread&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MB_ICONERROR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;WaitForSingleObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hThread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INFINITE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;VirtualFreeEx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hProc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pRemoteDll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MEM_RELEASE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;CloseHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hProc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;FreeLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hKernel32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;GetExitCodeThread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hThread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dwRet&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;TCHAR&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;_stprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Injection finished.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Exit code: 0x%08lX&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;dwRet&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;MessageBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hWnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Finished&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;MB_ICONINFORMATION&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;순서대로&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;dwPID를 PID로 가진 프로세스를 열고 (&lt;code&gt;OpenProcess()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;해당 프로세스에 DLL 주소를 적어넣을 메모리를 할당하고 (&lt;code&gt;VirtualAllocEx()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;DLL 주소를 써넣은 다음 (&lt;code&gt;WriteProcessMemory()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LoadLibraryA()&lt;/code&gt; 함수의 주소를 얻어오고 (&lt;code&gt;GetProcAddress()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LoadLibraryA()&lt;/code&gt;를 스레드 프로시저로 하는 스레드를 생성한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 코드에서는 32/64bit 문제를 건드리지 않았기 때문에 32/64bit 각각 버전으로 빌드를 따로해야된다.
참고로 스트링을 받는 Win32API 함수는 대부분 A버전과 W버전 두 가지가 있고 UNICODE가 정의됐는지 여부에 따라 A나 W로 define되어있는 A/W가 붙지 않은 함수가 존재한다. A/W가 붙지 않은 버전은 매크로로 구현되어 있기 때문에 실제 symbol이 없어서 이 때는 A가 붙은 것으로 제한해서 사용했다.&lt;/p&gt;
&lt;h3 id="injecting-dll"&gt;Injecting Dll&lt;/h3&gt;
&lt;p&gt;실행시키고 싶은 코드는 Dll의 형태로 만들어서 &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibrarya"&gt;&lt;code&gt;LoadLibrary()&lt;/code&gt;&lt;/a&gt; 함수를 통해 대상 프로세스에서 로딩시킬 것이다. 앞에서 언급했듯, &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibrarya"&gt;&lt;code&gt;LoadLibrary()&lt;/code&gt;&lt;/a&gt;가 호출되면 &lt;code&gt;DllMain()&lt;/code&gt;이 호출되는데, 이 함수는 다음 형태처럼 생겨야 한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;BOOL&lt;/span&gt; &lt;span class="n"&gt;WINAPI&lt;/span&gt; &lt;span class="nf"&gt;DllMain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_In_&lt;/span&gt; &lt;span class="n"&gt;HINSTANCE&lt;/span&gt; &lt;span class="n"&gt;hinstDLL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_In_&lt;/span&gt; &lt;span class="n"&gt;DWORD&lt;/span&gt; &lt;span class="n"&gt;fdwReason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_In_&lt;/span&gt; &lt;span class="n"&gt;LPVOID&lt;/span&gt; &lt;span class="n"&gt;lpvReserved&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;이 함수는 dll이 적재되는 시점 뿐만 아니라 여러 시점에서 호출되는데, 실행시키고 싶은 코드를 여기다 넣게 되면 injection과 동시에 실행이 돼서 좋기는 하지만 저 모든 시점에서 실행되기 때문에 잘 구분할 필요가 있다.
각 시점은 &lt;code&gt;fdwReason&lt;/code&gt; 패러미터를 통해 구분할 수 있다. 이 중 처음 적재될 때는 &lt;code&gt;DLL_PROCESS_ATTACH&lt;/code&gt; 값을 가지므로, 이 때만 실행되게 하면 되겠다. 이 함수가 &lt;code&gt;FALSE&lt;/code&gt;를 반환하면 적재 도중 오류가 발생한 것으로 취급하여 &lt;code&gt;LoadLibrary()&lt;/code&gt;는 실패하고 0을 반환하게 된다. &lt;code&gt;TRUE&lt;/code&gt;를 반환하면 적재가 성공하여 대상 프로세스에 dll이 성공적으로 안착하게 된다.
여기서 두 가지 활용방안이 있는데, 첫째는 교과서적으로 &lt;code&gt;TRUE&lt;/code&gt;를 반환하여 성공적으로 로드되는 것이다. 이 때 장점은 dll이 성공적으로 프로세스에 로딩되어 지속적인 효과를 줄 수 있다는 것이고, 단점은 dll이 이미 로드된 상태라 다시 injection을 할 수 없다는 것과, dll이 정상적인 방법으로 로딩된 것이 아니다보니, 대상 프로세스가 죽을 때까지 내려오지 않아 dll을 수정하는 것이 불가능하다는 것이다. 이 것은 injection될 dll을 계속 수정하면서 작업해야 할 때 치명적인 문제가 될 수 있는데, 물론 &lt;code&gt;LoadLibrary()&lt;/code&gt;를 사용했던 것과 비슷한 방법으로 &lt;code&gt;FreeLibrary()&lt;/code&gt;를 remote thread로 박아넣어 언로드시킬 수도 있다. 그러나 이 때는 언로드될 수 있도록 리소스를 비롯한 많은 것들을 원상복구하는 것이 반드시 필요하며, 적재가 해제될 때 &lt;code&gt;DLL_PROCESS_DETACH&lt;/code&gt;로 호출되는 &lt;code&gt;DllMain()&lt;/code&gt;에서 잘 관리해줘야만 한다. 이 때 되돌리는 것을 제대로 하지 않으면 &lt;code&gt;FreeLibrary()&lt;/code&gt;가 실패하거나 대상 프로세스가 죽어버릴 수도 있으므로 조심해서 자원들을 풀어주도록 하자.
두 번째는 &lt;code&gt;FALSE&lt;/code&gt;를 의도적으로 리턴하여 dll이 적재되지 않게 할 수도 있다. 코드는 실행되었지만 실제로 dll이 로드된 것은 아니므로, 여러번 다시 inject가 가능하다. 다만 &lt;code&gt;DllMain()&lt;/code&gt;이 호출된 상태는 &lt;code&gt;LoadLibrary()&lt;/code&gt;가 실행중인 상태이므로 여러 가지 제약사항이 있는 상태이다. &lt;code&gt;LoadLibrary()&lt;/code&gt; 중에 또 &lt;code&gt;LoadLibrary()&lt;/code&gt;를 호출하게 됐을 경우, MSDN에서는 데드락이 발생할 수 있으므로 절대 하지 말라고 경고되어 있다. 그 외에도 정상적인 상태라 할 수 없어 간단한 코드 정도는 그렇게 실행할 수 있겠지만, 좀 복잡해지거나 지속시간을 가져야 하는 경우라면 스레드를 따로 생성해서 거기서 처리하는 것을 추천한다. 물론 이 경우는 &lt;code&gt;FALSE&lt;/code&gt;를 일부러 리턴해 강제로 로딩을 막는 방법은 사용할 수 없을 것이다.&lt;/p&gt;
&lt;p&gt;예시는 다음과 같다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;windows.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;BOOL&lt;/span&gt; &lt;span class="n"&gt;WINAPI&lt;/span&gt; &lt;span class="nf"&gt;DllMain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HINSTANCE&lt;/span&gt; &lt;span class="n"&gt;hInstDll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DWORD&lt;/span&gt; &lt;span class="n"&gt;fdwReason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LPVOID&lt;/span&gt; &lt;span class="n"&gt;lpvReserved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fdwReason&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;DLL_PROCESS_ATTACH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;MessageBoxA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;Hello, world!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;I&amp;#39;m Injected&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MB_OK&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="future-work"&gt;Future Work&lt;/h2&gt;
&lt;p&gt;32/64bit 문제를 해결하는 길은 또 DLL Injection 포스팅 전체 내용만큼이나 길 것 같아 다음 포스팅으로 미루도록 하겠다.
그리고 추후에 DLL Injection을 통한 쿠키 공유 시스템(Internet Explorer의 쿠키를 뽑아 다른 브라우저에 이식하는)에 대해 정리할 예정이다.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;DLL Injection은 상당히 강력한 도구 중 하나이다. &lt;code&gt;CreateRemoteThread()&lt;/code&gt; API를 이용하면 DLL Injection을 간단하게 수행해볼 수 있다.&lt;/p&gt;</description></item><item><title>cffi를 써 봅시다</title><link>https://gwangyi.github.io/posts/introduce-cffi/</link><pubDate>Sat, 08 Dec 2018 01:01:00 +0000</pubDate><guid>https://gwangyi.github.io/posts/introduce-cffi/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Python이 강력한 언어이긴 하지만, python만으로 모든 문제를 해결하기는 어렵다. Python의 기준 구현체인 CPython은 C/C++이나 Go 처럼 네이티브 코드를 생성하는 것이 아니라 바이트코드를 생성한 뒤 이를 하나하나 해석하는 인터프리터라 속도가 느리기도 하고, 기존에 python으로 구현되지 않은 라이브러리를 이용해야 할 경우도 종종 발생한다.&lt;/p&gt;
&lt;p&gt;일반적인 경우, 속도가 필요한 부분은 C나 어셈블리어 등을 이용하여 작성하고, 비교적 속도에 민감하지 않은 부분은 생산성이 높은 고수준 언어를 이용해서 작성한 뒤 이를 이어붙이는 기법을 많이 사용한다. 대부분의 고수준 언어는 그 자체가 C/C++로 구현된 경우가 많아, 전체 프로젝트에 해당 언어의 처리 엔진을 심어버리는 방식으로 처리하기도 하지만, 반대로 C로 작성된 모듈을 해당 고수준 언어로 작성된 함수인 것처럼 이어붙이는 기능을 제공하는 경우도 있다.&lt;/p&gt;
&lt;p&gt;때로는 HW를 세세하게 조작하고 싶을 때도 있을 것이다. 마침 나도 컴퓨터 부품을 만드는 회사를 다니는지라, HW를 직접 조작한다고 말하기에는 많이 부족하지만 어쨌든 커널과 이야기해야 할 일이 많다. 물론 이런 부분들도 python만 이용해서 어떻게 해볼 수도 있지만, 프로그래밍 세계에서 매우 중요한 격언인 “&lt;em&gt;바퀴의 재발명&lt;/em&gt;”을 피하기 위해서라도 C로 작성된 모듈을 가져다 쓰는게 효율적인 해답이 되는 경우가 많다.&lt;/p&gt;
&lt;p&gt;이러한 &lt;strong&gt;이종 언어간 인터페이스&lt;/strong&gt;를 이용해 둘 이상의 언어를 이어붙이면, 하나의 언어로는 표현하기 힘든 부분까지도 표현할 수 있게 돼 프로그래밍 언어의 표현력을 한층 상승시킬수 있게 된다. 이번 포스팅에서는 그 중에서도 python과 C 언어를 이어붙일 수 있도록 도와주는 &lt;a href="https://cffi.readthedocs.io/en/latest/"&gt;cffi&lt;/a&gt;에 대해 소개하고자 한다.&lt;/p&gt;
&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;이종 언어간 인터페이스&lt;/strong&gt;는 대부분의 고수준 언어들이 고민하는 문제인 바, &lt;a href="https://en.wikipedia.org/wiki/Foreign_function_interface"&gt;FFI&lt;/a&gt;라는 개념으로 정의되어 있다. 단어나 개념만 두고 봤을 때는 서로 다른 두 언어를 이어붙이기만 한다면 다 &lt;a href="https://en.wikipedia.org/wiki/Foreign_function_interface"&gt;FFI&lt;/a&gt;라 부를 수 있겠지만, 보편적으로는 어떤 언어와 C 언어 사이의 인터페이스로 정의되곤 한다. C 자체가 어셈블리에 한없이 가까운 &amp;ndash; 누군가의 표현에 따르면 &lt;em&gt;함수 기능이 잘 지원되는 어셈블리어&lt;/em&gt; &amp;ndash; 언어인지라 C에서 쓰이기 위해 준비되어야 하는 요소들이 다른 언어들에 비해 매우 적어서인 듯하다. 마치 현실에서의 영어와도 비슷한데, 전 세계 사람들이 영어를 배우기 때문에 독일인과 한국인이 만나 이야기할 때도 영어로 대화하는 것과 비슷하다고 할 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;물론 C를 중간 언어로 두지 않고 직접 이어보려는 시도도 있는데, &lt;a href="http://www.swig.org"&gt;SWIG&lt;/a&gt; 같은 것을 들 수 있을 것이다. 완전히 C를 배제하는 것은 아니지만, 별도의 간결한 언어로 인터페이스를 작성하여 연결하는 부분을 자동화 함으로써 직접 C를 건드리는 것을 피할 수 있도록 해 주었다. 현실에서의 예를 들자면 에스페란토어와 같은 인공어를 들 수 있을 것인데, 다른 언어끼리의 소통을 위해 중립적인 언어를 새로 디자인했다는 점에서 비슷하다.&lt;/p&gt;
&lt;p&gt;Python에서는 &lt;a href="https://docs.python.org/3/library/ctypes.html"&gt;ctypes&lt;/a&gt; 모듈이 표준 라이브러리에 포함되어 &lt;a href="https://en.wikipedia.org/wiki/Foreign_function_interface"&gt;FFI&lt;/a&gt;를 사용할 수 있도록 되어 있다. &lt;a href="https://docs.python.org/3/library/ctypes.html"&gt;ctypes&lt;/a&gt;는 상당히 훌륭한 &lt;a href="https://en.wikipedia.org/wiki/Foreign_function_interface"&gt;FFI&lt;/a&gt; 모듈이기는 하나, 성능 오버헤드가 조금 있고 사용하기가 다소 불편한 감이 있다.
&lt;a href="https://docs.python.org/3/library/ctypes.html"&gt;ctypes&lt;/a&gt;를 이용하기 위해서는,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;먼저 C로 작성된 코드가 shared object나 DLL의 형태로 컴파일되어 있어야 하고,&lt;/li&gt;
&lt;li&gt;함수와 구조체 등을 &lt;a href="https://docs.python.org/3/library/ctypes.html"&gt;ctypes&lt;/a&gt;에 맞는 방식으로 다시 정리해야되며,&lt;/li&gt;
&lt;li&gt;OS에서 제공하는 symbol lookup 방식으로 찾을 수 있는 요소에 한해서만 접근이 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;첫 번째는 빌드 스크립트 등으로 어떻게든 되는 문제이긴 하지만, 두 번째는 작은 규모의 프로젝트라면 몰라도 많은 수의 함수를 가져와야 되면 손이 매우 많이 가는 일이 되며, 세 번째는 상수 매크로나 간단한 매크로 함수, inline 함수 등은 아예 사용이 불가능하다는 것을 의미하게 된다.&lt;/p&gt;
&lt;p&gt;이 세 가지의 문제를 한번에 해결시켜 줄 수 있는 것이 &lt;a href="https://cffi.readthedocs.io/en/latest/"&gt;cffi&lt;/a&gt;이다.&lt;/p&gt;
&lt;h2 id="calling-c-from-python"&gt;Calling C from Python&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://cffi.readthedocs.io/en/latest/"&gt;cffi&lt;/a&gt;의 사용법은 매우 직관적이다. 물론 C를 배운적이 없고 python만 사용해 본 사람이라면 &lt;a href="https://docs.python.org/3/library/ctypes.html"&gt;ctypes&lt;/a&gt;가 더 직관적일 수도 있다. &lt;a href="https://cffi.readthedocs.io/en/latest/"&gt;cffi&lt;/a&gt;가 직관적이라는 부분은, C의 문법으로 C 확장을 정의할 수 있다는 점에서 그렇다. 예를 들어 &lt;code&gt;puts()&lt;/code&gt; 함수를 &lt;a href="https://cffi.readthedocs.io/en/latest/"&gt;cffi&lt;/a&gt;로 가져다 쓰려면 다음과 같이 작성하면 된다.&lt;/p&gt;
&lt;p&gt;모듈 정의:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;cffi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FFI&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ffi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FFI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mylib&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;#include &amp;lt;stdio.h&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cdef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;void puts(const char *);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;사용:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;mylib&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;mylib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Hello, world!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;a href="https://cffi.readthedocs.io/en/latest/"&gt;cffi&lt;/a&gt;도 &lt;a href="https://docs.python.org/3/library/ctypes.html"&gt;ctypes&lt;/a&gt;처럼 이미 빌드된 shared object로부터 runtime binding을 통해 함수 등을 가져올 수도 있지만, 그렇게 쓴다면 굳이 &lt;a href="https://cffi.readthedocs.io/en/latest/"&gt;cffi&lt;/a&gt;를 쓸 필요성이 없다고 개인적으로 생각하기 때문에 그 방법에 대해서는 따로 기술하지 않도록 하겠다. 궁금하다면 &lt;a href="https://cffi.readthedocs.io/en/latest/overview.html#simple-example-abi-level-in-line"&gt;해당 문서 자료&lt;/a&gt;를 참조해서 보라.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;set_source()&lt;/code&gt; 함수의 첫 번째 인자는 &lt;code&gt;import&lt;/code&gt;를 통해 가져올 모듈의 주소가 된다. 패키지 안으로 넣고 싶다면 &lt;code&gt;'some.package.module'&lt;/code&gt;과 같이 &lt;code&gt;.&lt;/code&gt;으로 구분하면 이에 맞추어 디렉토리 구조를 만들면서 모듈을 생성하게 된다. 두 번째 인자는 모듈 안에 넣고자 하는 C 코드를 넣으면 된다. 여기서 정의된 함수나 전역변수 등을 나중에 꺼내 쓸 수 있다. 여기서는 생략되어 있지만 &lt;code&gt;libraries&lt;/code&gt; 패러미터로 추가 라이브러리(&lt;code&gt;-lsomething&lt;/code&gt; 등으로 지정하는 라이브러리)를 지정하거나, &lt;code&gt;library_dir&lt;/code&gt;, &lt;code&gt;include_dir&lt;/code&gt; 옵션을 통해 library directory나 include directory를 지정해 넣을 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cdef()&lt;/code&gt; 함수에서는 python에서 가져다 쓰고 싶은 함수, 전역변수 등을 C 언어 문법 그대로 작성해 넣는다. &lt;code&gt;typedef&lt;/code&gt;나 &lt;code&gt;struct&lt;/code&gt;, &lt;code&gt;union&lt;/code&gt; 등도 여기다 넣으면 나중에 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cdef()&lt;/code&gt;의 재미있는 점은, 여기서는 C 확장 모듈의 인터페이스를 정의하는 것이고, 실제 함수나 전역변수 등은 &lt;code&gt;set_source()&lt;/code&gt;에서 처리해야한다는 점이다. 다르게 말하면, 실제로는 함수나 전역변수가 아니더라도, 함수나 전역변수처럼 사용할 수 있다면 &lt;code&gt;cdef()&lt;/code&gt;에서 함수나 전역변수인 것처럼 정의해줌으로써 python에서 함수나 전역변수의 형태로 사용할 수 있다는 것이다. 이렇게 길게 설명하니 말이 어려운데, 예를 들자면 매크로 상수처럼 실제로 변수가 아닌 것도 마치 전역변수인 것마냥 외부로 노출시켜 python에서 가져다 쓸 수 있도록 하는 것이 가능하다는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cdef()&lt;/code&gt;의 다른 특징 하나는 여러번 호출이 가능하다는 것이다. 여러번 호출하면 이전 호출이 사라지는 것은 아니고 정의들이 누적이 된다.&lt;/p&gt;
&lt;p&gt;이렇게 정의된 모듈은 &lt;code&gt;compile()&lt;/code&gt; 함수를 통해서 실제로 python에서 가져다 쓸 수 있는 C extension 모듈로 컴파일된다. 컴파일 된 후에는 해당 모듈은 &lt;code&gt;ffi&lt;/code&gt; 객체와 &lt;code&gt;lib&lt;/code&gt; 객체를 노출하는데, &lt;code&gt;lib&lt;/code&gt; 객체를 통해 &lt;code&gt;cdef()&lt;/code&gt;에서 노출시킨 함수와 전역변수등을 접근하는 것이 가능하다. &lt;code&gt;ffi&lt;/code&gt; 객체는 &lt;code&gt;memmove&lt;/code&gt; 등의 C 메모리를 접근하는 유틸리티 함수와 자료형에 맞는 메모리를 할당받는 &lt;code&gt;new&lt;/code&gt; 등의 함수를 가지고 있다. 참고로 할당받은 메모리를 해제하는 함수는 따로 없는데, GC에서 해당 객체를 수집할 때 삭제하도록 되어 있기 때문이다.&lt;/p&gt;
&lt;h3 id="macro-constant--macro-function"&gt;Macro constant / Macro function&lt;/h3&gt;
&lt;p&gt;앞서 &lt;code&gt;cdef()&lt;/code&gt;에서 매크로도 노출시킬 수 있다고 했었다. 전역 변수를 노출시키게 되면 &lt;code&gt;setter&lt;/code&gt;와 &lt;code&gt;getter&lt;/code&gt;를 통해서 노출된 전역 변수를 읽어오거나 바꿀 수 있다. 하지만 Macro constant는 상수로 런타임에 그 값이 바뀔 수 없기 때문에, &lt;code&gt;const&lt;/code&gt; 한정자를 붙여 정의해줌으로써 가져다 쓸 수 있게 된다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my_lib&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;#define VALUE 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;#define STR &amp;#34;Hello!&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;```)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;ffi.cdef(&amp;#39;const int VALUE; const char * STR;&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;매크로 함수도 일반적인 함수처럼 사용이 가능하다면 마찬가지로 &lt;code&gt;cdef()&lt;/code&gt;를 통해 외부에 노출시켜줄 수 있다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my_lib&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;#define MIN(a, b) ((a) &amp;lt; (b) ? (a) : (b))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;```)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;ffi.cdef(&amp;#39;const int MIN(const int a, const int b)&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="memory-allocation"&gt;Memory allocation&lt;/h3&gt;
&lt;p&gt;C 자료형을 다루고자 할 때에는 &lt;code&gt;ffi.new()&lt;/code&gt; 함수를 사용하면 된다. 특이한 점은, 생성할 자료형을 지정할 때 C 문법을 사용한다는 것이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cdef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;struct happy {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; int score;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; int reason;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;happy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;struct happy *&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;happy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;happy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;buff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;char [1024]&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;buff&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x41&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;buff&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x42&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;buff&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buff&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ==&amp;gt; b&amp;#39;AB&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="calling-python-from-c"&gt;Calling Python from C&lt;/h2&gt;
&lt;p&gt;물론 C로 작성된 코드에서 python 함수를 호출하고 싶을 때도 있을 것이다. python 함수를 C 함수로 싸 주는 래퍼를 실시간으로 생성할 수 있다면 제일 좋겠지만, &lt;a href="https://cffi.readthedocs.io/en/latest/"&gt;cffi&lt;/a&gt;는 그 방법을 deprecate 시켜버리고 대신 &lt;code&gt;extern &amp;quot;Python&amp;quot;&lt;/code&gt;이라는 방법을 이용하여 감싸고 있다.&lt;/p&gt;
&lt;p&gt;미리 특정 이름을 가진 함수를 만들어 준비시켜 놓고, python에서 그 이름의 함수를 구현하여 이를 &lt;a href="https://cffi.readthedocs.io/en/latest/"&gt;cffi&lt;/a&gt;를 통해 연결시켜주면, C 함수가 마치 지정한 Python 함수인 것처럼 동작하도록 지정할 수 있다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my_lib&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;extern int g(int a);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;int f(int a) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; return g(a) + 5;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ffi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cdef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;extern &amp;#34;Python&amp;#34; int g(int a);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;int f(int a);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@ffi.def_extern&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;g&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;좀 더 자세한 자료는 &lt;a href="https://cffi.readthedocs.io/en/latest/using.html#extern-python-new-style-callbacks"&gt;Extern &amp;ldquo;Python&amp;rdquo; (new-style-callbacks)&lt;/a&gt; 문서를 참고하라.&lt;/p&gt;
&lt;h3 id="embedding"&gt;Embedding&lt;/h3&gt;
&lt;p&gt;단순한 callback으로만 사용한다면 C에서 Python 코드를 호출한다는 것이 그다지 큰 의미를 갖지 못할 수도 있다. 아예 python 해석기와 python 코드를 shared object로 감싸버려서, 다른 프로그램이 C 함수를 호출하듯이 python 함수를 호출할 수 있게 노출시킬 수도 있다.&lt;/p&gt;
&lt;p&gt;이 embedding은 &lt;a href="https://cffi.readthedocs.io/en/latest/overview.html#embedding"&gt;공식 문서&lt;/a&gt;에서 간결하고 단순하게 잘 표현해 놓았기 때문에 공식 문서를 한번만 읽어도 감을 잡을 수 있을 것이라고 기대한다.&lt;/p&gt;
&lt;p&gt;Embedding은 매우 강력한 기능인데, python 코드를 SystemVerilog의 DPI 인터페이스를 통해 SystemVerilog 시뮬레이터에 물려버릴 수도 있다.&lt;/p&gt;
&lt;h2 id="cooperation-with-setuptools"&gt;Cooperation with setuptools&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;compile()&lt;/code&gt; 함수를 통해 모듈을 컴파일 할 수도 있지만, &lt;a href="https://cffi.readthedocs.io/en/latest/"&gt;cffi&lt;/a&gt;의 진가는 &lt;a href="https://setuptools.readthedocs.io/en/latest/"&gt;setuptools&lt;/a&gt;와의 협업에도 있다. ffi object를 만들고 설정해주는 &lt;code&gt;.py&lt;/code&gt; 파일을 만든 뒤 &lt;code&gt;compile()&lt;/code&gt; 호출을 빼 놓자. 그 뒤에 &lt;code&gt;setup.py&lt;/code&gt;에 &lt;code&gt;setup_requires&lt;/code&gt; 항목으로 &lt;code&gt;cffi&amp;gt;=1.0.0&lt;/code&gt;을 추가해주고 &lt;code&gt;cffi_modules&lt;/code&gt; 항목으로 &lt;code&gt;ffi_builder.py:ffi_object&lt;/code&gt; 와 같이 만든 &lt;code&gt;.py&lt;/code&gt; 파일과 ffi object 이름을 넣어주면 &lt;code&gt;python setup.py&lt;/code&gt; 명령으로 패키징하거나 설치할 때 자동으로 &lt;code&gt;cffi&lt;/code&gt; 모듈을 컴파일해 줄 것이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;setuptools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;setup_requires&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;cffi&amp;gt;=1.0.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cffi_modules&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;piapprox_build:ffibuilder&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# &amp;#34;filename:global&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;install_requires&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;cffi&amp;gt;=1.0.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;install_requires&lt;/code&gt;가 들어간 이유는, &lt;code&gt;setup_requires&lt;/code&gt;는 패키지를 빌드할 때만 사용하고 설치가 끝나면 지워지기 때문에, 설치한 후로도 계속해서 의존할 라이브러리는 &lt;code&gt;install_requires&lt;/code&gt;에 따로 추가해야 하기 때문이다.&lt;/p&gt;
&lt;h2 id="future-work"&gt;Future work&lt;/h2&gt;
&lt;p&gt;Python으로 구현된 C preprocessor인 &lt;a href="https://ned14.github.io/pcpp/"&gt;pcpp&lt;/a&gt;를 소개하고자 한다. &lt;a href="https://cffi.readthedocs.io/en/latest/"&gt;cffi&lt;/a&gt;와 조합하면 &lt;code&gt;cdef()&lt;/code&gt;에 전달할 함수 정의 목록을 좀 더 쉽게 얻을 수 있다.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://cffi.readthedocs.io/en/latest/"&gt;cffi&lt;/a&gt;는 C 함수나 변수를 python C 확장의 형태로 감싸주거나, python 함수를 C 함수로 감싸주는 작업을 자동화해주는 편리한 &lt;a href="https://en.wikipedia.org/wiki/Foreign_function_interface"&gt;FFI&lt;/a&gt; 라이브러리이다. 게다가, C 언어의 문법을 그대로 사용하여 인터페이스를 정의할 수 있어 어렵지 않게 사용법을 익힐 수 있다. 이를 이용하면 python으로 작성 가능한 프로그램의 표현 범위를 크게 늘릴 수 있다.&lt;/p&gt;</description></item><item><title>zplug 소개</title><link>https://gwangyi.github.io/posts/introduce-zplug/</link><pubDate>Sat, 01 Dec 2018 12:01:00 +0000</pubDate><guid>https://gwangyi.github.io/posts/introduce-zplug/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;일반적으로 리눅스 환경을 처음 접하게 되면 &lt;a href="https://www.gnu.org/software/bash/"&gt;bash&lt;/a&gt; 셸을 접하는 경우가 대다수이다. 좀 오래된 환경을 사용한다면 가뭄에 콩나듯 &lt;a href="https://en.wikipedia.org/wiki/C_shell"&gt;csh&lt;/a&gt;을 볼 수 있는데, 최근에 csh을 접했다면 실제로는 &lt;a href="https://github.com/tcsh-org/tcsh"&gt;tcsh&lt;/a&gt;일 가능성이 높다. 이러한 셸들은 오래 널리 사용되어 왔기 때문에 방대한 양의 참고자료와 수많은 보조 도구들이 이미 존재하고 있다.&lt;/p&gt;
&lt;p&gt;하지만 그러한 자료들로도 부족함을 느꼈던 사람들은 여러가지 대안 셸들을 제안했고, 그 중 &lt;a href="http://www.zsh.org/"&gt;zsh&lt;/a&gt;는 강력한 interactive 기능과 높은 프로그래머블한 특성을 앞세워 많은 얼리 어답터들을 포섭하는데 성공했다.&lt;/p&gt;
&lt;p&gt;높은 프로그래머블 특성은 &lt;a href="http://www.zsh.org/"&gt;zsh&lt;/a&gt; 위에서 동작하는 수많은 플러그인들을 낳았고, 이런 플러그인을 관리하는 일이 어려워짐에 따라 다양한 플러그인 매니저들이 등장했다. 그 중에서도 이 문서에서는 &lt;a href="https://github.com/zplug/zplug"&gt;zplug&lt;/a&gt;를 소개하고자 한다.&lt;/p&gt;
&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.gnu.org/software/bash/"&gt;bash&lt;/a&gt;의 계보나 &lt;a href="https://github.com/tcsh-org/tcsh"&gt;tcsh&lt;/a&gt;의 계보는 재미있는 옛날 이야기 정도이니 검색해서 다른 곳에서 찾는 것이 빠를 것이라 생각해 생략하도록 하겠다. &lt;a href="https://www.gnu.org/software/bash/"&gt;bash&lt;/a&gt;는 interactive shell에서 강점을 갖고, &lt;a href="https://github.com/tcsh-org/tcsh"&gt;tcsh&lt;/a&gt;는 shell scripting에 강점을 갖고 있다는 특징이 있어 양 쪽의 장점을 취하고자 하는 사람들은 항상 있어왔다.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.zsh.org/"&gt;zsh&lt;/a&gt;는 &lt;a href="https://www.gnu.org/software/bash/"&gt;bash&lt;/a&gt;를 원형으로 삼아 만들어진 셸로, &lt;a href="https://www.gnu.org/software/bash/"&gt;bash&lt;/a&gt;와 호환성이 뛰어나고 다양한 부분을 script를 통해 커스터마이즈할 수 있는 특징이 있다. &lt;a href="https://en.wikipedia.org/wiki/Comparison_of_command_shells"&gt;Shell 별 비교&lt;/a&gt; 페이지를 보면 다른 셸들과 비교한 표를 볼 수 있다.&lt;/p&gt;
&lt;p&gt;아래 내용들은 &lt;a href="https://github.com/unixorn/awesome-zsh-plugins"&gt;awesome-zsh-plugins&lt;/a&gt; 페이지를 참고하여 작성하였다.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.zsh.org/"&gt;zsh&lt;/a&gt;는 다양한 플러그인들이 개발되어 있는데, 이런 쪽으로 제일 기본이 되는 &lt;a href="https://ohmyz.sh/"&gt;oh-my-zsh&lt;/a&gt;는 유용한 플러그인을 모아 번들 형태로 제공하고 있다. 이 후에 나오는 대부분의 플러그인 매니저는 &lt;a href="https://ohmyz.sh/"&gt;oh-my-zsh&lt;/a&gt;는 기본적으로 지원하는 경우가 대부분이다.&lt;/p&gt;
&lt;p&gt;zsh Plugin Manager는 &lt;a href="http://antigen.sharats.me/"&gt;antigen&lt;/a&gt;이 가장 기초가 되는 매니저이다. 많은 플러그인 매니저들이 &lt;a href="http://antigen.sharats.me/"&gt;antigen&lt;/a&gt;의 개선판이라는 이름을 달고서 나오고 있는 실정이다. &lt;a href="http://antigen.sharats.me/"&gt;Antigen&lt;/a&gt; 스스로는 vim+pathogen과 비슷한 컨셉으로 관리한다고 하며, &lt;a href="https://www.vim.org"&gt;vim&lt;/a&gt;에 &lt;a href="https://github.com/VundleVim/Vundle.vim"&gt;Vundle&lt;/a&gt;이 있다면 &lt;a href="http://www.zsh.org/"&gt;zsh&lt;/a&gt;에는 &lt;a href="http://antigen.sharats.me/"&gt;antigen&lt;/a&gt;이 있다고 표현하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/tarjoilija/zgen"&gt;zgen&lt;/a&gt;은 개인적으로 &lt;a href="http://antigen.sharats.me/"&gt;antigen&lt;/a&gt;을 쓰다가 느린 속도 때문에 갈아탔었던 플러그인 매니저이다. 최소한의 오버헤드를 가지도록 설계되어 있으며, 플러그인을 불러오는 모듈을 정적으로 생성해두고 변경이 발생하면 수동으로 업데이트해야되는 구조를 가지고 있다.&lt;/p&gt;
&lt;h2 id="zplug"&gt;Zplug&lt;/h2&gt;
&lt;p&gt;나는 &lt;a href="https://github.com/zplug/zplug"&gt;zplug&lt;/a&gt;를 사용하고 있는데, 사용하게 된 이유는 몇 가지가 있다. &lt;a href="https://github.com/tarjoilija/zgen"&gt;zgen&lt;/a&gt;도 좋기는 하지만, 회사의 특수한 환경에서 사용하기가 조금 불편하여 &lt;a href="https://github.com/zplug/zplug"&gt;zplug&lt;/a&gt;로 옮겨타게 됐다. &lt;a href="https://github.com/zplug/zplug"&gt;zplug&lt;/a&gt;의 특장점은 몇 가지가 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;빠른 플러그인 설치
일단 플러그인 설치가 병렬적으로 이루어져 빠르게 설치할 수 있다는 장점이 있다.&lt;/li&gt;
&lt;li&gt;직관적인 사용법
&lt;code&gt;.zshrc&lt;/code&gt; 파일에 &lt;code&gt;zplug&lt;/code&gt; 명령으로 플러그인들을 추가할 수 있는데, github에 있는 플러그인이라면 간단하게 추가할 수 있다. &lt;a href="https://ohmyz.sh/"&gt;oh-my-zsh&lt;/a&gt;나 [prezto][prezto]같은 플러그인 번들에 포함된 플러그인도 태그를 이용해 간단하게 추가할 수 있다. 이외에도 gist를 비롯한 일반적이지 않은 source를 사용할 수 있도록 커스터마이즈 할 수도 있다.&lt;/li&gt;
&lt;li&gt;Caching 지원
캐싱을 통해 플러그인 로딩 속도를 높일 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 외에도 &lt;a href="https://github.com/zplug/zplug"&gt;zplug&lt;/a&gt; 깃헙 저장소의 README를 보면 다양한 장점들을 제안하고 있는데, 언급되어 있지 않은 최고의 장점이 하나가 더 있어 택하게 되었다. 바로 플러그인의 repository를 관리하는 디렉토리를 별도로 지정할 수 있다는 것. 왜 이 것이 장점이 되냐면, 개인 계정의 용량에 제한이 크게 가해져 있어 공용계정 스토리지를 사용해야 되는 경우 플러그인 저장 장소를 바꿔줌으로써 개인 계정 용량을 관리할 수 있고, 때로는 플러그인 repository 관리용 디렉토리를 아예 공유하여 사용함으로써 환경 관리 및 설정 비용을 최소화할 수도 있기 때문이다.&lt;/p&gt;
&lt;h2 id="how-to-install-zplug"&gt;How to install zplug&lt;/h2&gt;
&lt;p&gt;인터넷이 되는 환경에서의 설치는 매우 간단하다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sL --proto-redir -all,https https://raw.githubusercontent.com/zplug/installer/master/installer.zsh &lt;span class="p"&gt;|&lt;/span&gt; zsh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;되지 않는 경우에는 zplug repository를 다운받아서 옮겨넣는 방식으로 설치할 수 있다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;https://github.com/zplug/zplug/archive/master.zip
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;위의 압축파일을 받아 풀면 &lt;code&gt;zplug-master&lt;/code&gt; 디렉토리가 생길텐데, 이걸 &lt;code&gt;$HOME/.zplug&lt;/code&gt;로 옮겨놓고 &lt;code&gt;init.zsh&lt;/code&gt;를 source하면 된다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;unzip master.zip
mv zplug-master $HOME/.zplug
echo &amp;#39;source $HOME/.zplug/init.zsh&amp;#39; &amp;gt;&amp;gt; ~/.zshrc
echo `zplug load&amp;#39; &amp;gt;&amp;gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="useful-plugins"&gt;Useful plugins&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://www.zsh.org/"&gt;zsh&lt;/a&gt; 플러그인 중에서 특별히 추천할만한 플러그인은 다음과 같다.&lt;/p&gt;
&lt;h3 id="oh-my-zsh"&gt;Oh-my-zsh&lt;/h3&gt;
&lt;p&gt;Oh my zsh 전체가 필요한 것은 아니지만, &lt;a href="https://github.com/tarjoilija/zgen"&gt;zgen&lt;/a&gt;과는 다르게 &lt;a href="https://github.com/zplug/zplug"&gt;zplug&lt;/a&gt;를 사용할 경우 lib 디렉토리에 들어있는 기능들이 자동적으로 활성화가 안되는 것 같다. 문제는 되지 않는 것이, &lt;code&gt;zplug load&lt;/code&gt; 명령으로 불러올 수 있다. &lt;a href="https://github.com/tarjoilija/zgen"&gt;zgen&lt;/a&gt;과의 차이는, &lt;a href="https://github.com/tarjoilija/zgen"&gt;zgen&lt;/a&gt;은 lib 디렉토리 내의 기능들이 선택의 여지 없이 한번에 활성화되지만 &lt;a href="https://github.com/zplug/zplug"&gt;zplug&lt;/a&gt;는 활성화를 선택할 수 있다는 점이다.
lib 디렉토리를 포함하여 oh my zsh는 여러 플러그인들을 가지고 있는데, 태그로 &lt;code&gt;from:oh-my-zsh&lt;/code&gt;를 표시해주면 oh my zsh의 플러그인들을 선택적으로 불러올 수 있다. 그 때 플러그인 이름에는 oh my zsh 리포지터리 기준으로 상대 경로를 지정해주면 된다. 예를 들면 다음과 같다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zplug &lt;span class="s2"&gt;&amp;#34;lib/key-bindings&amp;#34;&lt;/span&gt;, from:oh-my-zsh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zplug &lt;span class="s2"&gt;&amp;#34;plugins/git&amp;#34;&lt;/span&gt;, from:oh-my-zsh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;lib 디렉토리 안의 플러그인들은 간단하면서 사용성을 크게 향상시켜주는 것들이 많기 때문에 대부분 사용하는 것을 추천한다. 좀 더 자세한 내용은 &lt;a href="https://github.com/robbyrussel/oh-my-zsh"&gt;github&lt;/a&gt;를 참조하자.&lt;/p&gt;
&lt;h3 id="autosuggestion"&gt;Autosuggestion&lt;/h3&gt;
&lt;p&gt;셸을 쓰다보면 비슷한 명령이 들어간 미묘하게 다른 커맨드들을 쓸 일이 많다. 그 명령이 길기라도 하면 내용이 기억이 안나니 위 아래 화살표로 히스토리를 뒤지고 있거나, &lt;code&gt;history&lt;/code&gt; 명령에 &lt;code&gt;grep&lt;/code&gt;을 걸어서 보는 수 밖에 없었다.
&lt;a href="https://fishshell.com"&gt;fish&lt;/a&gt; 셸이 등장한 이후, 여기서 선보인 autosuggestion 기능을 다른 셸에 도입하는 시도들도 있었는데, &lt;a href="http://www.zsh.org/"&gt;zsh&lt;/a&gt;도 당연히 그런 플러그인들이 존재한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zplug &lt;span class="s2"&gt;&amp;#34;zsh-users/zsh-autosuggestions&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;a href="https://github.com/zsh-users/zsh-autosuggestions"&gt;github&lt;/a&gt;으로부터 바로 가져오면 되니 특별한 태그가 필요하지 않다. 이걸 사용하면, 사용 기록을 바탕으로 추천해줄만한 전체 명령을 어두운 색으로 미리 표시해줄 것이다.
그런데 그냥 쓰기에는 한가지 단점이 있는데, 바로 [solarized][solarized] 테마를 쓰면 배경색과 추천한 내용의 어두운 색이 겹쳐 글자가 잘 안보인다는 점이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;fg=23&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;위 설정값을 &lt;code&gt;zplug load&lt;/code&gt; 밑에 두면 살짝 밝은 색으로 나와서 보기에 조금 편리해진다.&lt;/p&gt;
&lt;h3 id="syntax-highlighter"&gt;Syntax highlighter&lt;/h3&gt;
&lt;p&gt;이 기능도 역시 &lt;a href="https://fishshell.com"&gt;fish&lt;/a&gt; 셸에서 유래된 것인데, 셸에 치는 명령어를 컨텍스트에 맞게 색을 칠해서 구분하기 쉽게 해준다. 명령어가 존재하는지 여부나 파일이 있는지, 파일이 디렉토리인지 등의 정보를 커맨드를 치면서 실시간으로 확인할 수 있어 매우 편리하다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zplug &lt;span class="s2"&gt;&amp;#34;zdharma/fast-syntax-highlighting&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;a href="https://github/zdharma/fast-syntax-highlighting"&gt;github&lt;/a&gt; 저장소를 참조하면 자세한 내용을 알 수 있다. 사족으로, 원래 &lt;a href="https://github.com/zsh-users/zsh-syntax-highlighting"&gt;zsh-users 에 등록된 highlighter&lt;/a&gt;을 썼었는데, 이게 더 빠르다고 해서 바꿨다. 바꿔보니 좀 더 다양한 highligting을 지원하는 것 같아 만족스럽다.&lt;/p&gt;
&lt;h3 id="awesome-zsh-plugins"&gt;Awesome-zsh-plugins&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://www.zsh.org/"&gt;zsh&lt;/a&gt; 플러그인도 역시 &lt;a href="https://github.com/unixorn/awesome-zsh-plugins"&gt;awesome-zsh-plugin&lt;/a&gt; 같은 페이지가 있다. 다양한 플러그인들이 모여 있으니 참고해서 좋은 툴을 사용하면 사용성을 많이 증대시킬 수 있을 것이다.&lt;/p&gt;
&lt;h2 id="offline-usage"&gt;Offline usage&lt;/h2&gt;
&lt;p&gt;독특한 사용법 중 하나로 공용 repo를 위에서 언급했었다. 개인적으로 플러그인을 살짝 손볼 필요가 있을 때는 공용 repo를 사용할 수 없겠지만, 인터넷이 안되는 상황이라고 할 경우 기존 방법으로 플러그인을 설치하는 것이 조금 복잡해지는데 공용 repo를 쓰면 귀찮은 것이 어느 정도 해결된다.
외부에서 사용하던 스크립트를 그대로 집어넣을 경우 github에 연결이 안되는 상황이면 플러그인을 설치할 수 없어 제대로 환경이 셋업되지 않는다. 이 때 누군가가 플러그인을 엄선해서 공용 디렉토리에 풀어놓고, 이를 참조하게 하면 인터넷 없이 플러그인을 관리할 수 있을 것이다.
가장 좋은 방법은 그런 공용 디렉토리를 소스로 사용하는 커스텀 태그를 추가하는 것이겠지만, 태그 추가와 같은 없는 기능을 추가하는 것은 아무리 프레임워크가 잘 만들어져 있다고 해도 손이 많이 가는 일이다.
그래서 내가 선택한 방법은, 미리 설치된 플러그인의 repo를 그대로 압축해서 인터넷이 안되는 환경에 옮겨논 후, zplug가 공용 디렉토리를 바라보도록 만들어서 이미 설치된 것으로 간주하게 하는 것이었다.
공용 repo를 바라보게 하는 것은 어렵지 않은데, &lt;code&gt;source $HOME/.zplug/init.zsh&lt;/code&gt; 앞에 &lt;code&gt;$ZPLUG_REPO&lt;/code&gt; 환경변수를 해당 공용 repo를 가리키게 하면 된다. 이렇게 하면 또 다른 장점은, 누군가가 공용 repo를 업데이트 해주기만 해도 이를 바라보는 모든 사람이 한번에 업데이트를 받을 수 있다는 점이다.&lt;/p&gt;
&lt;h2 id="future-work"&gt;Future work&lt;/h2&gt;
&lt;p&gt;다음에 기회가 된다면, 필요해서 간단하게 자작해봤던 zsh 스크립트에 대해 간단히 소개하는 시간이 있었으면&amp;hellip;좋겠지만, 쓸모있는 내용이 될 때까지 정리를 한 뒤에나 할 수 있을 것 같다.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;zplug는 플러그인 관리 구조를 간단하고 단순하게 만들어줘서 다양한 플러그인을 어렵지 않게 관리할 수 있고, 또한 공용 repo 사용을 통해 중앙관리까지 가능하여 매우 편리한 플러그인 매니저다.&lt;/p&gt;</description></item><item><title>Eleventy로 블로그 만들기 (3)</title><link>https://gwangyi.github.io/posts/eleventy-blog-3/</link><pubDate>Sat, 24 Nov 2018 12:01:00 +0000</pubDate><guid>https://gwangyi.github.io/posts/eleventy-blog-3/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;지난시간까지 CSS까지 입혀서 블로그를 이쁘게 꾸며보는데까지 진행했다. 이제는 블로그 글에 대한 신빙성을 높여주고 읽는 사람에게 나를 어필할 수 있는 &lt;a href="https://gwangyi.github.io/about/"&gt;About&lt;/a&gt; 페이지를 만들어보도록 하자.&lt;/p&gt;
&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;About 페이지를 만드는 것도 여러 방식이 있을 수 있겠지만, 결국 About 페이지에 담기는 것은 term과 그 term에 대한 설명들이 될 것이므로, 어느 정도 정형화된 형태를 가질 것이라는 것을 예상해볼 수 있다.&lt;/p&gt;
&lt;p&gt;정형화된 틀에 맞춰서 데이터 중심으로 페이지를 설계하는 것이 바로 template이고, eleventy는 바로 그 template 기능을 주로 사용하는 툴이므로 아주 적합한 사용방법이 아닐 수 없다.&lt;/p&gt;
&lt;h3 id="definition-list"&gt;Definition List&lt;/h3&gt;
&lt;p&gt;HTML에는 term과 description의 pair들을 모아 list로 만들어주는 태그가 존재한다. 바로 &lt;a href="https://www.w3.org/TR/html50/grouping-content.html#the-dl-element"&gt;&lt;code&gt;&amp;lt;dl&amp;gt;&lt;/code&gt;&lt;/a&gt; 라는 태그인데, 자식 노드로 &lt;code&gt;&amp;lt;dt&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;dd&amp;gt;&lt;/code&gt; 태그를 통해서 definition list를 구성할 수 있다. 하지만 곤란한 점이 있는데, &lt;a href="https://www.markdownguide.org/"&gt;markdown&lt;/a&gt;의 기본 정의에는 definition list를 지원하지 않는다는 점이다.
물론 용도가 확실하고 수요가 있는 태그들이라 여러 커스텀 마크다운 해석기에서 지원하기도 하는데, 제일 보편적인 형태는 다음과 같이 정의하는 방식을 사용한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;term
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;: definition
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;이렇게 사용할 경우 다음처럼 표시된다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;dl&gt;
&lt;dt&gt;term&lt;/dt&gt;
&lt;dd&gt;definition&lt;/dd&gt;
&lt;/dl&gt;
&lt;/blockquote&gt;
&lt;p&gt;다만, eleventy에서 기본적으로 사용하고 있는 마크다운 해석기인 &lt;a href="https://markdown-it.github.io/"&gt;markdown-it&lt;/a&gt;은 가장 기본적인 markdown 문법만을 지원하고, definition list같은 확장 문법은 플러그인을 통해 활성화시켜야 한다. 해당 플러그인의 이름은 &lt;a href="https://github.com/markdown-it/markdown-it-deflist"&gt;&lt;code&gt;markdown-it-deflist&lt;/code&gt;&lt;/a&gt;이며, &lt;code&gt;npm&lt;/code&gt;이나 &lt;code&gt;yarn&lt;/code&gt;을 통해 설치할 수 있다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;yarn add markdown-it-deflist
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="cm"&gt;/* Markdown Plugins */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;markdownIt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;markdown-it&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;markdownItAnchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;markdown-it-anchor&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;markdownItDeflist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;markdown-it-deflist&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;breaks&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;linkify&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;permalink&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;permalinkClass&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;direct-link&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;permalinkSymbol&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;md&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;markdownIt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;markdownItAnchor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;markdownItDeflist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;.eleventy.js&lt;/code&gt;의 대략 43번째줄 가량부터 &lt;a href="https://markdown-it.github.io/"&gt;markdown-it&lt;/a&gt;의 플러그인을 설정하고 있는 부분을 볼 수 있다. 원래는 &lt;a href="https://github.com/valeriangalliat/markdown-it-anchor"&gt;markdown-it-anchor&lt;/a&gt; 플러그인만 추가되어 있지만, definition list를 지원하는 플러그인인 &lt;a href="https://github.com/markdown-it/markdown-it-deflist"&gt;markdown-it-deflist&lt;/a&gt;를 추가한 것이다. 이렇게 바꾸고 나면 그 뒤부터는 definition list를 사용할 수 있다.&lt;/p&gt;
&lt;h3 id="yaml-as-a-data-file"&gt;YAML as a data file&lt;/h3&gt;
&lt;p&gt;eleventy는 template에 사용되는 &lt;a href="https://www.11ty.io/docs/data/"&gt;데이터파일을 템플릿과 별개로 관리&lt;/a&gt;할 수 있다. 일반적으로는 템플릿의 front matter로 기록하지만, 동일한 템플릿을 서로 다른 데이터로 사용하기 위해 &lt;a href="https://www.11ty.io/docs/data-template-dir/"&gt;분리할 수 있도록&lt;/a&gt; 되어 있다. 다만, 지원하는 데이터 파일의 포맷은 &lt;code&gt;.json&lt;/code&gt; 파일과 &lt;code&gt;.js&lt;/code&gt; 파일이 전부인 상태로, 특별히 확장할 수 있는 API를 현재로썬 지원하고 있지 않다.
front matter는 de facto standard인 YAML로 작성할 수 있게 한다 쳐도, 템플릿 데이터를 YAML로 사용할 수 없는 것은 상당히 불편한 일이다. 물론 &lt;code&gt;.js&lt;/code&gt;는 evaluate되므로 &lt;code&gt;.js&lt;/code&gt;로 YAML을 읽어와서 반환하는 방식으로 템플릿 데이터를 만들 수도 있겠지만, 불필요한 코드가 들어가고 watch를 위한 수정사항 tracking도 통하지 않으므로 기각하였다.
그래서 eleventy의 내부 동작을 잘 살펴보고 적당한 &lt;a href="https://en.wikipedia.org/wiki/Monkey_patch"&gt;monkey-patching&lt;/a&gt;을 통해 &lt;code&gt;.yaml&lt;/code&gt; 파일도 template data file 로 사용할 수 있도록 해 주는 플러그인을 제작했다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;yarn add eleventy-plugin-yamldata
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;luxon&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pluginRss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;@11ty/eleventy-plugin-rss&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pluginSyntaxHighlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;@11ty/eleventy-plugin-syntaxhighlight&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pluginTOC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;eleventy-plugin-toc&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pluginYamldata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;eleventy-plugin-yamldata&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pluginRss&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pluginSyntaxHighlight&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pluginTOC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pluginYamldata&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;다음에 기회가 있으면 플러그인을 만드는 과정에 대해서도 포스팅하도록 하겠다.&lt;/p&gt;
&lt;h2 id="about-page-design"&gt;&amp;lsquo;About page&amp;rsquo; design&lt;/h2&gt;
&lt;p&gt;About 페이지는 여러 개의 섹션으로 나누고, 각 섹션마다 주제와 설명, 그리고 세부 항목들을 나열하려고 한다. 즉, 다음과 같은 형식이 될 것이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Section&lt;/strong&gt;&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;term&lt;/dt&gt;
&lt;dd&gt;&lt;em&gt;description&lt;/em&gt; ― period
&lt;ul&gt;
&lt;li&gt;item&lt;/li&gt;
&lt;li&gt;item&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/blockquote&gt;
&lt;p&gt;참고로, Section은 &lt;code&gt;## Section&lt;/code&gt; 이 되어야 하겠으나, 이 문서의 구조를 깨버리기 때문에 강조하는 것으로 대체했다.&lt;/p&gt;
&lt;p&gt;이러한 점을 감안해서, about page의 데이터는 다음과 같이 형태를 갖추도록 구성할 것이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Section 1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Term 1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Description 1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2016&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;to Present&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;Item 1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;Item 2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Term 2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Description 2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2010&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;to 2016&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Section 2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;sections&lt;/code&gt; 키 안에 모든 섹션을 리스트로 넣고, 각 섹션은 &lt;code&gt;title&lt;/code&gt;과 &lt;code&gt;entries&lt;/code&gt;를 갖는다. &lt;code&gt;entries&lt;/code&gt;는 term과 description을 가지며, 섹션과 비슷한 방식으로 &lt;code&gt;term&lt;/code&gt;이 정의된다.&lt;/p&gt;
&lt;p&gt;그러면 이걸 표현할 nunjucks + markdown 템플릿을 구성해보면 다음처럼 쓸 수 있을 것이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{% for section in sections %}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## {{ section.title }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{% for entry in section.entries %}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{% if entry.title -%}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{ entry.title }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{% if entry.description %}: &lt;span class="ge"&gt;_{{ entry.description }}_&lt;/span&gt;{% if entry.period %} &amp;amp;horbar; {{ entry.period }}{% endif %}{% endif -%}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{% if entry.score %}: {% for i in range(5) %}{% if i &amp;lt; entry.score %}&amp;amp;starf;{% else %}&amp;amp;star;{% endif %}{% endfor %}{% endif %}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{%- for item in entry.items %}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;*&lt;/span&gt; {{ item }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{%- endfor %}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{% else -%}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;*&lt;/span&gt; {{ entry | safe }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{% endif %}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{% endfor %}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{% endfor %}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;구체적인 태그들에 대한 설명은 &lt;a href="https://mozilla.github.io/nunjucks/templating.html"&gt;Nunjucks Templating Docs&lt;/a&gt;를 참조하자.&lt;/p&gt;
&lt;h2 id="future-work"&gt;Future Work&lt;/h2&gt;
&lt;p&gt;문서 도중에 언급한 yaml data plugin의 구조에 대해서 포스팅 할 것이 남아 있다. 그리고 지금껏 만든 블로그를 &lt;a href="https://pages.github.com"&gt;github pages&lt;/a&gt; 서비스를 통해 발행하는 방법까지 하고 나면 블로그 시리즈는 대단원의 막을 내릴 수 있을 것 같다.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;About page에 한정시켜서 만든 것이지만, 정형화된 구조를 가진 데이터를 특정 형태에 맞게 출력하는데 유리한 template과 template data file을 이용해 페이지를 만들어 보았다. 이를 통해서 보여주는 부분과 실제 데이터를 분리했기 때문에 추후 리뉴얼이나 다른 곳에 적용시킬 때 활용도가 높도록 나누어주는 것을 할 수 있을 것이다.&lt;/p&gt;</description></item><item><title>Eleventy로 블로그 만들기 (2)</title><link>https://gwangyi.github.io/posts/eleventy-blog-2/</link><pubDate>Mon, 05 Nov 2018 21:54:00 +0000</pubDate><guid>https://gwangyi.github.io/posts/eleventy-blog-2/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;지난 시간에 글만 쓸 수 있을만한 간단한 블로그를 실제로 만들어 보았다. 지난 시간에 예고했던 대로 기본 틀의 구조를 이곳저곳 살펴보고, 제일 먼저 블로그의 전체적인 분위기를 바꿔보도록 하자.&lt;/p&gt;
&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;구조를 살펴보기 전에, 쓸만한 CSS 프레임워크들을 뒤져볼 것이다. 블로그 테마를 직접 꾸밀 수 있다면 아쉬울 것이 없겠지만, 나같이 디자인과는 담을 쌓은 사람들은 직접 만드는 것보단 많은 사람들이 쌓아올린 금자탑을 가져다 쓰는 편이 더 생산적이고 중요한 일에 집중하는데 도움을 줄 것이다. 무엇보다, 웹 기술이 온 세상을 집어삼키는 이 시점에서 CSS 프레임워크 쓰는 법을 조금만 배워놓으면, 앞으로의 삶이 편리해지리라는 확신을 가지고서 뒤져보도록 하자.&lt;/p&gt;
&lt;h3 id="awesome-css-frameworks"&gt;Awesome CSS Frameworks&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com"&gt;Github&lt;/a&gt;에는 워낙 다양한 오픈소스 프로젝트들이 널려 있어서 그 중에 마음에 드는 것을 찾을 수만 있다면 최고의 보물창고임에는 이론의 여지가 없다. 하지만 먼저 말했듯이, 너무 다양하기 때문에 원하는 것을 찾기가 쉽지 않다는 점에 착안, 특정 주제에 관련된 유명한 프로젝트들을 큐레이트해서 보여주는 다양한 저장소들 또한 존재한다. 아마도 최초의 큐레이팅 저장소의 이름이 그랬기 때문일 것 같지만, 그런 저장소들은 대부분 &lt;code&gt;awesome-something&lt;/code&gt; 과 같은 이름을 하고 있는 경우가 많다.&lt;/p&gt;
&lt;p&gt;Github에서 awesome css frameworks를 검색해보면, &lt;a href="https://github.com/troxler/awesome-css-frameworks/"&gt;troxler/awesome-css-frameworks&lt;/a&gt; 저장소가 제일 위에 나온다. 이 중에서 &lt;a href="https://getbootstrap.com"&gt;Bootstrap&lt;/a&gt;, &lt;a href="https://milligram.io"&gt;Milligram&lt;/a&gt;, &lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt; 같은 프레임워크들을 볼 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://getbootstrap.com"&gt;Bootstrap&lt;/a&gt;은 보편적으로 많이 쓰이는 프레임워크이고, 당시 나왔을 때 나같은 디알못도 유려한 사이트를 만들 수 있도록 컴포넌트들을 미리 다 정의해 둠으로써 디자인보다는 내용에 집중할 수 있게 해줘서 엄청난 반향을 불러일으켰던 프레임워크이다. 다만 단점이 있다면, Javascript 프레임워크 중 하나인 &lt;a href="https://jquery.com"&gt;jQuery&lt;/a&gt;와 강하게 결합되어 HTML/CSS만으로는 사이트를 제작할 수 없다는 것과, 이번 &lt;a href="https://11ty.io"&gt;Eleventy&lt;/a&gt; 웹사이트 제작과는 큰 상관이 없지만 다른 종류의 Javascript framework와 섞어 쓰기가 곤란하다는 점이 있다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://milligram.io"&gt;Milligram&lt;/a&gt;은 상당히 작은 footprint를 가진 프레임워크로, CSS 파일의 용량이 적기 때문에 빠르게 로딩되고, 지저분하게 태그마다 클래스 이름을 갖다붙이지 않아도 적절하게 처리해준다는 점은 장점이지만, 아무래도 전체적인 레이아웃을 잡는데는 기능이 좀 부족하다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt;는 CSS 만으로 구성된 프레임워크이고, &lt;a href="https://sass-lang.com"&gt;SASS&lt;/a&gt;나 &lt;a href="https://lesscss.org"&gt;LESS&lt;/a&gt; 코드가 제공되어 커스터마이징하기 편리하며, 모듈화가 되어 있어 필요한 모듈만 가져다 빌드할 수도 있는 특징을 가지고 있다. &lt;a href="https://getbootstrap.com"&gt;Bootstrap&lt;/a&gt;에 비해 지원하는 컴포넌트들이 그다지 부족하지 않고, Javascript를 배제하여 특정 Javascript framework에 대한 의존성이 없어 원하는 Javascript framework를 선택할 수 있는 자유도를 갖는다. 그에 더해서, 특정 태그를 부여하면 일정 영역에 대해 &lt;a href="https://milligram.io"&gt;Milligram&lt;/a&gt;처럼 태그마다 클래스 이름을 붙이지 않더라도 미려한 디자인으로 구성할 수 있기 때문에, 개인적인 판단으로는 &lt;a href="https://getbootstrap.com"&gt;Bootstrap&lt;/a&gt; 비슷한 거대한 CSS framework와 &lt;a href="https://milligram.io"&gt;Milligram&lt;/a&gt; 같은 가벼운 CSS framework의 장점을 모았다고 생각한다.&lt;/p&gt;
&lt;p&gt;원래, 최근 &lt;a href="https://electronjs.org"&gt;Electron&lt;/a&gt;에 요즘 핫하다는 &lt;a href="https://vuejs.org"&gt;Vue.js&lt;/a&gt;를 끼얹어서 뭘 만들어보려는데, &lt;a href="https://vuejs.org"&gt;Vue.js&lt;/a&gt;는 &lt;a href="https://getbootstrap.com"&gt;Bootstrap&lt;/a&gt;과 달리 디자인 요소가 단 하나도 없다는 것을 알고서 &lt;a href="https://getbootstrap.com"&gt;Bootstrap&lt;/a&gt;을 끼얹으려고 했었다. 그런데 &lt;a href="https://getbootstrap.com"&gt;Bootstrap&lt;/a&gt;은 앞에서 언급했듯 &lt;a href="https://jquery.com"&gt;jQuery&lt;/a&gt;와 강하게 결합되어 있어 &lt;a href="https://vuejs.org"&gt;Vue.js&lt;/a&gt;와 함께 쓰기에는 모양새가 별로 좋지 않았다. 이를 대체할 수 있는 CSS만으로 된 framework를 찾다가 발견한게 &lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt;였었다. 이번에 블로그를 만들면서 &lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt;를 계속 쓸지 다른걸 찾아볼지 고민을 좀 했었는데, 아무리 찾아봐도 &lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt;만한 것을 찾기 힘들어서 도로 돌아오게 되었다.&lt;/p&gt;
&lt;p&gt;여담으로, &lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt;라는 이름은 드래곤볼에 나오는 부르마의 영어식 이름이라고 한다.&lt;/p&gt;
&lt;h3 id="template-engine"&gt;Template engine&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://11ty.io"&gt;Eleventy&lt;/a&gt;는 static site generator의 일종으로, 문서를 작성하면 미리 만들어둔 적절한 템플릿과 함께 사이트 전체를 빌드해내는 것을 목적으로 한 프로그램이다. 이는 즉, &lt;a href="https://11ty.io"&gt;Eleventy&lt;/a&gt;를 잘 쓰려면 템플릿 엔진에 대한 이해도 어느 정도 필요하다는 말이 된다. &lt;a href="https://11ty.io"&gt;Eleventy&lt;/a&gt;가 지원하는 템플릿 언어는 몇 가지가 있는데, 내장된 템플릿 엔진은 &lt;a href="https://ejs.co"&gt;EJS&lt;/a&gt;, &lt;a href="https://handlebarsjs.com"&gt;Handlebars&lt;/a&gt;, &lt;a href="https://shopify.github.io/liquid"&gt;Liquid&lt;/a&gt;, &lt;a href="https://mustache.github.io"&gt;Mustache&lt;/a&gt;, &lt;a href="https://mozilla.github.io/nunjucks"&gt;Nunjucks&lt;/a&gt; 등이 들어있다. 그 외에도 몇 가지 더 있는데, HTML 문법에서 많이 벗어나는 것은 일단 건너뛰도록 하자.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ejs.co"&gt;EJS&lt;/a&gt;는 &lt;a href="https://ruby-lang.org/"&gt;Ruby&lt;/a&gt;의 &lt;a href="https://ruby-doc.org/stdlib-2.5.3/libdoc/erb/rdoc/ERB.html"&gt;ERB&lt;/a&gt;에서 영감을 받은 것으로, 특별한 별도의 템플릿 문법 없이 &lt;a href="https://www.php.net/"&gt;php&lt;/a&gt;와 비슷한 느낌으로 Javascript를 이용해 템플릿을 만들 수 있게 해준다. &lt;a href="https://handlebarsjs.com"&gt;Handlebars&lt;/a&gt;, &lt;a href="https://shopify.github.io/liquid"&gt;Liquid&lt;/a&gt;, &lt;a href="https://mustache.github.io"&gt;Mustache&lt;/a&gt;, &lt;a href="https://mozilla.github.io/nunjucks"&gt;Nunjucks&lt;/a&gt;는 서로서로 비슷하게 생겨서 영향을 받았지만, 키워드 면에서 조금씩 차이가 있다. 이 중 &lt;a href="https://shopify.github.io/liquid"&gt;Liquid&lt;/a&gt;는 static site generator의 시초격 되는 [Jekyll][jekyll]에서 채택한 것이고, &lt;a href="https://mozilla.github.io/nunjucks"&gt;Nunjucks&lt;/a&gt;는 &lt;a href="https://python.org/"&gt;Python&lt;/a&gt;계에서 널리 쓰이는 템플릿 언어인 &lt;a href="https://jinja.pocoo.org"&gt;Jinja&lt;/a&gt;의 Javascript 포팅 버전이다. 한번 배운건 배우는데 들인 시간이 아까워서라도 마르고 닳도록 써먹어야 되므로, &lt;a href="https://python.org/"&gt;Python&lt;/a&gt;에서도 쓸 일이 있을 &lt;a href="https://mozilla.github.io/nunjucks"&gt;Nunjucks&lt;/a&gt;를 기본 템플릿 엔진으로 써보도록 하자.&lt;/p&gt;
&lt;p&gt;한가지 주의할 것은, 앞에서 참조했던 &lt;a href="https://github.com/11ty/eleventy-base-blog"&gt;11ty/eleventy-base-blog&lt;/a&gt;의 기본 설정으론 html 파일은 &lt;a href="https://mozilla.github.io/nunjucks"&gt;Nunjucks&lt;/a&gt;를 쓰지만 md 파일은 &lt;a href="https://shopify.github.io/liquid"&gt;Liquid&lt;/a&gt;로 지정되어 있다. 템플릿 엔진을 이것저것 쓰면 정신사나우니 이걸 &lt;a href="https://mozilla.github.io/nunjucks"&gt;Nunjucks&lt;/a&gt;로 바꿔서 쓰도록 하자. 바꾸고 싶을 때는 &lt;code&gt;.eleventy.js&lt;/code&gt; 파일을 수정하면 된다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;markdownTemplateEngine&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;njk&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;원래 코드의 73번행 근처를 보면 이렇게 template engine을 바꿀 수 있도록 되어 있을 것이다. 이 때 주의할점은, &lt;a href="https://mozilla.github.io/nunjucks"&gt;Nunjucks&lt;/a&gt;나 &lt;a href="https://shopify.github.io/liquid"&gt;Liquid&lt;/a&gt;나 비슷한 문법을 쓰기 때문에 많은 경우 특별히 손 대지 않아도 호환이 되지만, 몇 가지의 문법은 호환되지 않아 단순히 템플릿 엔진을 바꾸기만 하면 사이트가 만들어지지 않을 것이다. 이 글이 써지는 현재 주석 구문이 좀 달라서 수정할 필요가 있다. &lt;code&gt;404.md&lt;/code&gt; 파일 안에 사용중이다. &lt;a href="https://shopify.github.io/liquid"&gt;Liquid&lt;/a&gt;의 주석은 &lt;code&gt;{% comment %}} ... {% endcomment %}&lt;/code&gt; 방식으로 걸지만, &lt;a href="https://mozilla.github.io/nunjucks"&gt;Nunjucks&lt;/a&gt;는 &lt;code&gt;{# ... #}&lt;/code&gt; 을 사용한다.&lt;/p&gt;
&lt;h2 id="applying-bulma-to-my-blog"&gt;Applying Bulma to my blog&lt;/h2&gt;
&lt;p&gt;이제 &lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt;를 블로그에 붙일 차례다. 아무 포스트나 잡고 열어보면, front matter에 &lt;code&gt;layout&lt;/code&gt; 필드가 있는 것을 볼 수 있다. 포스트라면 &lt;code&gt;layouts/post.njk&lt;/code&gt;라고 적혀있을텐데, 해당 파일이 일반적인 포스트의 껍데기 역할을 한다. 그런데 실제로 열어보면 내용물이 그다지 많지 않고 또 &lt;code&gt;layout&lt;/code&gt; 필드로 다른 레이아웃을 지정하고 있는 것을 알 수 있다. 이렇게 타고 올라가다보면 보통 하나의 레이아웃으로 모이게 될텐데, 바로 &lt;code&gt;layout/base.njk&lt;/code&gt; 파일이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;layout/base.njk&lt;/code&gt;를 열어보면 전체 HTML 구조를 볼 수 있다. 수정 방향은, 이 구조를 최대한 살리면서 &lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt;를 적용한 미려한 사이트를 만드는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;layout/base.njk&lt;/code&gt; 파일을 열어보면, css 파일과 feed를 지정하는 태그가 들어 있고, 본체인 &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; 태그 안에는 네비게이션 바가 들어 있는 &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt;, 본문이 들어가는 &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;, 꼬릿말이 들어가는 &lt;code&gt;&amp;lt;footer&amp;gt;&lt;/code&gt; 세 태그로 구성되어 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;h3 id="stylesheets"&gt;Stylesheets&lt;/h3&gt;
&lt;p&gt;제일 먼저 해야할 일은, 기존 css를 비활성화시키고 &lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt;를 추가하는 일이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ renderData.title or title or metadata.title }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;stylesheet&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;integrity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sha256-2pUeJf+y0ltRPSbKOeJh09ipQFYxUdct5nTY6GAXswA=&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;anonymous&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;stylesheet&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://unpkg.com/@mdi/font/css/materialdesignicons.min.css&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;alternate&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{{ metadata.feed.path | url }}&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;application/atom+xml&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{{ metadata.title }}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;a href="https://cdnjs.com/"&gt;cdnjs&lt;/a&gt;가 다양한 Javascript 라이브러리들을 CDN으로 제공해주긴 하지만, 없는 것들도 있다. &lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt;는 &lt;a href="https://cdnjs.com/"&gt;cdnjs&lt;/a&gt;에서 구할 수 있지만, &lt;a href="https://materialdesignicons.com/"&gt;MDI&lt;/a&gt;는 &lt;a href="https://cdnjs.com/"&gt;cdnjs&lt;/a&gt;에서 못 찾아서 &lt;a href="https://unpkg.com/"&gt;unpkg&lt;/a&gt;에서 링크했다. &lt;a href="https://unpkg.com/"&gt;unpkg&lt;/a&gt;는 &lt;a href="https://npmjs.com/"&gt;npm&lt;/a&gt;에 업로드되어 있는 Javascript 라이브러리를 CDN 형식으로 서비스해주는 사이트이다. 이렇게 적용하고 새로고침을 해보면, 기존 css가 다 벗겨지고 &lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt;의 기본적인 CSS만 적용되어 봐주기 힘든 모양으로 바뀔 것이다. 이제 여기서 HTML을 조금씩 수정해, 보기 좋은 형태로 순차적으로 고쳐나가 보자.&lt;/p&gt;
&lt;h3 id="html"&gt;HTML&lt;/h3&gt;
&lt;p&gt;이제 HTML 구조를 변경할 시간이다. header, main, footer 세 단계로 나뉘어진 구조를 유지하면서 &lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt; 스타일을 씌우는 쪽으로 수정하자.&lt;/p&gt;
&lt;h4 id="header"&gt;header&lt;/h4&gt;
&lt;p&gt;네비게이션 바 모양을 그대로 살려서 작업할 수도 있겠지만, 네비게이션 바는 &lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt;에서 그다지 의미적으로 배치된 요소가 아니라는 단점이 있다. 타이틀도 들어가고, &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; 기반의 네비게이션 바를 같이 쓸 수 있는 예제가 &lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt; 구성요소 중에 있는데, &lt;a href="https://bulma.io/documentation/layout/hero/#fullheight-hero-in-3-parts"&gt;Fullheight hero in 3 parts&lt;/a&gt;가 바로 그것이다. 이를 사용해보도록 하자. 우리가 쓸 부분은 제목 부분과 하단 탭 부분만 쓰고, &lt;code&gt;hero-head&lt;/code&gt; 부분에 들어가는 네비게이션은 쓰지 않을 것이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;home&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{{ &amp;#39;/&amp;#39; | url }}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ metadata.title }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;nav&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {%- for nav in collections.nav | reverse -%}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;nav-item{% if nav.url == page.url %} nav-item-active{% endif %}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{{ nav.url | url }}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ nav.data.navtitle }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {%- endfor -%}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;위와 같은 코드를 다음처럼 수정한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;hero is-primary&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;hero-body&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;title home&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{{ &amp;#39;/&amp;#39; | url }}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ metadata.title }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;subtitle&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ metadata.feed.subtitle }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;hero-foot&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;nav&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tabs is-boxed&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;container&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {%- for nav in collections.nav | reverse -%}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="err"&gt;{%&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt; &lt;span class="na"&gt;nav&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;url &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt; &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;url&lt;/span&gt; &lt;span class="err"&gt;%}&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;is-active&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;{%&lt;/span&gt; &lt;span class="na"&gt;endif&lt;/span&gt; &lt;span class="err"&gt;%}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{{ nav.url | url }}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ nav.data.navtitle }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {%- endfor -%}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; 태그 전체가 hero가 될 것이므로 그에 맞는 클래스를 추가했다. 사이트 제목 부분이 hero의 title이 되었고, feed에서 쓰는 부제목을 추가했다. 이 두 부분은 hero의 body 부분이므로 &lt;code&gt;hero-body&lt;/code&gt;로 싸여졌다. 네비게이션 부분은 hero의 탭으로 만들었는데, 이 것은 hero의 아랫부분에 들어가야 되므로 &lt;code&gt;hero-foot&lt;/code&gt;으로 먼저 쌌고, 탭으로 만들기 위해 &lt;code&gt;tabs&lt;/code&gt; 클래스가 추가된 &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; 태그로 쌌다. &lt;code&gt;is-boxed&lt;/code&gt; 클래스를 추가해 탭의 모양을 잡아주었다. &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; 태그를 &lt;code&gt;container&lt;/code&gt;로 감싸줘서 탭이 중앙으로 적당히 정렬되도록 했으며, &lt;code&gt;nav&lt;/code&gt; 클래스가 &lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt;에서 의미를 갖기 때문에 클래스를 빼주었다. 마지막으로, 활성화된 탭은 &lt;code&gt;is-active&lt;/code&gt; 클래스를 사용해야하므로 &lt;code&gt;nav-item-active&lt;/code&gt;에서 &lt;code&gt;is-active&lt;/code&gt;로 바꿔주었다.&lt;/p&gt;
&lt;h4 id="main"&gt;main&lt;/h4&gt;
&lt;p&gt;본문 부분은 다음과 같이 수정했다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="err"&gt;{%&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt; &lt;span class="na"&gt;templateClass&lt;/span&gt; &lt;span class="err"&gt;%}&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{{ templateClass }}&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;{%&lt;/span&gt; &lt;span class="na"&gt;endif&lt;/span&gt; &lt;span class="err"&gt;%}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {{ content | safe }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;section{% if templateClass %} {{ templateClass }}{% endif %}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;container content&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {{ content | safe }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;단순히 &lt;a href="https://bulma.io/documentation/layout/section"&gt;&lt;code&gt;section&lt;/code&gt;&lt;/a&gt; 클래스를 부여한 뒤 &lt;a href="https://bulma.io/documentation/layout/container"&gt;&lt;code&gt;container&lt;/code&gt;&lt;/a&gt; 클래스가 부여된 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; 태그로 감쌌다. 안에는 본문만 들어갈 것이므로 &lt;a href="https://bulma.io/documentation/elements/content"&gt;&lt;code&gt;content&lt;/code&gt;&lt;/a&gt;를 부여했다.&lt;/p&gt;
&lt;h4 id="footer"&gt;footer&lt;/h4&gt;
&lt;p&gt;꼬릿말에는 저작권 표시와 나의 깃헙 프로필 페이지를 추가할 것이다. 기존의 &lt;code&gt;&amp;lt;footer&amp;gt;&lt;/code&gt; 태그는 내용이 비어 있으니 지우고 다음 내용으로 대체하자. &lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt;는 &lt;a href="https://bulma.io/documentation/layout/footer"&gt;&lt;code&gt;footer&lt;/code&gt;&lt;/a&gt; 구성요소를 가지고 있다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;footer&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;footer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;content has-text-centered&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Copyright &lt;span class="ni"&gt;&amp;amp;copy;&lt;/span&gt; {{ metadata.author.name }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {% if metadata.author.github %}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://github.com/{{ metadata.author.github }}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;icon&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;i&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;mdi mdi-github-circle&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;i&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {% endif %}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;footer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;여기서 특이한 사항은, &lt;code&gt;_data/metadata.json&lt;/code&gt; 파일에 &lt;code&gt;author&lt;/code&gt; 필드 안에 &lt;code&gt;github&lt;/code&gt; 필드가 있는지를 보고 분기하는 것인데, &lt;code&gt;_data/metadata.json&lt;/code&gt;에 깃헙 정보를 추가하지 않았다면 깃헙 링크를 스킵하고 보여주지 않도록 처리했다.&lt;/p&gt;
&lt;h3 id="post"&gt;Post&lt;/h3&gt;
&lt;p&gt;기본 post 레이아웃은 포스트 내용을 보여주는데는 부족함이 없지만, 몇 가지 아쉬운 점이 있다. 먼저 포스트가 작성된 시각이 보이지 않고, 포스트에 달린 태그가 보이지 않는다. 또, 긴 포스팅일 경우 목차가 자동으로 작성되면 좋을 것이다. post 레이아웃은 &lt;code&gt;_includes/layouts/post.njk&lt;/code&gt; 안에 들어 있다.&lt;/p&gt;
&lt;p&gt;태그 목록과 포스팅 날짜는 post 레이아웃에는 존재하지 않지만, 포스트 목록에 대한 템플릿인 &lt;code&gt;_includes/postslist.njk&lt;/code&gt; 파일에 나와 있으므로 이를 참조해서 진행하도록 하자.&lt;/p&gt;
&lt;h4 id="tag-list"&gt;Tag list&lt;/h4&gt;
&lt;p&gt;제목 바로 밑에는 해당 포스트에 달린 태그 리스트를 보여주도록 하자. 포스트 안으로 들어왔기 때문에 &lt;code&gt;tags&lt;/code&gt; 변수를 사용하면 태그 리스트를 출력할 수 있다. 그런데 몇 페이지는 &lt;code&gt;tags&lt;/code&gt;가 정의되어 있지 않을 수 있기 때문에, 조건부로 넣으면 될 것이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{%- if tags %}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{%- for tag in tags -%}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{%- if tag != &amp;#39;post&amp;#39; -%}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {% set tagUrl %}/tags/{{ tag }}/{% endset %}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{{ tagUrl | url }}&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tag&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ tag }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{%- endif %}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{%- endfor %}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{% endif -%}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt;의 &lt;code&gt;tag&lt;/code&gt; 구성요소를 사용해서 태그 모양으로 달았다.&lt;/p&gt;
&lt;h4 id="posted-date"&gt;Posted date&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt; 태그를 이용해서 의미론적으로 구성하도록 하자. 다음과 같은 내용을 제목 밑에 넣으면 된다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{%- if date -%}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt; &lt;span class="na"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{{ date | htmlDateString }}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ date | readableDate }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{% endif %}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h4 id="table-of-contents"&gt;Table of Contents&lt;/h4&gt;
&lt;p&gt;목차는 추가 플러그인을 이용하는 것이 편리하다. &lt;code&gt;eleventy-plugin-toc&lt;/code&gt; 플러그인이 이미 있으므로 이를 이용하도록 하자.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;yarn add eleventy-plugin-toc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;플러그인을 설치했으면 이용하도록 해 주어야 한다. &lt;code&gt;.eleventy.js&lt;/code&gt; 파일을 열어서 다음 부분을 추가하도록 하자.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;luxon&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pluginRss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;@11ty/eleventy-plugin-rss&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pluginSyntaxHighlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;@11ty/eleventy-plugin-syntaxhighlight&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pluginTOC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;eleventy-plugin-toc&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pluginRss&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pluginSyntaxHighlight&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;eleventyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pluginTOC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;이제 &lt;code&gt;toc&lt;/code&gt; 필터를 이용할 수 있게 되었다. 다음 코드를 &lt;code&gt;_includes/layouts/post.njk&lt;/code&gt;에 추가하도록 하자. &lt;code&gt;{{ content | safe }}&lt;/code&gt; 위에 넣어주어야 목차가 본문 위에 위치할 수 있으므로 참고하자.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{%- if &amp;#39;post&amp;#39; in tags -%}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;nav&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;box&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; Table of Contents &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {{ content | toc | safe }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{%- endif -%}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;목차는 포스팅에만 나오게 하기 위해 &lt;code&gt;tags&lt;/code&gt;에 &lt;code&gt;post&lt;/code&gt;가 있을 때만 보여주도록 했다.&lt;/p&gt;
&lt;h2 id="future-work"&gt;Future Work&lt;/h2&gt;
&lt;p&gt;이제 사이트의 틀을 대강 갖추었으므로, 나의 소개 페이지를 만들어 볼 차례이다. 소개 페이지는 대강의 틀을 먼저 갖춘 뒤, 내용은 별도의 데이터파일로 작성 후 템플릿을 통해 모양새를 갖추는 식으로 진행할 예정이다.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;이번 포스팅을 통해서는, &lt;a href="https://11ty.io"&gt;Eleventy&lt;/a&gt;의 템플릿 구조를 간단하게 살핀 후 &lt;a href="https://bulma.io"&gt;Bulma&lt;/a&gt;를 적용하여 사이트를 미려하게 꾸며보았다. 잘 되어 있는 오픈소스를 가져다 쓰면, 그럴듯한 사이트를 만드는 것도 그렇게 어려운 일이 아니라는 것을 볼 수 있었다.&lt;/p&gt;
&lt;p&gt;분량 조절에 실패해 글의 내용이 너무 길어졌다. 다음에는 분량을 잘 조절할 필요가 있겠다.&lt;/p&gt;</description></item><item><title>Eleventy로 블로그 만들기 (1)</title><link>https://gwangyi.github.io/posts/eleventy-blog/</link><pubDate>Mon, 29 Oct 2018 22:01:00 +0000</pubDate><guid>https://gwangyi.github.io/posts/eleventy-blog/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;예전에 야심찬 목표를 가지고 블로그를 만든 적이 있었다. 당시 블로그 작성을 위해 &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt;을 사용했는데, &lt;a href="https://www.ruby-lang.org/"&gt;Ruby&lt;/a&gt;를 쓰던 관성이 조금 남아있기도 했고, static site generator에 대해 다양하게 알지 못하던 때였다.&lt;/p&gt;
&lt;p&gt;최근에 블로그를 다시 살리려고 알아보는데, &lt;a href="https://nodejs.org/"&gt;NodeJS&lt;/a&gt; + &lt;a href="https://www.npmjs.com/"&gt;NPM&lt;/a&gt;의 의존성 관리에 비하면 &lt;a href="https://rubygems.org/"&gt;RubyGems&lt;/a&gt;는 &lt;a href="https://rvm.io/"&gt;RVM&lt;/a&gt;을 쓰지 않는 이상 시스템 라이브러리를 건드려야 되는 등 지저분하게 느껴져서, &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt; 외의 다른 static site generator를 찾다가 &lt;a href="https://11ty.io/"&gt;Eleventy&lt;/a&gt;를 발견하게 됐다.&lt;/p&gt;
&lt;p&gt;그리하여, 아직 완전히 익숙해지지 못한 &lt;a href="https://nodejs.org/"&gt;NodeJS&lt;/a&gt; 기반 static site generator인 &lt;a href="https://11ty.io/"&gt;Eleventy&lt;/a&gt;를 이용해 블로그를 만들어보자는 대장정이 시작된 것이다.&lt;/p&gt;
&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;Static Site Generator라고 불리는 일련의 툴들은, 일반적으로 웹 사이트 제작에 사용하는 &lt;a href="https://www.w3.org/html/"&gt;HTML&lt;/a&gt;보다 조금 더 간결한 &lt;a href="https://www.markdownguide.org/"&gt;Markdown&lt;/a&gt;과 같은 마크업 언어를 사용해서 컨텐츠를 작성하면, &lt;a href="http://jinja.pocoo.org/"&gt;Jinja&lt;/a&gt;나 &lt;a href="https://shopify.github.io/liquid/"&gt;Liquid&lt;/a&gt; 등의 템플릿 엔진을 통해 사이트를 &lt;em&gt;build&lt;/em&gt; 하는 기능을 수행한다. &lt;a href="https://wordpress.org/"&gt;WordPress&lt;/a&gt; 등의 CMS와의 차이점은, 요청이 있을 때마다 DB를 참조하여 보여줄 페이지의 내용을 생성하는 CMS와는 달리 static site generator는 전체 사이트 구조를 미리 생성해 두고 요청이 있을 때는 미리 생성해둔 페이지를 서비스한다는 차이가 있다. 미리 생성된 페이지를 서비스하기 때문에 잦은 업데이트가 없는 부분에서 유리하다.&lt;/p&gt;
&lt;p&gt;이 바닥에서 제일 유명한 툴이 &lt;a href="https://pages.github.com/"&gt;GitHub Pages&lt;/a&gt;에서 지원해주는 &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt;로, &lt;a href="https://www.ruby-lang.org/"&gt;Ruby&lt;/a&gt; 기반으로 &lt;a href="https://www.markdownguide.org/"&gt;Markdown&lt;/a&gt;과 &lt;a href="https://shopify.github.io/liquid/"&gt;Liquid&lt;/a&gt;를 이용해 사이트를 생성할 수 있다. 이 외에도 &lt;a href="https://www.python.org/"&gt;Python&lt;/a&gt; 기반의 &lt;a href="https://hyde.github.io/"&gt;Hyde&lt;/a&gt;, &lt;a href="https://golang.org/"&gt;Go&lt;/a&gt; 기반의 &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt; 등이 있다. 이 외에도 &lt;a href="https://staticgen.com/"&gt;StaticGen&lt;/a&gt; 사이트에서 다양한 static site generator들을 접할 수 있다.&lt;/p&gt;
&lt;p&gt;이 중에서 특별히 &lt;a href="https://11ty.io/"&gt;Eleventy&lt;/a&gt;를 선택한 이유는 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/"&gt;NodeJS&lt;/a&gt;를 사용한다.&lt;/li&gt;
&lt;li&gt;다양한 마크업 언어와 템플릿 엔진을 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;쓰는 사람이 &lt;em&gt;아직&lt;/em&gt; 없어 보인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://11ty.io/"&gt;Eleventy&lt;/a&gt;는 site generator일 뿐으로, 블로그처럼 쓰기 위해서는 골격을 짜 줄 필요가 있다. 다만 우리는 바쁜 사람들로, 골격을 처음부터 짜려면 &lt;a href="https://11ty.io/"&gt;Eleventy&lt;/a&gt;에 대해 심도있는 공부를 할 필요가 있기 때문에 미리 만들어진 골격으로부터 시작할 것이다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://11ty.io/"&gt;Eleventy&lt;/a&gt; 개발자 측에서 제공하는 간단한 골격이 이미 만들어져 있으므로 가져다 쓰도록 하자. 저장소는 &lt;a href="https://github.com/11ty/eleventy-base-blog"&gt;eleventy-base-blog&lt;/a&gt;에서 볼 수 있다.&lt;/p&gt;
&lt;p&gt;제일 먼저 해야 할 일은 &lt;a href="https://nodejs.org/"&gt;NodeJS&lt;/a&gt;와 &lt;a href="https://yarnpkg.com/"&gt;Yarn&lt;/a&gt;을 설치하는 일이다. 저장소에 있는 README 파일에는 &lt;a href="https://www.npmjs.com/"&gt;NPM&lt;/a&gt;을 쓰라고 되어 있긴 하지만, NPM에 비해 Yarn이 훨씬 빠르고 편리하기 때문에 Yarn을 사용하도록 하자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/en/download/"&gt;NodeJS Download&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://yarnpkg.com/en/docs/install"&gt;Yarn Download&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;설치가 끝나면 저장소를 클론할 차례다. 다음 명령어를 순차적으로 수행하고 나면 블로그 내용을 브라우저에서 볼 준비가 끝난다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone https://github.com/11ty/eleventy-base-blog my-blog
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; my-blog
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;yarn
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;yarn build --serve
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;그러면 창에 다음과 비슷한 메시지들이 출력될 것이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;yarn run v1.10.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ eleventy --serve
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing _site/sitemap.xml from ./sitemap.xml.njk.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing _site/feed/.htaccess from ./feed/htaccess.njk.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing _site/feed/feed.xml from ./feed/feed.njk.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing _site/index.html from ./index.njk.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing _site/posts/firstpost/index.html from ./posts/firstpost.md.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing _site/posts/fourthpost/index.html from ./posts/fourthpost.md.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing _site/posts/secondpost/index.html from ./posts/secondpost.md.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing _site/posts/thirdpost/index.html from ./posts/thirdpost.md.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing _site/about/index.html from ./about/index.md.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing _site/tags/index.html from ./tags-list.njk.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing _site/posts/index.html from ./archive.njk.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing _site/404.html from ./404.md.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing _site/tags/another-tag/index.html from ./tags.njk.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing _site/tags/second-tag/index.html from ./tags.njk.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing _site/tags/number-2/index.html from ./tags.njk.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Writing _site/tags/tagList/index.html from ./tags.njk.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Copied &lt;span class="m"&gt;2&lt;/span&gt; items and Processed &lt;span class="m"&gt;16&lt;/span&gt; files in 0.69 seconds &lt;span class="o"&gt;(&lt;/span&gt;43.1ms each&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Watching…
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;Browsersync&lt;span class="o"&gt;]&lt;/span&gt; Access URLs:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ----------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Local: http://localhost:8080
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ----------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; UI: http://localhost:3001
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ----------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;Browsersync&lt;span class="o"&gt;]&lt;/span&gt; Serving files from: _site
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Local 옆에 있는 주소를 브라우저에 입력하면 블로그 내용을 볼 수 있다. &lt;code&gt;from&lt;/code&gt; 오른편에 있는 파일 이름이 생성된 파일에 대한 소스 파일이고, 왼쪽에 있는 파일이 소스 파일로부터 생성된 웹페이지 파일이다. 궁금하니 이것 저것 열어서 어떤 구조로 되어 있는지 잘 살펴보자.&lt;/p&gt;
&lt;h2 id="how-to-post"&gt;How to Post&lt;/h2&gt;
&lt;p&gt;전체 구조는 천천히 살펴보기로 하고, 먼저 포스팅을 하려면 어떻게 해야할지를 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;./posts&lt;/code&gt; 디렉토리를 살펴보면, 미리 만들어져 있는 4개의 포스팅이 보인다. 열어보게 되면 맨 앞엔 static site generator 대부분이 사용하는 &lt;a href="https://www.11ty.io/docs/data-frontmatter/"&gt;front matter&lt;/a&gt; 포맷의 헤더가 위치하고 있고, 그 밑에는 markdown 파일인 만큼 &lt;a href="https://www.markdownguide.org/"&gt;Markdown&lt;/a&gt;으로 작성된 포스트의 본체가 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;간단하게 사용하려면 front matter의 &lt;code&gt;title&lt;/code&gt; 필드와 &lt;code&gt;date&lt;/code&gt; 필드를 적절하게 고치고, &lt;code&gt;tags&lt;/code&gt; 필드에 필요한 태그를 달아준 뒤 내용을 써 보도록 하자. 처음 eleventy를 실행할 때 &lt;code&gt;--serve&lt;/code&gt; 옵션을 줬기 때문에, 수정 후 저장만 하면 전체 사이트가 다시 빌드될 것이다. 그리고 열어놓은 브라우저도 Browsersync를 통해 자동으로 업데이트 될 것이다.&lt;/p&gt;
&lt;h2 id="future-work"&gt;Future Work&lt;/h2&gt;
&lt;p&gt;다음 포스팅에서는, 포스트를 제외한 나머지 부분을 적당히 손보는 방법에 대해서 다루고자 한다.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;요즘에는 옛날에 비해 홈페이지를 만드는 방법에 있어서 어마어마한 선택지를 놓고 고를 수 있게 되었다. 그 중에서 요즘 널리 쓰이는 Modern Javascript도 배우고 문서 작업에 편리한 Markdown도 배울 수 있는 Eleventy로 사이트를 구축하는 법에 대해 맛을 보았다. 앞으로 블로그 업데이트와 함께 계속해서 쓸모없는 정보들을 포스팅해보도록 하겠다.&lt;/p&gt;</description></item></channel></rss>