原文:Exercise 26: Write A First Real Programhtml
译者:飞龙git
这本书你已经完成一半了,因此你须要作一个期中检测。期中检测中你须要从新构建一个我特意为本书编写的软件,叫作devpkg
。随后你须要以一些方式扩展它,而且经过编写一些单元测试来改进代码。程序员
注github
我在一些你须要完成的练习以前编写了这个练习。若是你如今尝试这个练习,记住软件可能会含有一些bug,你可能因为个人错误会产生一些问题,也可能不知道须要什么来完成它。若是这样的话,经过help@learncodethehardway.org来告诉我,以后等待我写完其它练习。shell
devpkg
?devpkg
是一个简单的C程序,能够用于安装其它软件。我特意为本书编写了它,做为一种方式来教你真正的软件是如何构建的,以及如何复用他人的库。它使用了一个叫作Apache可移植运行时(APR)的库,其中含有许多工做跨平台的便利的C函数,包括Windows。此外,它只是从互联网(或本地文件)抓取代码,而且执行一般的./configure ; make ; make install
命令,每一个程序员都用到过。数据库
这个练习中,你的目标是从源码构建devpkg
,完成我提供的每一个挑战,而且使用源码来理解devpkg
作了什么和为何这样作。apache
咱们打算建立一个具备三个命令的工具:编程
devpkg -Sbash
在电脑上安装新的软件。数据结构
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可移植运行时(APR)来未完成这个练习得到一个可移植的工具集。APR并非必要的,你也能够不用它,可是你须要写的代码就会很是多。我如今强制你使用APR,使你可以熟悉连接和使用其余的库。最后,APR也能在Windows上工做,因此你能够把它迁移到许多其它平台上。
你应该获取apr-1.4.5
和apr-util-1.3
的库,以及浏览在apr.apache.org主站上的文档。
下面是一个ShellScript,用于安装所需的全部库。你应该手动将它写到一个文件中,以后运行它直到APR安装好而且没有任何错误。
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语言的书不是一个很好的程序。
简单运行这个脚本,修复它直到正常工做,就完成的全部库的安装,以后你须要完成项目的剩下部分。
你须要建立一些简单的项目文件来起步。下面是我一般建立一个新项目的方法:
mkdir devpkg cd devpkg touch README Makefile
你应该已经安装了APR和APR-util,因此你须要一些更多的文件做为基本的依赖:
练习20中的dbg.h
。
从http://bstring.sourceforge.net/下载的bstrlib.h
和bstrlib.c
。下载.zip
文件,解压而且将这个两个文件拷贝到项目中。
运行make bstrlib.o
,若是这不能正常工做,阅读下面的“修复bstring
”指南。
注
在一些平台上
bstring.c
文件会出现下列错误:
bstrlib.c:2762: error: expected declaration specifiers or '...' before numeric constant这是因为做者使用了一个很差的定义,它在一些平台上不能工做。你须要修改第2759行的
#ifdef __GNUC__
,并把它改为:
#if defined(__GNUC__) && !defined(__APPLE__)
以后在Mac OSX平台上就应该可以正常工做了。
作完上面这些后,你应该有了Makefile
,README
,dbg.h
,bstrlib.h
和bstrlib.c
,并作好了准备。
咱们最好从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
选项。你读到的这份列表是我打算教给你的,但不要认为这是我构建软件的通用方法。有时候我会事先知道主题,而且会作更多的规划。也有时我会编写一份规划并将它扔掉,以后再规划更好的版本。它彻底取决于个人经验告诉我哪一个比较好,或者个人灵感将我带到何处。
若是你碰到一个“专家”,它告诉你只有一个方法能够解决编程问题,那么它在骗你。要么它们实际使用了不少策略,要么他们并不足够好。
程序中必须有个方法来记录已经安装的URL,列出这些URL,而且检查一些程序是否已安装以便跳过。我会使用一个简单、扁平化的文件数据库,以及bstrlib.h
。
首先,建立db.h
头文件,以便让你知道须要实现什么。
#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
。
#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; }
在继续以前,仔细阅读这些文件的每一行,而且确保你以准确地输入了它们。经过逐行阅读代码来实践它。同时,跟踪每一个函数调用,而且确保你使用了check
来校验返回值。最后,在APR网站上的文档,或者bstrlib.h 或 bstrlib.c的源码中,查阅每一个你不认识的函数。
devkpg
的一个关键设计是,使用相似于curl
、tar
和git
的外部工具来完成大部分的工做。咱们能够找到在程序内部完成这些工做的库,可是若是咱们只是须要这些程序的基本功能,这样就毫无心义。在Unix运行其它命令并不丢人。
为了完成这些,我打算使用apr_thread_proc.h
函数来运行程序,可是我也但愿建立一个简单的类“模板”系统。我会使用struct Shell
,它持有全部运行程序所需的信息,可是在参数中有一些“空位”,我能够将它们替换成实际值。
观察shell.h
文件来了解我会用到的结构和命令。你能够看到我使用extern
来代表其余的.c
文件也能访问到shell.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
中建立实际变量。
#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
函数以及如何编写它们。在下个挑战中你须要分析这一函数。
Shell_exec
为这些文件(以及向挑战1那样的完整的代码复查)设置的挑战是完整分析Shell_exec
,而且拆分代码来了解工做原理。你应该可以理解每一行代码,for
循环如何工做,以及参数如何被替换。
一旦你分析完成,向struct Shell
添加一个字段,提供须要替代的args
变量的数量。更新全部命令来接受参数的正确数量,随后增长一个错误检查,来确认参数被正确替换,以及在错误时退出。
如今你须要构造正确的命令来完成功能。这些命令会用到APR的函数、db.h
和shell.h
来执行下载和构建软件的真正工做。这些文件最为复杂,因此要当心编写它们。你须要首先编写commands.h
文件,接着在commands.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
中。
#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
的工做原理。
像以前同样,完整地复查一遍代码来保证如出一辙。接着浏览每一个函数而且确保你知道他如何工做。你也应该跟踪这个文件或其它文件中,每一个函数对其它函数的调用。最后,确认你理解了这里的全部调用APR的函数。
一旦你正确编写并分析了这个文件,把我当成一个傻瓜同样来评判个人设计,我须要看看你是否能够改进它。不要真正修改代码,只是建立一个notes.txt
而且写下你的想法和你须要修改的地方。
devpkg
的main
函数devpkg.c
是最后且最重要的,可是也多是最简单的文件,其中建立了main
函数。没有与之配套的.h
文件,由于这个文件包含其余全部文件。这个文件用于建立devpkg
可执行程序,同时组装了来自Makefile
的其它.o
文件。在文件中输入代码并保证正确。
#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; }
为这个文件设置的挑战是理解参数如何处理,以及参数是什么,以后建立含有使用指南的README
文件。在编写README
的同时,也编写一个简单的simple.sh,它运行
./devpkg来检查每一个命令都在实际环境下工做。在你的脚本顶端使用
set -e`,使它跳过第一个错误。
最后,在Valgrind
下运行程序,确保在进行下一步以前,全部东西都能正常运行。
最后的挑战就是这个期中检测,它包含三件事情:
将你的代码与个人在线代码对比,以100%的分数开始,每错一行减去1%。
在你的notes.txt
中记录你是如何改进代码和devpkg
的功能,而且实现你的改进。
编写一个devpkg
的替代版本,使用其余你喜欢的语言,或者你以为最适合编写它的语言。对比两者,以后基于你的结果改进你的devpkg
的C版本。
你能够执行下列命令来将你的代码与个人对比:
cd .. # get one directory above your current one git clone git://gitorious.org/devpkg/devpkg.git devpkgzed diff -r devpkg devpkgzed
这将会克隆个人devpkg
版本到devpkgzed
目录中。以后使用工具diff
来对比你的和个人代码。书中你所使用的这些文件直接来自于这个项目,因此若是出现了不一样的行,确定就有错误。
要记住这个练习没有真正的及格或不及格,它只是一个方式来让你挑战本身,并尽量变得精确和谨慎。