说明
本文主要介绍巧妙利用访问时间提取和重组某嵌入式产品SDK代码的实践。
一 问题提出
目前产品平台为便于编译管理,要求各模块组织为include-source目录结构,分别存放头文件和源文件。但芯片厂家提供的SDK按功能划分为众多子目录,如subdir1(.c,.h)…subdirN(.c,.h)…Makefile,并将头文件和源文件一并存放在各子目录内。
此外,厂家SDK支持多种管理场景,通过选项开关编译不同的目录和文件。而产品硬件定板后管理场景固定,其他场景所涉及的代码将不再需要。
因此,需要遍历厂家SDK目录,提取在用的文件并重组为include-source目录结构。
二 解决方案
根据重组要求,有四种解决方案。
2.1 方案一
某些产品已将厂家SDK重组为include-source目录结构。因此,可人工比对现有产品已重组的SDK,从新的SDK中摘取所需文件,重组后再次编译,根据编译结果进行必要的修改和调整。
显然,该方案效率很低(尤其是甄别不同场景所用的同名文件时),而且不安全(可能遗漏SDK新增内容)。
2.2 方案二
编译厂家SDK后,解析编译输出中出现的文件(表明在用),手工或脚本提取后重组。
该方案解析难度稍大,且需考虑重复出现的文件(尤其是嵌套包含的头文件出现编译警告时)。
2.3 方案三
可参考《将模块代码量精简为2%的实践》一文中剔除未使用条件编译分支的思路,稍加修改即可用于解决本文所提问题。
其原理如下:
1) 创建include和source目录。
2) 在SDK待处理代码(所有源文件和头文件)的首行插入gcc扩展预编译头#warning。
3) 编译待处理代码获取gcc编译输出并进行分析。
4) 编译结果中”#warning”警告所涉及的文件表明在用,可删除编译头后按源文件和头文件类型分别拷贝至include和source目录。
其中,步骤1和3可手工进行,步骤2和4对《实践》一文中提供的Python脚本稍加修改即可实现。
2.3 方案四
编译SDK后,遍历目录读取各文件的访问时间*,提取符合指定时间(表明本次编译读到)的文件后重组。“指定时间”格式形如"Mon Aug 18 09:59:46 2014",通常设置为精确到日或时的编译时间。
该方案实现简单,应用起来也比方案三方便,故作为本文的优选实现。
三 代码实现
本节将实现上节所述的方案四(假设源文件名为DirMover.c)。所需的头文件如下:
1 #include <string.h> 2 #include <time.h> //ctime 3 #include <errno.h> 4 #include <unistd.h> //getcwd 5 #include <sys/stat.h> 6 #include <dirent.h>
首先定义一组全局数据:
1 #define DIR_LEN 512 //目录绝对路径最大长度 2 #define DIR_FILE_LEN 1024 //目录和文件组成的绝对路径最大长度 3 4 //将目标代码的原始目录结构改为Include-Source目录结构 5 typedef struct{ 6 char szIncDir[DIR_LEN]; //Include目录 7 char szSrcDir[DIR_LEN]; //Source目录 8 }T_DIR_TREE; 9 char gszCmpTime[sizeof("Mon Aug 18 09:59:46 2014")] = {0};
重组前需要创建include和source目录。MakeCleanDir()函数用于创建空的目录,若目录已存在且内含文件,则删除已存在的文件。
1 int MakeCleanDir(const char *pszDir, mode_t dwMode) 2 { 3 if(0 == mkdir(pszDir, dwMode)) 4 return 0; 5 6 if(errno != EEXIST) 7 { 8 fprintf(stderr, "Cannot make directory: %s(%s)! ", pszDir, strerror(errno)); 9 return -1; 10 } 11 12 char szCopyCmd[DIR_FILE_LEN] = {0}; 13 snprintf(szCopyCmd, sizeof(szCopyCmd), "rm -rf %s/*", pszDir); 14 system(szCopyCmd); //删除该目录下的文件 15 return 0; 16 }
为简化实现,删除操作直接调用rm命令。基于同样的考虑,后文拷贝文件时也直接调用cp命令。
创建重组目录后,调用TraverseDirectory()函数遍历原SDK目录:
1 typedef int (*TravFileFunc)(char *pszAbsFile, struct stat *ptFileStatus, void* pvTravInfo); 2 int TraverseDirectory(const char *pszCurDir, void* pvTravInfo, TravFileFunc fpTravFile) 3 { 4 DIR *pDir = opendir(pszCurDir); 5 if(NULL == pDir){ 6 fprintf(stderr, "Cannot open directory: %s! ", pszCurDir); 7 return -1; 8 } 9 10 struct dirent *pFileEntry = NULL; 11 while((pFileEntry = readdir(pDir)) != NULL){ 12 //剔除当前目录.,上级目录..及隐藏文件,避免死循环遍历目录 13 if(0 == strncmp(pFileEntry->d_name, ".", 1)) 14 continue; 15 16 struct stat tFileStatus; //文件状态信息 17 char szAbsFile[DIR_FILE_LEN] = {0}; //文件绝对路径 18 sprintf(szAbsFile, "%s/%s", pszCurDir, pFileEntry->d_name); 19 if(stat(szAbsFile, &tFileStatus) != 0){ 20 fprintf(stderr, "Call stat error(%s)! ", strerror(errno)); 21 return -1; 22 } 23 24 if(S_ISDIR(tFileStatus.st_mode)) //文件类型为目录,递归子目录 25 { 26 TraverseDirectory(szAbsFile, pvTravInfo, fpTravFile); 27 continue; 28 } 29 30 if(fpTravFile(szAbsFile, &tFileStatus, pvTravInfo) != 0) 31 break; 32 } 33 34 closedir(pDir); 35 return 0; 36 }
为求通用性,将TravFileFunc回调函数指针执行文件操作。此处回调函数原体为MoveFile()函数,该函数检查当前文件的返回时间,并将符合要求的文件分别拷贝至include和source目录。
1 int MoveFile(char *pszAbsFile, struct stat *ptFileStatus, void* pvTravInfo) 2 { 3 printf("Current file: %s(atime: %s). ", pszAbsFile, ctime(&ptFileStatus->st_atime)); 4 5 //跳过Include和Source目录 6 T_DIR_TREE *ptNewDirTree = (T_DIR_TREE *)pvTravInfo; 7 char *pSlashPos = strrchr(pszAbsFile, '/'); //目录与文件名之间的'/'号 8 *pSlashPos = '