ppremake.cxx 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. // Filename: ppremake.cxx
  2. // Created by: drose (25Sep00)
  3. //
  4. ////////////////////////////////////////////////////////////////////
  5. #include "ppremake.h"
  6. #include "ppMain.h"
  7. #include "ppScope.h"
  8. #include "check_include.h"
  9. #include "tokenize.h"
  10. #include "sedProcess.h"
  11. #ifdef HAVE_GETOPT
  12. #include <getopt.h>
  13. #else
  14. #include <gnu_getopt.h>
  15. #endif
  16. #include <set>
  17. #include <vector>
  18. #include <algorithm>
  19. #include <sys/stat.h>
  20. #include <unistd.h>
  21. bool unix_platform = false;
  22. bool windows_platform = false;
  23. static void
  24. usage() {
  25. cerr <<
  26. "\n"
  27. "ppremake [opts] subdir-name [subdir-name..]\n"
  28. "ppremake\n"
  29. "ppremake -s 'sed-command' <input >output\n"
  30. "\n"
  31. "This is Panda pre-make: a script preprocessor that scans the source\n"
  32. "directory hierarchy containing the current directory, looking for\n"
  33. "directories that contain a file called " SOURCE_FILENAME ". At the top of the\n"
  34. "directory tree must be a file called " PACKAGE_FILENAME ", which should define\n"
  35. "key variable definitions for processing, as well as pointing out the\n"
  36. "locations of further config files.\n\n"
  37. "The package file is read and interpreted, followed by each source file\n"
  38. "in turn; after each source file is read, the template file (specified in\n"
  39. "the config file) is read. The template file contains the actual statements\n"
  40. "to be output and will typically be set up to generate Makefiles or whatever\n"
  41. "is equivalent and appropriate to the particular build environment in use.\n\n"
  42. "The parameters are the names of the subdirectories (their local names, not\n"
  43. "the relative or full paths to them) that are to be processed. All\n"
  44. "subdirectories (that contain a file named " SOURCE_FILENAME ") will be\n"
  45. "scanned, but only the named subdirectories will have output files\n"
  46. "generated. If no parameter is given, then all directories will be\n"
  47. "processed.\n\n"
  48. "ppremake -s is a special form of the command that runs as a very limited\n"
  49. "sed. It has nothing to do with building makefiles, but is provided mainly\n"
  50. "so platforms that don't have sed built in can still portably run simple sed\n"
  51. "scripts.\n\n"
  52. "Options:\n\n"
  53. " -h Display this page.\n"
  54. " -V Report the version of ppremake, and exit.\n"
  55. " -P Report the current platform name, and exit.\n\n"
  56. " -D pp.dep Examine the given dependency file, and re-run ppremake\n"
  57. " only if the dependency file is stale.\n\n"
  58. " -d Instead of generating makefiles, report the set of\n"
  59. " subdirectories that the named subdirectory depends on.\n"
  60. " Directories are named by their local name, not by the\n"
  61. " path to them; e.g. util instead of src/util.\n"
  62. " -n As above, but report the set of subdirectories that\n"
  63. " depend on (need) the named subdirectory. Options -d and\n"
  64. " -n may be combined, and you may also name multiple\n"
  65. " subdirectories to scan at once.\n\n"
  66. " -p platform Build as if for the indicated platform name.\n"
  67. " -c config.pp Read the indicated user-level config.pp file after reading\n"
  68. " the system config.pp file. If this is omitted, the value\n"
  69. " given in the environment variable PPREMAKE_CONFIG is used\n"
  70. " instead.\n\n";
  71. }
  72. static void
  73. report_version() {
  74. cerr << "This is " << PACKAGE << " version " << VERSION << ".\n";
  75. }
  76. static void
  77. report_platform() {
  78. cerr << "ppremake built for platform " << PLATFORM << ".\n";
  79. }
  80. ////////////////////////////////////////////////////////////////////
  81. // Function: check_one_file
  82. // Description: Checks a single file listed in the dependency cache
  83. // to see if it matches the cache. Returns true if it
  84. // does, false if it does not.
  85. ////////////////////////////////////////////////////////////////////
  86. static bool
  87. check_one_file(const string &dir_prefix, const vector<string> &words) {
  88. assert(words.size() >= 2);
  89. string pathname = dir_prefix + words[0];
  90. time_t mtime = strtol(words[1].c_str(), (char **)NULL, 10);
  91. struct stat st;
  92. if (stat(pathname.c_str(), &st) < 0) {
  93. // The file doesn't even exist!
  94. return false;
  95. }
  96. if (st.st_mtime == mtime) {
  97. // The modification time matches; don't bother to read the file.
  98. return true;
  99. }
  100. // The modification time doesn't match, so we'll need to read the
  101. // file and look for #include directives. First, get the complete
  102. // set of files we're expecting to find.
  103. set<string> expected_files;
  104. for (int i = 2; i < (int)words.size(); i++) {
  105. const string &word = words[i];
  106. size_t slash = word.rfind('/');
  107. if (slash == string::npos) {
  108. // Every file is expected to include a slash.
  109. return false;
  110. }
  111. expected_files.insert(word.substr(slash + 1));
  112. }
  113. // Now open the source file and read it for #include directives.
  114. set<string> found_files;
  115. ifstream in(pathname.c_str());
  116. if (!in) {
  117. // Can't read the file for some reason.
  118. return false;
  119. }
  120. string line;
  121. getline(in, line);
  122. while (!in.fail() && !in.eof()) {
  123. string filename = check_include(line);
  124. if (!filename.empty() && filename.find('/') == string::npos) {
  125. found_files.insert(filename);
  126. }
  127. getline(in, line);
  128. }
  129. // Now check that the two sets are equivalent.
  130. return (expected_files == found_files);
  131. }
  132. ////////////////////////////////////////////////////////////////////
  133. // Function: check_dependencies
  134. // Description: Read in the indicated dependency cache file,
  135. // verifying that it is still current. If it is stale,
  136. // return false; otherwise, return true.
  137. ////////////////////////////////////////////////////////////////////
  138. static bool
  139. check_dependencies(const string &dep_filename) {
  140. // Extract the directory prefix from the dependency filename. This
  141. // is everything up until (and including) the last slash.
  142. string dir_prefix;
  143. size_t slash = dep_filename.rfind('/');
  144. if (slash != string::npos) {
  145. dir_prefix = dep_filename.substr(0, slash + 1);
  146. }
  147. ifstream in(dep_filename.c_str());
  148. if (!in) {
  149. // The dependency filename doesn't even exist: it's definitely
  150. // stale.
  151. return false;
  152. }
  153. string line;
  154. getline(in, line);
  155. while (!in.fail() && !in.eof()) {
  156. vector<string> words;
  157. tokenize_whitespace(line, words);
  158. if (words.size() < 2) {
  159. // Invalid file; return false.
  160. return false;
  161. }
  162. if (!check_one_file(dir_prefix, words)) {
  163. // This file is stale; return false.
  164. return false;
  165. }
  166. getline(in, line);
  167. }
  168. // All files are ok; return true.
  169. return true;
  170. }
  171. int
  172. main(int argc, char *argv[]) {
  173. string progname = argv[0];
  174. extern char *optarg;
  175. extern int optind;
  176. const char *optstr = "hVPD:dnp:c:s:";
  177. bool any_d = false;
  178. bool dependencies_stale = false;
  179. bool report_depends = false;
  180. bool report_needs = false;
  181. string platform = PLATFORM;
  182. string ppremake_config;
  183. bool got_ppremake_config = false;
  184. string sed_command;
  185. bool got_sed_command = false;
  186. int flag = getopt(argc, argv, optstr);
  187. while (flag != EOF) {
  188. switch (flag) {
  189. case 'h':
  190. usage();
  191. exit(0);
  192. case 'V':
  193. report_version();
  194. exit(0);
  195. break;
  196. case 'P':
  197. report_platform();
  198. exit(0);
  199. break;
  200. case 'D':
  201. if (!check_dependencies(optarg)) {
  202. dependencies_stale = true;
  203. }
  204. any_d = true;
  205. break;
  206. case 'd':
  207. report_depends = true;
  208. break;
  209. case 'n':
  210. report_needs = true;
  211. break;
  212. case 'p':
  213. platform = optarg;
  214. break;
  215. case 'c':
  216. ppremake_config = optarg;
  217. got_ppremake_config = true;
  218. break;
  219. case 's':
  220. sed_command = optarg;
  221. got_sed_command = true;
  222. break;
  223. default:
  224. exit(1);
  225. }
  226. flag = getopt(argc, argv, optstr);
  227. }
  228. argc -= (optind-1);
  229. argv += (optind-1);
  230. if (got_sed_command) {
  231. SedProcess sp;
  232. if (!sp.add_script_line(sed_command)) {
  233. exit(1);
  234. }
  235. sp.run(cin, cout);
  236. exit(0);
  237. }
  238. // If the user supplied one or more -d parameters, then we should
  239. // not continue unless some of the dependencies were stale.
  240. if (any_d) {
  241. if (!dependencies_stale) {
  242. exit(0);
  243. }
  244. cout << progname << "\n";
  245. }
  246. PPScope global_scope((PPNamedScopes *)NULL);
  247. global_scope.define_variable("PPREMAKE", PACKAGE);
  248. global_scope.define_variable("PPREMAKE_VERSION", VERSION);
  249. global_scope.define_variable("PLATFORM", platform);
  250. global_scope.define_variable("PACKAGE_FILENAME", PACKAGE_FILENAME);
  251. global_scope.define_variable("SOURCE_FILENAME", SOURCE_FILENAME);
  252. if (got_ppremake_config) {
  253. // If this came in on the command line, define a variable as such.
  254. // Otherwise, the system scripts can pull this value in from the
  255. // similarly-named environment variable.
  256. global_scope.define_variable("PPREMAKE_CONFIG", ppremake_config);
  257. }
  258. PPMain ppmain(&global_scope);
  259. if (!ppmain.read_source(".")) {
  260. exit(1);
  261. }
  262. if (report_depends || report_needs) {
  263. // With -d or -n, just report inter-directory dependency
  264. // relationships.
  265. if (argc < 2) {
  266. cerr << "No named directories.\n";
  267. exit(1);
  268. }
  269. for (int i = 1; i < argc; i++) {
  270. cerr << "\n";
  271. if (report_depends) {
  272. ppmain.report_depends(argv[i]);
  273. }
  274. cerr << "\n";
  275. if (report_needs) {
  276. ppmain.report_needs(argv[i]);
  277. }
  278. }
  279. } else {
  280. // Without -d or -n, do normal processing.
  281. if (argc < 2) {
  282. if (!ppmain.process_all()) {
  283. exit(1);
  284. }
  285. } else {
  286. for (int i = 1; i < argc; i++) {
  287. if (!ppmain.process(argv[i])) {
  288. cerr << "Unable to process " << argv[i] << ".\n";
  289. exit(1);
  290. }
  291. }
  292. }
  293. }
  294. cerr << "No errors.\n";
  295. return (0);
  296. }