atree.cpp revision 131292c0422b4e6eca5ee0eda431c9faae5cf35d
1#include <stdio.h> 2#include <stdlib.h> 3#include <string.h> 4#include <unistd.h> 5#include <stdarg.h> 6#include "options.h" 7#include "files.h" 8#include "fs.h" 9#include <set> 10#include <iostream> 11#include <sstream> 12 13using namespace std; 14 15bool g_debug = getenv("ATREE_DEBUG") != NULL; 16vector<string> g_listFiles; 17vector<string> g_inputBases; 18map<string, string> g_variables; 19string g_outputBase; 20string g_dependency; 21bool g_useHardLinks = false; 22 23const char* USAGE = 24"\n" 25"Usage: atree OPTIONS\n" 26"\n" 27"Options:\n" 28" -f FILELIST Specify one or more files containing the\n" 29" list of files to copy.\n" 30" -I INPUTDIR Specify one or more base directories in\n" 31" which to look for the files\n" 32" -o OUTPUTDIR Specify the directory to copy all of the\n" 33" output files to.\n" 34" -l Use hard links instead of copying the files.\n" 35" -m DEPENDENCY Output a make-formatted file containing the list.\n" 36" of files included. It sets the variable ATREE_FILES.\n" 37" -v VAR=VAL Replaces ${VAR} by VAL when reading input files.\n" 38" -d Verbose debug mode.\n" 39"\n" 40"FILELIST file format:\n" 41" The FILELIST files contain the list of files that will end up\n" 42" in the final OUTPUTDIR. Atree will look for files in the INPUTDIR\n" 43" directories in the order they are specified.\n" 44"\n" 45" In a FILELIST file, comment lines start with a #. Other lines\n" 46" are of the format:\n" 47"\n" 48" [rm|strip] DEST\n" 49" SRC [strip] DEST\n" 50" -SRCPATTERN\n" 51"\n" 52" DEST should be path relative to the output directory.\n" 53" 'rm DEST' removes the destination file and fails if it's missing.\n" 54" 'strip DEST' strips the binary destination file.\n" 55" If SRC is supplied, the file names can be different.\n" 56" SRCPATTERN is a pattern for the filenames.\n" 57"\n"; 58 59int usage() 60{ 61 fwrite(USAGE, strlen(USAGE), 1, stderr); 62 return 1; 63} 64 65static bool 66add_variable(const char* arg) { 67 const char* p = arg; 68 while (*p && *p != '=') p++; 69 70 if (*p == 0 || p == arg || p[1] == 0) { 71 return false; 72 } 73 74 ostringstream var; 75 var << "${" << string(arg, p-arg) << "}"; 76 g_variables[var.str()] = string(p+1); 77 return true; 78} 79 80static void 81debug_printf(const char* format, ...) 82{ 83 if (g_debug) { 84 fflush(stderr); 85 va_list ap; 86 va_start(ap, format); 87 vprintf(format, ap); 88 va_end(ap); 89 fflush(stdout); 90 } 91} 92 93int 94main(int argc, char* const* argv) 95{ 96 int err; 97 bool done = false; 98 while (!done) { 99 int opt = getopt(argc, argv, "f:I:o:hlm:v:d"); 100 switch (opt) 101 { 102 case -1: 103 done = true; 104 break; 105 case 'f': 106 g_listFiles.push_back(string(optarg)); 107 break; 108 case 'I': 109 g_inputBases.push_back(string(optarg)); 110 break; 111 case 'o': 112 if (g_outputBase.length() != 0) { 113 fprintf(stderr, "%s: -o may only be supplied once -- " 114 "-o %s\n", argv[0], optarg); 115 return usage(); 116 } 117 g_outputBase = optarg; 118 break; 119 case 'l': 120 g_useHardLinks = true; 121 break; 122 case 'm': 123 if (g_dependency.length() != 0) { 124 fprintf(stderr, "%s: -m may only be supplied once -- " 125 "-m %s\n", argv[0], optarg); 126 return usage(); 127 } 128 g_dependency = optarg; 129 break; 130 case 'v': 131 if (!add_variable(optarg)) { 132 fprintf(stderr, "%s Invalid expression in '-v %s': " 133 "expected format is '-v VAR=VALUE'.\n", 134 argv[0], optarg); 135 return usage(); 136 } 137 break; 138 case 'd': 139 g_debug = true; 140 break; 141 default: 142 case '?': 143 case 'h': 144 return usage(); 145 } 146 } 147 if (optind != argc) { 148 fprintf(stderr, "%s: invalid argument -- %s\n", argv[0], argv[optind]); 149 return usage(); 150 } 151 152 if (g_listFiles.size() == 0) { 153 fprintf(stderr, "%s: At least one -f option must be supplied.\n", 154 argv[0]); 155 return usage(); 156 } 157 158 if (g_inputBases.size() == 0) { 159 fprintf(stderr, "%s: At least one -I option must be supplied.\n", 160 argv[0]); 161 return usage(); 162 } 163 164 if (g_outputBase.length() == 0) { 165 fprintf(stderr, "%s: -o option must be supplied.\n", argv[0]); 166 return usage(); 167 } 168 169 170#if 0 171 for (vector<string>::iterator it=g_listFiles.begin(); 172 it!=g_listFiles.end(); it++) { 173 printf("-f \"%s\"\n", it->c_str()); 174 } 175 for (vector<string>::iterator it=g_inputBases.begin(); 176 it!=g_inputBases.end(); it++) { 177 printf("-I \"%s\"\n", it->c_str()); 178 } 179 printf("-o \"%s\"\n", g_outputBase.c_str()); 180 if (g_useHardLinks) { 181 printf("-l\n"); 182 } 183#endif 184 185 vector<FileRecord> files; 186 vector<FileRecord> more; 187 vector<string> excludes; 188 set<string> directories; 189 set<string> deleted; 190 191 // read file lists 192 for (vector<string>::iterator it=g_listFiles.begin(); 193 it!=g_listFiles.end(); it++) { 194 err = read_list_file(*it, g_variables, &files, &excludes); 195 if (err != 0) { 196 return err; 197 } 198 } 199 200 // look for input files 201 err = 0; 202 for (vector<FileRecord>::iterator it=files.begin(); 203 it!=files.end(); it++) { 204 err |= locate(&(*it), g_inputBases); 205 } 206 207 // expand the directories that we should copy into a list of files 208 for (vector<FileRecord>::iterator it=files.begin(); 209 it!=files.end(); it++) { 210 if (it->sourceIsDir) { 211 err |= list_dir(*it, excludes, &more); 212 } 213 } 214 for (vector<FileRecord>::iterator it=more.begin(); 215 it!=more.end(); it++) { 216 files.push_back(*it); 217 } 218 219 // get the name and modtime of the output files 220 for (vector<FileRecord>::iterator it=files.begin(); 221 it!=files.end(); it++) { 222 stat_out(g_outputBase, &(*it)); 223 } 224 225 if (err != 0) { 226 return 1; 227 } 228 229 // gather directories 230 for (vector<FileRecord>::iterator it=files.begin(); 231 it!=files.end(); it++) { 232 if (it->sourceIsDir) { 233 directories.insert(it->outPath); 234 } else { 235 string s = dir_part(it->outPath); 236 if (s != ".") { 237 directories.insert(s); 238 } 239 } 240 } 241 242 // gather files that should become directores 243 // and directories that should become files 244 for (vector<FileRecord>::iterator it=files.begin(); 245 it!=files.end(); it++) { 246 if (it->outMod != 0 && it->sourceIsDir != it->outIsDir) { 247 deleted.insert(it->outPath); 248 } 249 } 250 251 // delete files 252 for (set<string>::iterator it=deleted.begin(); 253 it!=deleted.end(); it++) { 254 debug_printf("deleting %s\n", it->c_str()); 255 err = remove_recursively(*it); 256 if (err != 0) { 257 return err; 258 } 259 } 260 261 // remove all files or directories as requested from the input atree file. 262 // must be done before create new directories. 263 for (vector<FileRecord>::iterator it=files.begin(); 264 it!=files.end(); it++) { 265 if (!it->sourceIsDir) { 266 if (it->fileOp == FILE_OP_REMOVE && 267 deleted.count(it->outPath) == 0) { 268 debug_printf("remove %s\n", it->outPath.c_str()); 269 err = remove_recursively(it->outPath); 270 if (err != 0) { 271 return err; 272 } 273 } 274 } 275 } 276 277 // make directories 278 for (set<string>::iterator it=directories.begin(); 279 it!=directories.end(); it++) { 280 debug_printf("mkdir %s\n", it->c_str()); 281 err = mkdir_recursively(*it); 282 if (err != 0) { 283 return err; 284 } 285 } 286 287 // copy (or link) files that are newer or of different size 288 for (vector<FileRecord>::iterator it=files.begin(); 289 it!=files.end(); it++) { 290 if (!it->sourceIsDir) { 291 if (it->fileOp == FILE_OP_REMOVE) { 292 continue; 293 } 294 295 debug_printf("copy %s(%ld) ==> %s(%ld)", 296 it->sourcePath.c_str(), it->sourceMod, 297 it->outPath.c_str(), it->outMod); 298 299 if (it->outSize != it->sourceSize || it->outMod < it->sourceMod) { 300 err = copy_file(it->sourcePath, it->outPath); 301 debug_printf(" done.\n"); 302 if (err != 0) { 303 return err; 304 } 305 } else { 306 debug_printf(" skipping.\n"); 307 } 308 309 if (it->fileOp == FILE_OP_STRIP) { 310 debug_printf("strip %s\n", it->outPath.c_str()); 311 err = strip_file(it->outPath); 312 if (err != 0) { 313 return err; 314 } 315 } 316 } 317 } 318 319 // output the dependency file 320 if (g_dependency.length() != 0) { 321 FILE *f = fopen(g_dependency.c_str(), "w"); 322 if (f != NULL) { 323 fprintf(f, "ATREE_FILES := $(ATREE_FILES) \\\n"); 324 for (vector<FileRecord>::iterator it=files.begin(); 325 it!=files.end(); it++) { 326 if (!it->sourceIsDir) { 327 fprintf(f, "%s \\\n", it->sourcePath.c_str()); 328 } 329 } 330 fprintf(f, "\n"); 331 fclose(f); 332 } else { 333 fprintf(stderr, "error opening manifest file for write: %s\n", 334 g_dependency.c_str()); 335 } 336 } 337 338 return 0; 339} 340