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