/* SvnRev * * This utility retrieves the highest number that follows the "$Id: $" keyword * or a combination of the $Rev: $ and $Date: $ keywords. The Subversion * version control system expands these keywords and keeps them up to date. * For an example of the tag, see the end of this comment. * * Details on the usage and the operation of this utility is available on-line * at http://www.compuphase.com/svnrev.htm. * * * Acknowledgements * * The support for .java files is contributed by Tom McCann (tommc@spoken.com). * The option for prefixing and/or suffixing the build number (in the string * constant SVN_REVSTR) was suggested by Robert Nitzel. * * * License * * Copyright (c) 2005-2009, ITB CompuPhase (www.compuphase.com). * * This software is provided "as-is", without any express or implied warranty. * In no event will the authors be held liable for any damages arising from * the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software in * a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. * * Version: $Id: svnrev.c 1497 2010-02-12 17:20:21Z achaloyan $ */ #include #include #include #include #include #if defined __WIN32__ || defined _Win32 || defined _WIN32 #define DIRSEP '\\' #elif defined macintosh #define DIRSEP ':' #else /* assume Linux/Unix */ #define DIRSEP '/' #endif #define MAX_LINELENGTH 512 #define MAX_SYMBOLLENGTH 32 static void about(void) { printf("svnrev 1.7.\n\n"); printf("Usage: svnrev [options] [input [...]]\n\n" "Options:\n" "-ofilename\tOutput filename for the file with the build number. When no\n" "\t\tfilename follows \"-o\", the result is written to stdout. The\n" "\t\tdefault filename is \"svnrev.h\" for C/C++ and \"VersionInfo.java\"\n" "\t\tfor Java.\n\n" "-fpattern\tFormat: Adds text before or after the build number in the\n" "\t\tconstant SVN_REVSTR. The pattern has the form \"text#text\"\n" "\t\t(without the quotes) where \"text\" is arbitrary text and \"#\"\n" "\t\twill be replaced by the build number.\n\n" "-i\t\tIncremental: this option should be used when the list of input\n" "\t\tfiles is a subset of all files in the project. When -i is\n" "\t\tpresent, svnrev also scans the output file that was generated\n" "\t\ton a previous run.\n\n" "-jname\t\tJava: this option writes a java package file instead of a C/C++\n" "\t\theader file. The name of the Java package must follow the\n" "\t\toption (this is not the filename).\n\n" "-v\t\tVerbose: prints the names of files that are modified since the\n" "\t\tlast commit (into version control) to stderr.\n"); exit(1); } static void processfile(const char *name, int failsilent, int *max_build, int *accum_build, int *max_year, int *max_month, int *max_day, int *ismodified) { char str[MAX_LINELENGTH], str_base[MAX_LINELENGTH]; char name_base[MAX_LINELENGTH]; char *p1; FILE *fp, *fp_base; int build, maj_build; int year, month, day; int cnt; char modchar; /* since we also want to verify whether the file is modified in version * control, get the path to the working copy name * for every source file "\, the "working copy" base can * be found in "\.svn\text-base\.svn-base" */ if ((p1 = strrchr(name, DIRSEP)) != NULL) { ++p1; /* skip directory separator character ('\' in Windows, '/' in Linux) */ strncpy(name_base, name, (int)(p1 - name)); name_base[(int)(p1 - name)] = '\0'; } else { name_base[0] = '\0'; p1 = (char*)name; } /* if */ sprintf(name_base + strlen(name_base), ".svn%ctext-base%c%s.svn-base", DIRSEP, DIRSEP, p1); /* first extract the revision keywords */ fp = fopen(name, "r"); if (fp == NULL) { if (!failsilent) fprintf(stderr, "Failed to open input file '%s'\n", name); return; } /* if */ fp_base = fopen(name_base, "r"); /* fail silently */ build = 0; maj_build = 0; /* RCS / CVS */ year = month = day = 0; while (fgets(str, sizeof str, fp) != NULL) { if (fp_base == NULL || fgets(str_base, sizeof str_base, fp_base) == NULL) str_base[0] = '\0'; if ((p1 = strstr(str, "$Id:")) != NULL && strchr(p1+1, '$') != NULL) { if (sscanf(p1, "$Id: %*s %d %d-%d-%d", &build, &year, &month, &day) < 4 && sscanf(p1, "$Id: %*s %d %d/%d/%d", &build, &year, &month, &day) < 4) if (sscanf(p1, "$Id: %*s %d.%d %d-%d-%d", &maj_build, &build, &year, &month, &day) < 5) sscanf(p1, "$Id: %*s %d.%d %d/%d/%d", &maj_build, &build, &year, &month, &day); } else if ((p1 = strstr(str, "$Rev:")) != NULL && strchr(p1+1, '$') != NULL) { if (sscanf(p1, "$Rev: %d.%d", &maj_build, &build) < 2) { sscanf(p1, "$Rev: %d", &build); maj_build = 0; } /* if */ } else if ((p1 = strstr(str, "$Revision:")) != NULL && strchr(p1+1, '$') != NULL) { if (sscanf(p1, "$Revision: %d.%d", &maj_build, &build) < 2) { /* SvnRev also writes this keyword in its own generated file; read it * back for partial updates */ cnt = sscanf(p1, "$Revision: %d%c", &build, &modchar); if (cnt == 2 && modchar == 'M' && ismodified != NULL) *ismodified = 1; maj_build = 0; } /* if */ } else if ((p1 = strstr(str, "$Date:")) != NULL && strchr(p1+1, '$') != NULL) { if (sscanf(p1, "$Date: %d-%d-%d", &year, &month, &day) < 3) sscanf(p1, "$Date: %d/%d/%d", &year, &month, &day); } else if (ismodified != NULL && *ismodified == 0 && fp_base != NULL) { /* no keyword present, compare the lines for equivalence */ *ismodified = strcmp(str, str_base) != 0; } /* if */ if (maj_build) *accum_build += build; /* RCS / CVS */ else if (build > *max_build) *max_build = build; /* Subversion */ if (year > *max_year || (year == *max_year && month > *max_month) || (year == *max_year && month == *max_month && day > *max_day)) { *max_year = year; *max_month = month; *max_day = day; } /* if */ if (build > 0 && year > 0 && (fp_base == NULL || ismodified == NULL || *ismodified != 0)) break; /* both build # and date found, not comparing or modification * already found => no need to search further */ } /* while */ fclose(fp); if (fp_base != NULL) fclose(fp_base); } int main(int argc, char *argv[]) { char *outname = NULL; FILE *fp; FILE *input_file; char *input_file_name = NULL; char *path_prefix = NULL; int index; int process_self = 0; int verbose = 0; int max_build, accum_build; int max_year, max_month, max_day; int ismodified, filemodified; char prefix[MAX_SYMBOLLENGTH], suffix[MAX_SYMBOLLENGTH]; char modified_suffix[2]; int write_java = 0; /* flag for Java output, 0=.h output, 1=.java output */ /* java package to put revision info in. * REVIEW - I assume if you want Java output you will specify a package. */ char *java_package = NULL; if (argc <= 1) about(); /* collect the options */ prefix[0] = '\0'; suffix[0] = '\0'; for (index = 1; index < argc; index++) { /* check for options */ if (argv[index][0] == '-' #if defined __WIN32__ || defined _Win32 || defined _WIN32 || argv[index][0] == '/' #endif ) { switch (argv[index][1]) { case 'f': { int len; char *ptr = strchr(&argv[index][2], '#'); len = (ptr != NULL) ? (int)(ptr - &argv[index][2]) : (int)strlen(&argv[index][2]); if (len >= MAX_SYMBOLLENGTH) len = MAX_SYMBOLLENGTH - 1; strncpy(prefix, &argv[index][2], len); prefix[len] = '\0'; ptr = (ptr != NULL) ? ptr + 1 : strchr(argv[index], '\0'); len = (int)strlen(ptr); if (len >= MAX_SYMBOLLENGTH) len = MAX_SYMBOLLENGTH - 1; strncpy(suffix, ptr, len); suffix[len] = '\0'; break; } /* case */ case 'i': process_self = 1; break; case 'j': write_java=1; java_package = &argv[index][2]; break; case 'o': outname = &argv[index][2]; break; case 'r': input_file_name = &argv[index][2]; break; case 'p': path_prefix = &argv[index][2]; break; case 'v': verbose = 1; break; default: fprintf(stderr, "Invalid option '%s'\n", argv[index]); about(); } /* switch */ } /* if */ } /* for */ if (outname == NULL) outname = write_java ? "SvnRevision.java" : "uni_revision.h"; if (!process_self && *outname != '\0') remove(outname); /* phase 1: scan through all files and get the highest build number */ max_build = 0; accum_build = 0; /* for RCS / CVS */ max_year = max_month = max_day = 0; ismodified = 0; if(input_file_name) { input_file = fopen(input_file_name, "r"); if (input_file != NULL) { apr_dir_t *dir; apr_finfo_t finfo; apr_status_t rv; apr_pool_t *pool; char *file_path; char dir_path[256]; /* line */ int offset = 0; if(path_prefix) offset = sprintf(dir_path, "%s", path_prefix); else offset = sprintf(dir_path, "../../"); apr_initialize(); apr_pool_create(&pool,NULL); while (fgets(dir_path + offset, sizeof(dir_path) - offset, input_file) != NULL ) { /* read a line */ size_t len = strlen(dir_path)-1; if(dir_path[len] == '\n') dir_path[len] = 0; rv = apr_dir_open(&dir,dir_path,pool); if(rv == APR_SUCCESS) { while (apr_dir_read(&finfo, APR_FINFO_NAME, dir) == APR_SUCCESS) { /* get next file */ if(finfo.filetype != APR_REG) continue; apr_filepath_merge(&file_path,dir_path,finfo.name,0,pool); filemodified = 0; if (strcasecmp(file_path, outname)!=0) processfile(file_path, 0, &max_build, &accum_build, &max_year, &max_month, &max_day, &filemodified); if (filemodified && verbose) fprintf(stderr, "\tNotice: modified file '%s'\n", file_path); ismodified = ismodified || filemodified; } apr_dir_close(dir); } else { fprintf(stderr, "No such directory '%s'\n", dir_path); } } fclose (input_file); apr_pool_destroy(pool); apr_terminate(); } else { fprintf(stderr, "No such input file '%s'\n", input_file_name); } } else { for (index = 1; index < argc; index++) { /* skip the options (already handled) */ if (argv[index][0] == '-' #if defined __WIN32__ || defined _Win32 || defined _WIN32 || argv[index][0] == '/' #endif ) continue; filemodified = 0; if (strcasecmp(argv[index], outname)!=0) processfile(argv[index], 0, &max_build, &accum_build, &max_year, &max_month, &max_day, &filemodified); if (filemodified && verbose) fprintf(stderr, "\tNotice: modified file '%s'\n", argv[index]); ismodified = ismodified || filemodified; } /* for */ } /* also run over the existing header file, if any */ if (process_self && *outname != '\0') processfile(outname, 1, &max_build, &accum_build, &max_year, &max_month, &max_day, NULL/*&ismodified*/); if (accum_build > max_build) max_build = accum_build; modified_suffix[0] = ismodified ? 'M' : '\0'; modified_suffix[1] = '\0'; /* phase 2: write a file with this highest build number */ if (*outname == '\0') { fp = stdout; } else if ((fp = fopen(outname, "w")) == NULL) { fprintf(stderr, "Failed to create output file '%s'\n", outname); return 2; } /* if */ if (*outname != '\0') { /* don't print the comments to stdout */ fprintf(fp, "/* This file was generated by the \"svnrev\" utility\n" " * (http://www.compuphase.com/svnrev.htm).\n" " * You should not modify it manually, as it may be re-generated.\n" " *\n" " * $Revision: %d%s$\n" " * $Date: %04d-%02d-%02d$\n" " */\n\n", max_build, modified_suffix, max_year, max_month, max_day); } /* if */ fprintf(fp, "#ifndef UNI_REVISION_H\n"); fprintf(fp, "#define UNI_REVISION_H\n\n"); fprintf(fp, "#define UNI_REVISION\t\t%d\n", max_build); fprintf(fp, "#define UNI_REVISION_STRING\t\"%s%d%s%s\"\n", prefix, max_build, modified_suffix, suffix); fprintf(fp, "#define UNI_REVISION_DATE\t\"%04d-%02d-%02d\"\n", max_year, max_month, max_day); fprintf(fp, "#define UNI_REVISION_STAMP\t%04d%02d%02dL\n", max_year, max_month, max_day); fprintf(fp, "#define UNI_REVISION_MODIFIED\t%d\n", ismodified); fprintf(fp, "\n#endif /* UNI_REVISION_H */\n"); if (*outname != '\0') fclose(fp); return 0; }