kyle shanks
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    --- title: 'make 編譯管理、編譯軟體' disqus: kyleAlien --- make 編譯管理、編譯軟體 === ## Overview of Content [TOC] ## make 概述 大部分源碼中都不會是單一檔案,所以如果有手動編譯所有檔案、進行連結,那會是一件頗滿煩的事情;而 Unix 中出現了一個 **編譯管理工具 make**,透過它可以幫我們組織要編譯的檔案,並進行有條理的編譯工作 > 通常你在源碼中看到檔名為 `makefile`、`Makefile` 的檔案時,就說明該應用使用 make 進行編譯管理 ### Makefile 概述 * Makefile 是 make 指令的腳本,make 指令會讀取該腳本做相對應的處理 * Makefile 它能更有效的組織編譯工作(**但它並 ++不是編譯++**),它算是一種腳本(類似於 `Window's bat` & `Linux's Shell` 或是 `Python`) **所以 Makefile 重點是在學習它的規則** 1. Makefile 由 Make 工具進行解析,所以必須安裝 `make` 工具 (必要) ```shell= sudo apt install make ``` 2. 安裝 `gcc`、`tree` (下面範例會用到) ```shell= sudo apt install gcc sudo apt install tree ``` :::danger * Window's ubuntu 安裝 gcc 有問題 ? 可以試試看以下方法 ```shell= # 升級軟體 sudo apt-get update # 修正錯誤 sudo apt-get -f install # 設定 Debian configure sudo dpkg --configure -a # 重新安裝 sudo apt install gcc ``` ::: ### Makefile - 腳本格式 * Makefile 腳本格式如下 ```shell= TARGET: Prerequisites Commands ``` 1. `TARGET`(目標): 目標,可以是一個實際檔案,也可以是另一個規則的目標 2. `Prerequisites`(規則): 目標所依賴的所有文件或規則 (可多個) 3. `Commands`(行為、命令): 當 `Prerequisites` 有任何一個比 `TARGET` 新時都會觸發到 Command 命令的執行,具體命令是依照使用者需求所設置 > eg. 調用 `shell`, `gcc`, `java`... 等等 :::warning * **Commands 前必須要有一個 Tab 空格!** ::: ### Makefile 入門 - 簡單使用 * 需要創建 4 個檔案:^1^ `main.c`、^2^ `utility.c`、^3^ `utility.h`、^4^ `Makefile` 文件 | 文件名稱 | 功能 | | -------- | -------- | | `main.c` | 主要檔案,會依賴 utility 標頭檔案 | | `utility.c` | **實現** getNumber() 函數 | | `utility.h` | **宣告** getNumber() 函數 | | `Makefile` | 編譯過程的主導者(組織編譯) | 1. `main.c` 主要在呼叫 getNumber 函數 ```c= #include <stdio.h> // 系統 Lib #include "utility.h" // 自定義 Lib int main(int arg, char* argv[]) { printf("Hello, getNumber=%d\n", getNumber()); } ``` 2. `utility.h` 宣告函數,好讓其他檔案使用 ```c= #ifndef NUMBER #define NUMBER int getNumber(void); #endif ``` 3. `utility.c` 簡單實現函數 ```c= #include "utility.h" int getNumber() { return 2; } ``` 4. `Makefile` 編譯規劃者 ```shell= # 從上到下分析 # 變數:代表了要生成的物件 OBJS=main.o utility.o # 要製作出SimpleMakefile 檔案,就必須先要有 main.o、utility.o 兩個檔案 # 繼續往下尋找 main.o、utility.o 關鍵字 SMmgimpleMakefile: $(OBJS) # 指定 輸出名稱,並配合其他 .o 檔案 gcc $(OBJS) -o SimpleMakefile # 找到 main.o 的製作 main.o: main.c # 編譯 main.c,這個步驟會輸出 main.o gcc -c main.c # 找到 utility.o 的製作 utility.o: utility.c # 編譯 utility.c,這個步驟會輸出 utility.o gcc -c utility.c ``` * 編譯順序 & 關係圖 > ![](https://i.imgur.com/cN4CqJm.png) * 使用 `make` 指令就可以生成 SimpleMakefile 文件 (上面有說明輸出的文件名稱) ```shell= # 執行 Makefile (SimpleMakefile) 文件 make # 查看多了哪些文件 tree . # 執行剛剛輸出的 SimpleMakefile 文件 ./SimpleMakefile ``` > ![](https://i.imgur.com/wi14Pja.png) ### Makefile 引用參數 * 可以在外部宣告參數,在使用 `${}` or `$()` 引用該參數進來 使用 vim 修改 MakeFile 檔案 ```shell= FILE_NAME = SimpleMakefile_2 SimpleMakefile: main.o utility.o gcc -o $(FILE_NAME) main.o utility.o main.o:main.c gcc -c main.c utility.o:utility.c gcc -c utility.c ``` **--make 輸出結果--** > ![](https://i.imgur.com/C1pPraj.png) ### make 內建規則 * make 有自己的內建規則 1. **自動尋找對應檔**: 當只有指定 `.o` 檔,但沒有指定 `.c` 檔時,它會 **自己取找 `.o` 檔的對應 `.c` 檔案**;範例如下: ```shell= OBJS=main.o utility.o SimpleMakefile: $(OBJS) gcc $(OBJS) -o SimpleMakefile ``` 呼叫 make 開始建構 ```shell= make ``` > ![](https://hackmd.io/_uploads/ryMl-rvnh.png) 2. **迭代增量**:它會檢測原始檔是否有更改,**如果沒有更改並已經有 `.o` 檔,則不進行編譯**,這是一種典型的 **反應鏈** ```shell= make # 由於第二次編譯原始檔沒有更動,所以也不會更新 .o 檔 make ``` > ![](https://hackmd.io/_uploads/BkvWMBv2n.png) ### Makefile 高級用法 * Android 的 Makefile 有很多種高級用法,以下舉兩種較常見的用法 1. **`Target-Specific Variable`** (區域型變數): 這是一種局部作用域的變量,它只針對特定目標起效果,以下使用 `build/core/package internal.mk` [**internal.mk**](https://android.googlesource.com/platform/build/+/master/core/package_internal.mk#78) 的部分代碼有 `PRIVATE_DEX_FILE` 關鍵字 ```shell= ## 這裡的 built_dex 就是 `全局變量` ## 若執行 my_dex_jar(Command) 時就會使用到 built_dex,這時若全局變量 build_dex 受到其他的影響導致 ## 其改變時就會影響到 my_dex_jar 的結果 # # 所以需要一個 `區域變數` 也就是 PRIVATE_DEX_FILE ifdef LOCAL_DEX_PREOPT $(my_dex_jar): PRIVATE_DEX_FILE := $(built_dex) # PRIVATE_DEX_FILE 只存在 my_dex_jar 中 $(my_dex_jar): $(built_dex) $(SOONG_ZIP) $(hide) mkdir -p $(dir $@) && rm -f $@ $(call create-dex-jar,$@,$(PRIVATE_DEX_FILE)) endif ``` 2. **`Static Pattern Rules`** 它的經典格式中帶有兩個冒號 `:`,它可以用來過濾 & 產生真正需要依賴的對象 ```shell= ## 指令格式 TARGETS ...: TARGET-PATTERN: DEP-PATTERNS COMMANDS ``` 靜態模式語法,常常被用在多目標的場景 ```shell= # 抓取其中的 *.o 檔案,並輸出為 *.c 檔案 foo.c tcc.o bar.o : %.o : %.c ``` 上面的例子 `TARGET-PATTERN` 帶有 `%` 符號 1. 它會與 `TARGETS` 進行匹配得到 (STEM),也就是從 [foo.c tcc.o bar.o] 中匹配 .o 結尾,結果就是 [tcc.o bar.o] 2. 再與 `DEP-PATTERNS` 的 `%` 符號結合,**最終生成 tcc.c、bar.c 檔案**,原本的 foo.c 就不會進行任何動作 ## 其他 ### make 命令行 以下有有幾個常用的 make 命令 | make option | 功能 | | - | - | | `-n` | 不進行整合編譯,只會顯示建構時會使用到哪些指令(可以安全的檢查) | | `-f` | 改變 make 預設的腳本名 | 1. 查看編譯時會編譯的檔案(查看指令) > ![](https://hackmd.io/_uploads/Bk8SSSv23.png) 2. 指定編譯腳本檔為 MyFile > ![](https://hackmd.io/_uploads/HJqAHrv33.png) ### 標準巨集、變數 * make 指令有許多巨集、變數 :::info * 巨集:可以當作開始執行 make 後不可再改動的數(像是常數的概念) ::: * 常見巨集: | 巨集 | 說明 | 補充 | | -------- | -------- | -------- | | `CFLAGS` | 給 C 編譯器的選項 | make 會將該選項作為參數,用在所有 `.c` -> `.o` 的階段 | | `LDFLAGS` | 給 C 連結時的選項 | - | | `LDLIBS` | 連結時,指定函式庫名 | 配合 `LDFLAGS` 一起使用 | | `CC` | C 編譯器 | 可以指定 `gcc` or `clang`... 等等 | | `CPPFLAGS` | 給 C 預編譯階段的選項 | - | | `CXXFLAGS` | 給 C++ 編譯器的選項 | - | 範例:修改編譯器為 clang ```shell= OBJS=main.o utility.o SimpleMakefile: $(OBJS) # 編譯器改作 CC $(CC) $(OBJS) -o SimpleMakefile ``` > ![](https://hackmd.io/_uploads/SkvHFSvn3.png) * 常見變數:變數會隨著建構目標的設置不同而改變,通常不用在編譯指令時指定 | 巨集 | 說明 | | -------- | -------- | | `$@` | 寫在規則裡時,表示當前目標 (TARGET) | | `$*` | 取得目前目標的名稱 | 範例: ```shell= OBJS=main.o utility.o testParam: $(OBJS) echo "Target name: $@, base name: $*" ``` > ![](https://hackmd.io/_uploads/rJPosBDhn.png) <!-- ### 常規目標 --> ## 從 C 源碼編譯出可執行應用 我們在學習時往往最常忽略的就是 **建構專案的行為**,因為大部分 IDE 都協助我們快速、方便的建構出應用;但 **建構專案** 其實是學習程式設計、軟體開發的必要基礎 > IDE 隱藏了細節,讓我們快但可能也讓我們笨了(不知所以然) ### 安裝軟體 - 概述 在 Linux 系統中,幾乎所有東西都有他的 Source code(源碼),從核心、函式庫、網頁應用... 等等都有 我們大多可以透過 PackageManager 來幫我們安裝,但其實我們也可以自己下載並手動安裝;手動安裝有以下幾點特點 * **優點** 1. 自定義設定 2. 更清楚地知道你的應用細節(該應用需要哪些依賴、設置) 3. 可以安裝 **特定版本** 的應用 4. 可以訂製特別的設定選項,產出特定環境的軟體並分享(像是可以讓 Docker 環境使用) :::warning 但這並不代表建議都手動安裝! ::: * **缺點**:其實透過優點就可以反推缺點 1. 了解細節要付出時間成本的代價(訂製越多代價越大) > 關注細節常常會在一些小設定上出錯,花時間除錯... 2. 自行安裝軟體通常也不會自己更新 > 通常發行版都會跟隨開發者發佈新的軟體而更新 ### 下載源碼包 - sqlite * 我們嘗試自己下載 [**sqlite**](https://github.com/sqlite/sqlite) 源碼包; 1. 下載 sqlite 源碼 ```shell= wget https://github.com/sqlite/sqlite/archive/refs/heads/master.zip ``` 2. 解壓 zip 包 ```shell= # 先查看 zip 內容 # 如果沒有單獨資料夾,建議創建一個資料夾再解壓 unzip -l ./master.zip | head unzip -q ./master.zip cd sqlite-master ``` > ![](https://hackmd.io/_uploads/HJrJT9hh3.png) :::danger * **預防惡意程式碼** 預先查看壓縮包其實相當重要,如果預覽中方現絕對路徑的檔案,那非常危險(如果是強制改變你的 `/etc/passwd`, `/etc/inetd.conf`... 等等設定就相當危險!) ::: ### 源碼文件 - sqlite * 接著我們來安裝剛剛解壓的 `sqlite` 源碼包 1. 在進入解壓目錄後我們首先要做的 **觀察、閱讀檔案**;重點檔案如下 | 重點檔案 | 功能 | | - | - | | README | 該檔案是一定要看的檔案,基本上會有包的 **描述、手冊、安裝提示... 等等資訊** | | INSTALL | 內有編譯、安裝軟體的指令 | ```shell= # 閱讀 README 檔案 cat README.md | less ``` 其中一段內容可以看到它教我們如何編譯該應用 > ![](https://hackmd.io/_uploads/Hy6Bkoh23.png) 2. **有關 make 的檔案** | 檔案 | 說明 | | - | - | | Makefile | 如上面小節所說,用來集成源碼編譯的腳本 | | configure | `GNU autoconf` 讀取的設定檔 | | Makefile.in | 由 `GNU autoconf` 工具產出的腳本 | | CMakeList.txt | `CMake` 讀取的設定檔,最終產出 `Makefile` | 舊的軟體會自己編寫 `Makefile`,並有時候會需要我們自己手動去修改 `Makefile`,但現在大多數是透過某些工具自動產出 `Makefile` * **GNU autoconf**:透過 `configure` 設定檔來產出對應的 `Makefile.in` 檔案 * **CMake**:透過 `CMakeList.txt` 設定檔產出對應的 `Makefile` 檔案 3. 源碼相關檔案 | 檔案 | 說明 | | - | - | | `.c`、`.h`、`.cc` | 這些檔案大多(非一定)是 C 原始碼檔案 | | `.o` | `.o` 代表的是物件檔(二進制檔案),比較少出現,如果有的話通常代表該包的依賴庫 | ### 產出 Makefile 工具 - GNU autoconf * 在編譯源碼之前,我們先了解一下編譯腳本工具的發展 * 雖然 C 作為高級語言(擁有可移植不同平台的特性),但對於不同平台通常需要不同的參數、選項設置,以往對於不同平台會有不同平台的 `Makefile` 檔案 由於這種不方便性,導致衍生出了 **腳本分析系統**:它會針對不同平台自動產出對應的 `Makefile` 程式,而 `GNU autoconf` 就是其中一個工具 如果使用此系統的包含以下檔案,通常就是可使用 `GNU autoconf` | 檔案 | 說明 | | - | - | | configure | `GNU autoconf` 讀取的設定檔 | | Makefile.in | `Makefile` 範本 | | config.h.in | `config.h` 範本 | * 通常只要執行以下指令就可以從產出 `Makefile.in` 產出對應的 `Makefile` 產出 `configure` 前 > ![](https://hackmd.io/_uploads/SycHPo323.png) 產出 `configure` 後,可以看到 `Makefile` 的產生,並有許多 Make 任務 ```shell= ./configure ``` > ![](https://hackmd.io/_uploads/B1Rsvj233.png) :::success * 如果沒有這些功能請使用以下指令安裝必要的編譯工具 ```shell= sudo apt install build-essential ``` ::: * **auoconfig 重點資訊** 1. **`configure` 選項**:關於 `configure` 的一些設定,我們應該要知道一些常用的選項,選項、功能如下 | 選項 | 功能 | 補充 | | -------- | -------- | - | | `--prefix=<位置>` | 指定 Makefile 內對於應用設定的安裝位置 | 預設應用會裝到 `/usr/bin`、二進位檔案會安裝到 `/usr/local/bin`、函數庫會安裝到 `/usr/local/lib` 目錄 | | `--bindir=<dir>` | 指定二進位檔案放置的位置 | - | | `--sbindir=<dir>` | 指定系統二進位檔案放置的位置 | - | | `--libdir=<dir>` | 指定函式庫放置的位置 | - | | `--with-package=<dir>` | 指定編譯時需要依賴包的位置 | 通常是某個函式庫不在標準位置時使用 | | `--disable-shared` | 不產生共享函式庫(`.so` 檔) | | 2. **autoconf 自動產生的 Makefile 的 Make 任務**: | 選項 | 功能 | | -------- | -------- | | `clean` | 清除所有物件檔案(`.o` files)、可執行程式、函式庫 | | `distclean` | 清除自動產生的所有東西(`Makefile`、`config.h`、`config.log`... 等等),恢復至原始狀態 | | `check` | 執行內建的測試,**檢查編譯其產生的程式的可行性** | | `install-strip` | 安裝時移除函式庫中多餘的除錯資訊、多餘符號,這可以讓程式佔有較少空間 | 3. **autoconf 日誌檔案**: 如果 `configure` 過程出錯可以去查看 `config.log` 檔案 > 由於訊息量很大,建議從底部開始尋找 (搜尋看看 `for more details` 字串看看) ### 手動安裝 (指定位置) - sqlite * 根據上述的 `configure` 選項,我們來重新產出 `Makefile` 檔案,使用指令如下 1. 創建一個目標目錄(`myDir`),等等安裝時就安裝在該目錄下 ```shell= mkdir myDir # 安裝到指定目錄 ./configure --prefix=`pwd`/myDir ``` > ![](https://hackmd.io/_uploads/rkGunjh22.png) 確認 myDir 是否已經設定進 Makefile > ![](https://hackmd.io/_uploads/Hk2hhsn3n.png) 2. **安裝 sqlite 包** ```shell= # 先確認會安裝哪些東西 make -n install make install ls -laF myDir/ tree myDir/ ``` 可以看到可執行(二進位檔)檔安裝在指定目錄下的 `/bin` 目錄之下(`sqlite3`) > ![](https://hackmd.io/_uploads/rykUy2332.png) ### library 依賴 - pc 檔 * 一個應用基本上都會依賴於另 N 個可執行程式(不太會重造輪胎,除非有這個必要性);就像 Git 源碼的依賴包:`Curl`、`Zlib`、`Openssl`、`Expat`、`Libiconv`... ``` mermaid graph TD; Git應用-->Curl; Git應用-->Zlib; Git應用-->Openssl; Git應用-->Expat; Git應用-->Libiconv; ``` * 我們可以透過 `pkg-config` 指令查看目標應用都依賴些什麼庫 ```shell= pkg-config --libs ./myDir/bin/sqlite3 ``` 而 `pkg-config` 的運作方式,其實是去讀取應用對應的 `<應用>.pc` 檔 ```shell= cat ./myDir/lib/pkgconfig/sqlite3.pc ``` > ![](https://hackmd.io/_uploads/ry_qOh2n2.png) :::success * **找不到 `.pc` 檔**? 1. 可以創建 `.pc` 連接檔案到目標 `/lib/pkgconfig` 目錄中 2. 指定環境變數 `PKG_CONFIG_PATH` ::: ## Appendix & FAQ :::info ::: ###### tags: `Linux 基礎` `C`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully