<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 練習26:編寫第一個真正的程序 > 原文:[Exercise 26: Write A First Real Program](http://c.learncodethehardway.org/book/ex26.html) > 譯者:[飛龍](https://github.com/wizardforcel) 這本書你已經完成一半了,所以你需要做一個期中檢測。期中檢測中你需要重新構建一個我特地為本書編寫的軟件,叫做`devpkg`。隨后你需要以一些方式擴展它,并且通過編寫一些單元測試來改進代碼。 > 注 > 我在一些你需要完成的練習之前編寫了這個練習。如果你現在嘗試這個練習,記住軟件可能會含有一些bug,你可能由于我的錯誤會產生一些問題,也可能不知道需要什么來完成它。如果這樣的話,通過[help@learncodethehardway.org](mailto:help@learncodethehardway.org)來告訴我,之后等待我寫完其它練習。 ## 什么是`devpkg`? `devpkg`是一個簡單的C程序,可以用于安裝其它軟件。我特地為本書編寫了它,作為一種方式來教你真正的軟件是如何構建的,以及如何復用他人的庫。它使用了一個叫做[Apache可移植運行時(APR)](http://apr.apache.org/)的庫,其中含有許多工作跨平臺的便利的C函數,包括Windows。此外,它只是從互聯網(或本地文件)抓取代碼,并且執行通常的`./configure ; make ; make install`命令,每個程序員都用到過。 這個練習中,你的目標是從源碼構建`devpkg`,完成我提供的每個挑戰,并且使用源碼來理解`devpkg`做了什么和為什么這樣做。 ## 我們打算創建什么 我們打算創建一個具有三個命令的工具: devpkg -S 在電腦上安裝新的軟件。 devpkg -I 從URL安裝軟件。 devpkg -L 列出安裝的所有軟件。 devpkg -F 為手動構建抓取源代碼。 devpkg -B 構建所抓取的源碼代碼并且安裝它,即使它已經安裝了。 我們想讓`devpkg`能夠接受幾乎任何URL,判斷項目的類型,下載,安裝,以及注冊已經安裝的軟件。我們也希望它能夠處理一個簡單的依賴列表,以便它能夠安裝項目所需的所有軟件。 ## 設計 為了完成這一目標,`devpkg`具有非常簡單的設計: 使用外部命令 大多數工作都是通過類似于`curl`、`git`和`tar`的外部命令完成的。這樣減少了`devpkg`所需的代碼量。 簡單的文件數據庫 你可以輕易使它變得很復雜,但是一開始你需要完成一個簡單的文件數據庫,位于`/usr/local/.devpkg/db`,來跟蹤已安裝的軟件。 `/usr/local` 同樣你可以使它更高級,但是對于初學者來說,假設項目始終位于`/usr/local`中,它是愛多數Unix軟件的標準安裝目錄。 `configure; make; make install` 假設大多數軟件可以通過`configure; make; make install`來安裝,也許`configure`是可選的。如果軟件不能通過這種方式安裝,要么提供某種方式來修改命令,要么`devpkg`就可以無視它。 用戶可以root 我們假設用于可以使用`sudo`來提升至root權限,除非他們直到最后才想root。 這會使我們的程序像當初設想的一樣簡單,并且對于它的功能來說已經足夠了。之后你可以進一步修改它。 ## Apache 可移植運行時 你需要做的另外一件事情就是使用[Apache可移植運行時(APR)](http://apr.apache.org/)來未完成這個練習獲得一個可移植的工具集。APR并不是必要的,你也可以不用它,但是你需要寫的代碼就會非常多。我現在強制你使用APR,使你能夠熟悉鏈接和使用其他的庫。最后,APR也能在Windows上工作,所以你可以把它遷移到許多其它平臺上。 你應該獲取`apr-1.4.5`和`apr-util-1.3`的庫,以及瀏覽在[apr.apache.org主站](http://apr.apache.org/)上的文檔。 下面是一個ShellScript,用于安裝所需的所有庫。你應該手動將它寫到一個文件中,之后運行它直到APR安裝好并且沒有任何錯誤。 ```sh set -e # go somewhere safe cd /tmp # get the source to base APR 1.4.6 curl -L -O http://archive.apache.org/dist/apr/apr-1.4.6.tar.gz # extract it and go into the source tar -xzvf apr-1.4.6.tar.gz cd apr-1.4.6 # configure, make, make install ./configure make sudo make install # reset and cleanup cd /tmp rm -rf apr-1.4.6 apr-1.4.6.tar.gz # do the same with apr-util curl -L -O http://archive.apache.org/dist/apr/apr-util-1.4.1.tar.gz # extract tar -xzvf apr-util-1.4.1.tar.gz cd apr-util-1.4.1 # configure, make, make install ./configure --with-apr=/usr/local/apr # you need that extra parameter to configure because # apr-util can't really find it because...who knows. make sudo make install #cleanup cd /tmp rm -rf apr-util-1.4.1* apr-1.4.6* ``` 我希望你輸入這個腳本,因為這就是`devpkg`基本上所做的事情,只是帶有了一些選項和檢查項。實際上,你可以使用Shell以更少的代碼來完成它,但是這對于一本C語言的書不是一個很好的程序。 簡單運行這個腳本,修復它直到正常工作,就完成的所有庫的安裝,之后你需要完成項目的剩下部分。 ## 項目布局 你需要創建一些簡單的項目文件來起步。下面是我通常創建一個新項目的方法: ```sh mkdir devpkg cd devpkg touch README Makefile ``` ## 其它依賴 你應該已經安裝了APR和APR-util,所以你需要一些更多的文件作為基本的依賴: + 練習20中的`dbg.h`。 + 從[http://bstring.sourceforge.net/](http://bstring.sourceforge.net/)下載的`bstrlib.h`和`bstrlib.c`。下載`.zip`文件,解壓并且將這個兩個文件拷貝到項目中。 + 運行`make bstrlib.o`,如果這不能正常工作,閱讀下面的“修復`bstring`”指南。 > 注 > 在一些平臺上`bstring.c`文件會出現下列錯誤: > ```sh > bstrlib.c:2762: error: expected declaration specifiers or '...' before numeric constant > ``` > 這是由于作者使用了一個不好的定義,它在一些平臺上不能工作。你需要修改第2759行的`#ifdef __GNUC__`,并把它改成: > ```c > #if defined(__GNUC__) && !defined(__APPLE__) > ``` 之后在Mac OSX平臺上就應該能夠正常工作了。 做完上面這些后,你應該有了`Makefile`,`README`,`dbg.h`,`bstrlib.h`和`bstrlib.c`,并做好了準備。 ## Makefile 我們最好從`Makefile`開始,因為它列出了項目如何構建,以及你會創建哪些源文件。 ```Makefile PREFIX?=/usr/local CFLAGS=-g -Wall -I${PREFIX}/apr/include/apr-1 -I${PREFIX}/apr/include/apr-util-1 LDFLAGS=-L${PREFIX}/apr/lib -lapr-1 -pthread -laprutil-1 all: devpkg devpkg: bstrlib.o db.o shell.o commands.o install: all install -d $(DESTDIR)/$(PREFIX)/bin/ install devpkg $(DESTDIR)/$(PREFIX)/bin/ clean: rm -f *.o rm -f devpkg rm -rf *.dSYM ``` 比起之前看到過的,這并沒有什么新東西,除了可能有些奇怪的`?=`語法,它表示“如果之前沒有定義,就將`PREFIX`設置為該值”。 > 注 > 如果你使用了最近版本的Ubuntu,你會得到`apr_off_t` 或 `off64_t`的錯誤,之后需要向`CFLAGS`添加`-D_LARGEFILE64_SOURCE=1`。 > 所需的另一件事是,你需要向`/etc/ld.conf.so.d/`添加`/usr/local/apr/lib`,之后運行`ldconfig`使它能夠選擇正常的庫。 ## 源文件 我們可以從`makefile`中看到,`devpkg`有四個依賴項,它們是: `bstrlib.o` 由`bstrlib.c`和`bstrlib.o`產生,你已經將它們引入了。 `db.o` 由`db.c`和`db.h`產生,它包含了一個小型“數據庫”程序集的代碼。 `shell.o` 由`shell.c`和`shell.h`產生,包含一些函數,是類似`curl`的一些命令運行起來更容易。 `commands.o` 由`commands.c`和`commands.h`產生,包含了`devpkg`所需的所有命令并使它更易用。 `devpkg` 它不會顯式提到,但是它是`Makefile`在這一部分的目標。它由`devpkg.c`產生,包含用于整個程序的`main`函數。 你的任務就是創建這些文件,并且輸入代碼并保證正確。 > 注 > 你讀完這個描述可能會想,“Zed為什么那么聰明,坐著就能設計出來這些文件?!”我并不是用我強大的代碼功力魔術般地把`devpkg`設計成這樣。而是我做了這些: > + 我編寫了簡單的`README`來獲得如何構建項目的靈感。 > + 我創建了一個簡單的bash腳本(就像你編寫的那樣)來理清所需的所有組件。 > + 我創建了一個`.c`文件,并且在它上面花了幾天,醞釀并想出點子。 > + 接著我編寫并調試程序,之后我將這一個大文件分成四個文件。 > + 做完這些之后,我重命名和優化了函數和數據結構,使它們在邏輯上更“美觀”。 > + 最后,使新程序成功并以相同方式工作之后,我添加了一些新的特性,比如`-F`和`-B`選項。 > 你讀到的這份列表是我打算教給你的,但不要認為這是我構建軟件的通用方法。有時候我會事先知道主題,并且會做更多的規劃。也有時我會編寫一份規劃并將它扔掉,之后再規劃更好的版本。它完全取決于我的經驗告訴我哪個比較好,或者我的靈感將我帶到何處。 > 如果你碰到一個“專家”,它告訴你只有一個方法可以解決編程問題,那么它在騙你。要么它們實際使用了很多策略,要么他們并不足夠好。 ## DB函數 程序中必須有個方法來記錄已經安裝的URL,列出這些URL,并且檢查一些程序是否已安裝以便跳過。我會使用一個簡單、扁平化的文件數據庫,以及`bstrlib.h`。 首先,創建`db.h`頭文件,以便讓你知道需要實現什么。 ```c #ifndef _db_h #define _db_h #define DB_FILE "/usr/local/.devpkg/db" #define DB_DIR "/usr/local/.devpkg" int DB_init(); int DB_list(); int DB_update(const char *url); int DB_find(const char *url); #endif ``` 之后實現`db.c`中的這些函數,在你編寫它的時候,像之前一樣使用`make`。 ```c #include <unistd.h> #include <apr_errno.h> #include <apr_file_io.h> #include "db.h" #include "bstrlib.h" #include "dbg.h" static FILE *DB_open(const char *path, const char *mode) { return fopen(path, mode); } static void DB_close(FILE *db) { fclose(db); } static bstring DB_load() { FILE *db = NULL; bstring data = NULL; db = DB_open(DB_FILE, "r"); check(db, "Failed to open database: %s", DB_FILE); data = bread((bNread)fread, db); check(data, "Failed to read from db file: %s", DB_FILE); DB_close(db); return data; error: if(db) DB_close(db); if(data) bdestroy(data); return NULL; } int DB_update(const char *url) { if(DB_find(url)) { log_info("Already recorded as installed: %s", url); } FILE *db = DB_open(DB_FILE, "a+"); check(db, "Failed to open DB file: %s", DB_FILE); bstring line = bfromcstr(url); bconchar(line, '\n'); int rc = fwrite(line->data, blength(line), 1, db); check(rc == 1, "Failed to append to the db."); return 0; error: if(db) DB_close(db); return -1; } int DB_find(const char *url) { bstring data = NULL; bstring line = bfromcstr(url); int res = -1; data = DB_load(); check(data, "Failed to load: %s", DB_FILE); if(binstr(data, 0, line) == BSTR_ERR) { res = 0; } else { res = 1; } error: // fallthrough if(data) bdestroy(data); if(line) bdestroy(line); return res; } int DB_init() { apr_pool_t *p = NULL; apr_pool_initialize(); apr_pool_create(&p, NULL); if(access(DB_DIR, W_OK | X_OK) == -1) { apr_status_t rc = apr_dir_make_recursive(DB_DIR, APR_UREAD | APR_UWRITE | APR_UEXECUTE | APR_GREAD | APR_GWRITE | APR_GEXECUTE, p); check(rc == APR_SUCCESS, "Failed to make database dir: %s", DB_DIR); } if(access(DB_FILE, W_OK) == -1) { FILE *db = DB_open(DB_FILE, "w"); check(db, "Cannot open database: %s", DB_FILE); DB_close(db); } apr_pool_destroy(p); return 0; error: apr_pool_destroy(p); return -1; } int DB_list() { bstring data = DB_load(); check(data, "Failed to read load: %s", DB_FILE); printf("%s", bdata(data)); bdestroy(data); return 0; error: return -1; } ``` ### 挑戰1:代碼復查 在繼續之前,仔細閱讀這些文件的每一行,并且確保你以準確地輸入了它們。通過逐行閱讀代碼來實踐它。同時,跟蹤每個函數調用,并且確保你使用了`check`來校驗返回值。最后,在APR網站上的文檔,或者bstrlib.h 或 bstrlib.c的源碼中,查閱每個你不認識的函數。 ## Shell 函數 `devkpg`的一個關鍵設計是,使用類似于`curl`、`tar`和`git`的外部工具來完成大部分的工作。我們可以找到在程序內部完成這些工作的庫,但是如果我們只是需要這些程序的基本功能,這樣就毫無意義。在Unix運行其它命令并不丟人。 為了完成這些,我打算使用`apr_thread_proc.h`函數來運行程序,但是我也希望創建一個簡單的類“模板”系統。我會使用`struct Shell`,它持有所有運行程序所需的信息,但是在參數中有一些“空位”,我可以將它們替換成實際值。 觀察`shell.h`文件來了解我會用到的結構和命令。你可以看到我使用`extern`來表明其他的`.c`文件也能訪問到`shell.c`中定義的變量。 ```c #ifndef _shell_h #define _shell_h #define MAX_COMMAND_ARGS 100 #include <apr_thread_proc.h> typedef struct Shell { const char *dir; const char *exe; apr_procattr_t *attr; apr_proc_t proc; apr_exit_why_e exit_why; int exit_code; const char *args[MAX_COMMAND_ARGS]; } Shell; int Shell_run(apr_pool_t *p, Shell *cmd); int Shell_exec(Shell cmd, ...); extern Shell CLEANUP_SH; extern Shell GIT_SH; extern Shell TAR_SH; extern Shell CURL_SH; extern Shell CONFIGURE_SH; extern Shell MAKE_SH; extern Shell INSTALL_SH; #endif ``` 確保你已經創建了`shell.h`,并且`extern Shell`變量的名字和數量相同。它們被`Shell_run`和`Shell_exec`函數用于運行命令。我定義了這兩個函數,并且在`shell.c`中創建實際變量。 ```c #include "shell.h" #include "dbg.h" #include <stdarg.h> int Shell_exec(Shell template, ...) { apr_pool_t *p = NULL; int rc = -1; apr_status_t rv = APR_SUCCESS; va_list argp; const char *key = NULL; const char *arg = NULL; int i = 0; rv = apr_pool_create(&p, NULL); check(rv == APR_SUCCESS, "Failed to create pool."); va_start(argp, template); for(key = va_arg(argp, const char *); key != NULL; key = va_arg(argp, const char *)) { arg = va_arg(argp, const char *); for(i = 0; template.args[i] != NULL; i++) { if(strcmp(template.args[i], key) == 0) { template.args[i] = arg; break; // found it } } } rc = Shell_run(p, &template); apr_pool_destroy(p); va_end(argp); return rc; error: if(p) { apr_pool_destroy(p); } return rc; } int Shell_run(apr_pool_t *p, Shell *cmd) { apr_procattr_t *attr; apr_status_t rv; apr_proc_t newproc; rv = apr_procattr_create(&attr, p); check(rv == APR_SUCCESS, "Failed to create proc attr."); rv = apr_procattr_io_set(attr, APR_NO_PIPE, APR_NO_PIPE, APR_NO_PIPE); check(rv == APR_SUCCESS, "Failed to set IO of command."); rv = apr_procattr_dir_set(attr, cmd->dir); check(rv == APR_SUCCESS, "Failed to set root to %s", cmd->dir); rv = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH); check(rv == APR_SUCCESS, "Failed to set cmd type."); rv = apr_proc_create(&newproc, cmd->exe, cmd->args, NULL, attr, p); check(rv == APR_SUCCESS, "Failed to run command."); rv = apr_proc_wait(&newproc, &cmd->exit_code, &cmd->exit_why, APR_WAIT); check(rv == APR_CHILD_DONE, "Failed to wait."); check(cmd->exit_code == 0, "%s exited badly.", cmd->exe); check(cmd->exit_why == APR_PROC_EXIT, "%s was killed or crashed", cmd->exe); return 0; error: return -1; } Shell CLEANUP_SH = { .exe = "rm", .dir = "/tmp", .args = {"rm", "-rf", "/tmp/pkg-build", "/tmp/pkg-src.tar.gz", "/tmp/pkg-src.tar.bz2", "/tmp/DEPENDS", NULL} }; Shell GIT_SH = { .dir = "/tmp", .exe = "git", .args = {"git", "clone", "URL", "pkg-build", NULL} }; Shell TAR_SH = { .dir = "/tmp/pkg-build", .exe = "tar", .args = {"tar", "-xzf", "FILE", "--strip-components", "1", NULL} }; Shell CURL_SH = { .dir = "/tmp", .exe = "curl", .args = {"curl", "-L", "-o", "TARGET", "URL", NULL} }; Shell CONFIGURE_SH = { .exe = "./configure", .dir = "/tmp/pkg-build", .args = {"configure", "OPTS", NULL}, }; Shell MAKE_SH = { .exe = "make", .dir = "/tmp/pkg-build", .args = {"make", "OPTS", NULL} }; Shell INSTALL_SH = { .exe = "sudo", .dir = "/tmp/pkg-build", .args = {"sudo", "make", "TARGET", NULL} }; ``` 自底向上閱讀`shell.c`的代碼(這也是常見的C源碼布局),你會看到我創建了實際的`Shell`變量,它在`shell.h`中以`extern`修飾。它們雖然在這里,但是也被程序的其它部分使用。這就是創建全局變量的方式,它們可以存在于一個`.c`文件中,但是可在任何地方使用。你不應該創建很多這類變量,但是它們的確很方便。 繼續閱讀代碼,我們讀到了`Shell_run`,它是一個“基”函數,只是基于`Shell`中的東西執行命令。它使用了許多在`apr_thread_proc.h`中定義的函數,你需要查閱它們的每一個來了解工作原理。這就像是一些使用`system`函數調用的代碼一樣,但是它可以讓你控制其他程序的執行。例如,在我們的`Shell`結構中,存在`.dir`屬性在運行之前強制程序必須在指定目錄中。 最后,我創建了`Shell_exec`函數,它是個變參函數。你在之前已經看到過了,但是確保你理解了`stdarg.h`函數以及如何編寫它們。在下個挑戰中你需要分析這一函數。 ### 挑戰2:分析`Shell_exec` 為這些文件(以及向挑戰1那樣的完整的代碼復查)設置的挑戰是完整分析`Shell_exec`,并且拆分代碼來了解工作原理。你應該能夠理解每一行代碼,`for`循環如何工作,以及參數如何被替換。 一旦你分析完成,向`struct Shell`添加一個字段,提供需要替代的`args`變量的數量。更新所有命令來接受參數的正確數量,隨后增加一個錯誤檢查,來確認參數被正確替換,以及在錯誤時退出。 ## 命令行函數 現在你需要構造正確的命令來完成功能。這些命令會用到APR的函數、`db.h`和`shell.h`來執行下載和構建軟件的真正工作。這些文件最為復雜,所以要小心編寫它們。你需要首先編寫`commands.h`文件,接著在`commands.c`文件中實現它的函數。 ```c #ifndef _commands_h #define _commands_h #include <apr_pools.h> #define DEPENDS_PATH "/tmp/DEPENDS" #define TAR_GZ_SRC "/tmp/pkg-src.tar.gz" #define TAR_BZ2_SRC "/tmp/pkg-src.tar.bz2" #define BUILD_DIR "/tmp/pkg-build" #define GIT_PAT "*.git" #define DEPEND_PAT "*DEPENDS" #define TAR_GZ_PAT "*.tar.gz" #define TAR_BZ2_PAT "*.tar.bz2" #define CONFIG_SCRIPT "/tmp/pkg-build/configure" enum CommandType { COMMAND_NONE, COMMAND_INSTALL, COMMAND_LIST, COMMAND_FETCH, COMMAND_INIT, COMMAND_BUILD }; int Command_fetch(apr_pool_t *p, const char *url, int fetch_only); int Command_install(apr_pool_t *p, const char *url, const char *configure_opts, const char *make_opts, const char *install_opts); int Command_depends(apr_pool_t *p, const char *path); int Command_build(apr_pool_t *p, const char *url, const char *configure_opts, const char *make_opts, const char *install_opts); #endif ``` `commands.h`中并沒有很多之前沒見過的東西。你應該看到了一些字符串的定義,它們在任何地方都會用到。真正的代碼在`commands.c`中。 ```c #include <apr_uri.h> #include <apr_fnmatch.h> #include <unistd.h> #include "commands.h" #include "dbg.h" #include "bstrlib.h" #include "db.h" #include "shell.h" int Command_depends(apr_pool_t *p, const char *path) { FILE *in = NULL; bstring line = NULL; in = fopen(path, "r"); check(in != NULL, "Failed to open downloaded depends: %s", path); for(line = bgets((bNgetc)fgetc, in, '\n'); line != NULL; line = bgets((bNgetc)fgetc, in, '\n')) { btrimws(line); log_info("Processing depends: %s", bdata(line)); int rc = Command_install(p, bdata(line), NULL, NULL, NULL); check(rc == 0, "Failed to install: %s", bdata(line)); bdestroy(line); } fclose(in); return 0; error: if(line) bdestroy(line); if(in) fclose(in); return -1; } int Command_fetch(apr_pool_t *p, const char *url, int fetch_only) { apr_uri_t info = {.port = 0}; int rc = 0; const char *depends_file = NULL; apr_status_t rv = apr_uri_parse(p, url, &info); check(rv == APR_SUCCESS, "Failed to parse URL: %s", url); if(apr_fnmatch(GIT_PAT, info.path, 0) == APR_SUCCESS) { rc = Shell_exec(GIT_SH, "URL", url, NULL); check(rc == 0, "git failed."); } else if(apr_fnmatch(DEPEND_PAT, info.path, 0) == APR_SUCCESS) { check(!fetch_only, "No point in fetching a DEPENDS file."); if(info.scheme) { depends_file = DEPENDS_PATH; rc = Shell_exec(CURL_SH, "URL", url, "TARGET", depends_file, NULL); check(rc == 0, "Curl failed."); } else { depends_file = info.path; } // recursively process the devpkg list log_info("Building according to DEPENDS: %s", url); rv = Command_depends(p, depends_file); check(rv == 0, "Failed to process the DEPENDS: %s", url); // this indicates that nothing needs to be done return 0; } else if(apr_fnmatch(TAR_GZ_PAT, info.path, 0) == APR_SUCCESS) { if(info.scheme) { rc = Shell_exec(CURL_SH, "URL", url, "TARGET", TAR_GZ_SRC, NULL); check(rc == 0, "Failed to curl source: %s", url); } rv = apr_dir_make_recursive(BUILD_DIR, APR_UREAD | APR_UWRITE | APR_UEXECUTE, p); check(rv == APR_SUCCESS, "Failed to make directory %s", BUILD_DIR); rc = Shell_exec(TAR_SH, "FILE", TAR_GZ_SRC, NULL); check(rc == 0, "Failed to untar %s", TAR_GZ_SRC); } else if(apr_fnmatch(TAR_BZ2_PAT, info.path, 0) == APR_SUCCESS) { if(info.scheme) { rc = Shell_exec(CURL_SH, "URL", url, "TARGET", TAR_BZ2_SRC, NULL); check(rc == 0, "Curl failed."); } apr_status_t rc = apr_dir_make_recursive(BUILD_DIR, APR_UREAD | APR_UWRITE | APR_UEXECUTE, p); check(rc == 0, "Failed to make directory %s", BUILD_DIR); rc = Shell_exec(TAR_SH, "FILE", TAR_BZ2_SRC, NULL); check(rc == 0, "Failed to untar %s", TAR_BZ2_SRC); } else { sentinel("Don't now how to handle %s", url); } // indicates that an install needs to actually run return 1; error: return -1; } int Command_build(apr_pool_t *p, const char *url, const char *configure_opts, const char *make_opts, const char *install_opts) { int rc = 0; check(access(BUILD_DIR, X_OK | R_OK | W_OK) == 0, "Build directory doesn't exist: %s", BUILD_DIR); // actually do an install if(access(CONFIG_SCRIPT, X_OK) == 0) { log_info("Has a configure script, running it."); rc = Shell_exec(CONFIGURE_SH, "OPTS", configure_opts, NULL); check(rc == 0, "Failed to configure."); } rc = Shell_exec(MAKE_SH, "OPTS", make_opts, NULL); check(rc == 0, "Failed to build."); rc = Shell_exec(INSTALL_SH, "TARGET", install_opts ? install_opts : "install", NULL); check(rc == 0, "Failed to install."); rc = Shell_exec(CLEANUP_SH, NULL); check(rc == 0, "Failed to cleanup after build."); rc = DB_update(url); check(rc == 0, "Failed to add this package to the database."); return 0; error: return -1; } int Command_install(apr_pool_t *p, const char *url, const char *configure_opts, const char *make_opts, const char *install_opts) { int rc = 0; check(Shell_exec(CLEANUP_SH, NULL) == 0, "Failed to cleanup before building."); rc = DB_find(url); check(rc != -1, "Error checking the install database."); if(rc == 1) { log_info("Package %s already installed.", url); return 0; } rc = Command_fetch(p, url, 0); if(rc == 1) { rc = Command_build(p, url, configure_opts, make_opts, install_opts); check(rc == 0, "Failed to build: %s", url); } else if(rc == 0) { // no install needed log_info("Depends successfully installed: %s", url); } else { // had an error sentinel("Install failed: %s", url); } Shell_exec(CLEANUP_SH, NULL); return 0; error: Shell_exec(CLEANUP_SH, NULL); return -1; } ``` 在你輸入并編譯它之后,就可以開始分析了。如果到目前為止你完成了前面的挑戰,你會理解如何使用`shell.c`函數來運行shell命令,以及參數如何被替換。如果沒有則需要回退到前面的挑戰,確保你真正理解了`Shell_exec`的工作原理。 ### 挑戰3:評判我的設計 像之前一樣,完整地復查一遍代碼來保證一模一樣。接著瀏覽每個函數并且確保你知道他如何工作。你也應該跟蹤這個文件或其它文件中,每個函數對其它函數的調用。最后,確認你理解了這里的所有調用APR的函數。 一旦你正確編寫并分析了這個文件,把我當成一個傻瓜一樣來評判我的設計,我需要看看你是否可以改進它。不要真正修改代碼,只是創建一個`notes.txt`并且寫下你的想法和你需要修改的地方。 ## `devpkg`的`main`函數 `devpkg.c`是最后且最重要的,但是也可能是最簡單的文件,其中創建了`main`函數。沒有與之配套的`.h`文件,因為這個文件包含其他所有文件。這個文件用于創建`devpkg`可執行程序,同時組裝了來自`Makefile`的其它`.o`文件。在文件中輸入代碼并保證正確。 ```c #include <stdio.h> #include <apr_general.h> #include <apr_getopt.h> #include <apr_strings.h> #include <apr_lib.h> #include "dbg.h" #include "db.h" #include "commands.h" int main(int argc, const char const *argv[]) { apr_pool_t *p = NULL; apr_pool_initialize(); apr_pool_create(&p, NULL); apr_getopt_t *opt; apr_status_t rv; char ch = '\0'; const char *optarg = NULL; const char *config_opts = NULL; const char *install_opts = NULL; const char *make_opts = NULL; const char *url = NULL; enum CommandType request = COMMAND_NONE; rv = apr_getopt_init(&opt, p, argc, argv); while(apr_getopt(opt, "I:Lc:m:i:d:SF:B:", &ch, &optarg) == APR_SUCCESS) { switch (ch) { case 'I': request = COMMAND_INSTALL; url = optarg; break; case 'L': request = COMMAND_LIST; break; case 'c': config_opts = optarg; break; case 'm': make_opts = optarg; break; case 'i': install_opts = optarg; break; case 'S': request = COMMAND_INIT; break; case 'F': request = COMMAND_FETCH; url = optarg; break; case 'B': request = COMMAND_BUILD; url = optarg; break; } } switch(request) { case COMMAND_INSTALL: check(url, "You must at least give a URL."); Command_install(p, url, config_opts, make_opts, install_opts); break; case COMMAND_LIST: DB_list(); break; case COMMAND_FETCH: check(url != NULL, "You must give a URL."); Command_fetch(p, url, 1); log_info("Downloaded to %s and in /tmp/", BUILD_DIR); break; case COMMAND_BUILD: check(url, "You must at least give a URL."); Command_build(p, url, config_opts, make_opts, install_opts); break; case COMMAND_INIT: rv = DB_init(); check(rv == 0, "Failed to make the database."); break; default: sentinel("Invalid command given."); } return 0; error: return 1; } ``` ### 挑戰4:README 和測試文件 為這個文件設置的挑戰是理解參數如何處理,以及參數是什么,之后創建含有使用指南的`README`文件。在編寫`README`的同時,也編寫一個簡單的simple.sh`,它運行`./devpkg`來檢查每個命令都在實際環境下工作。在你的腳本頂端使用`set -e`,使它跳過第一個錯誤。 最后,在`Valgrind`下運行程序,確保在進行下一步之前,所有東西都能正常運行。 ## 期中檢測 最后的挑戰就是這個期中檢測,它包含三件事情: + 將你的代碼與我的在線代碼對比,以100%的分數開始,每錯一行減去1%。 + 在你的`notes.txt`中記錄你是如何改進代碼和`devpkg`的功能,并且實現你的改進。 + 編寫一個`devpkg`的替代版本,使用其他你喜歡的語言,或者你覺得最適合編寫它的語言。對比二者,之后基于你的結果改進你的`devpkg`的C版本。 你可以執行下列命令來將你的代碼與我的對比: ```sh cd .. # get one directory above your current one git clone git://gitorious.org/devpkg/devpkg.git devpkgzed diff -r devpkg devpkgzed ``` 這將會克隆我的`devpkg`版本到`devpkgzed`目錄中。之后使用工具`diff`來對比你的和我的代碼。書中你所使用的這些文件直接來自于這個項目,所以如果出現了不同的行,肯定就有錯誤。 要記住這個練習沒有真正的及格或不及格,它只是一個方式來讓你挑戰自己,并盡可能變得精確和謹慎。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看