#if !defined(_XOPEN_SOURCE)
#if __STDC_VERSION__ >= 199901L
#define _XOPEN_SOURCE 600
#else
#define _XOPEN_SOURCE 4
#endif
#endif
#include "unicode/utypes.h"
#include "unicode/putil.h"
#include "cmemory.h"
#include "cstring.h"
#include "filestrm.h"
#include "toolutil.h"
#include "unicode/uclean.h"
#include "unewdata.h"
#include "uoptions.h"
#include "putilimp.h"
#if U_HAVE_POPEN
# include <unistd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
U_CDECL_BEGIN
#include "pkgtypes.h"
#include "makefile.h"
U_CDECL_END
static int executeMakefile(const UPKGOptions *o);
static void loadLists(UPKGOptions *o, UErrorCode *status);
static void fillInMakefileFromICUConfig(UOption *option);
static struct
{
const char *name, *alt_name;
UPKGMODE *fcn;
const char *desc;
} modes[] =
{
{ "files", 0, pkg_mode_files, "Uses raw data files (no effect). Installation copies all files to the target location." },
#ifdef U_MAKE_IS_NMAKE
{ "dll", "library", pkg_mode_windows, "Generates one common data file and one shared library, <package>.dll"},
{ "common", "archive", pkg_mode_windows, "Generates just the common file, <package>.dat"},
{ "static", "static", pkg_mode_windows, "Generates one statically linked library, " LIB_PREFIX "<package>" UDATA_LIB_SUFFIX }
#else
#ifdef UDATA_SO_SUFFIX
{ "dll", "library", pkg_mode_dll, "Generates one shared library, <package>" UDATA_SO_SUFFIX },
#endif
{ "common", "archive", pkg_mode_common, "Generates one common data file, <package>.dat" },
{ "static", "static", pkg_mode_static, "Generates one statically linked library, " LIB_PREFIX "<package>" UDATA_LIB_SUFFIX }
#endif
};
enum {
NAME,
BLDOPT,
MODE,
HELP,
HELP_QUESTION_MARK,
VERBOSE,
COPYRIGHT,
COMMENT,
DESTDIR,
CLEAN,
NOOUTPUT,
REBUILD,
TEMPDIR,
INSTALL,
SOURCEDIR,
ENTRYPOINT,
REVISION,
MAKEARG,
FORCE_PREFIX,
LIBNAME,
QUIET
};
static UOption options[]={
UOPTION_DEF( "name", 'p', UOPT_REQUIRES_ARG),
UOPTION_DEF( "bldopt", 'O', UOPT_REQUIRES_ARG),
UOPTION_DEF( "mode", 'm', UOPT_REQUIRES_ARG),
UOPTION_HELP_H,
UOPTION_HELP_QUESTION_MARK,
UOPTION_VERBOSE,
UOPTION_COPYRIGHT,
UOPTION_DEF( "comment", 'C', UOPT_REQUIRES_ARG),
UOPTION_DESTDIR,
UOPTION_DEF( "clean", 'k', UOPT_NO_ARG),
UOPTION_DEF( "nooutput",'n', UOPT_NO_ARG),
UOPTION_DEF( "rebuild", 'F', UOPT_NO_ARG),
UOPTION_DEF( "tempdir", 'T', UOPT_REQUIRES_ARG),
UOPTION_DEF( "install", 'I', UOPT_REQUIRES_ARG),
UOPTION_SOURCEDIR ,
UOPTION_DEF( "entrypoint", 'e', UOPT_REQUIRES_ARG),
UOPTION_DEF( "revision", 'r', UOPT_REQUIRES_ARG),
UOPTION_DEF( "makearg", 'M', UOPT_REQUIRES_ARG),
UOPTION_DEF( "force-prefix", 'f', UOPT_NO_ARG),
UOPTION_DEF( "libname", 'L', UOPT_REQUIRES_ARG),
UOPTION_DEF( "quiet", 'q', UOPT_NO_ARG)
};
const char options_help[][320]={
"Set the data name",
#ifdef U_MAKE_IS_NMAKE
"The directory where the ICU is located (e.g. <ICUROOT> which contains the bin directory)",
#else
"Specify options for the builder. (Autdetected if icu-config is available)",
#endif
"Specify the mode of building (see below; default: common)",
"This usage text",
"This usage text",
"Make the output verbose",
"Use the standard ICU copyright",
"Use a custom comment (instead of the copyright)",
"Specify the destination directory for files",
"Clean out generated & temporary files",
"Suppress output of data, just list files to be created",
"Force rebuilding of all data",
"Specify temporary dir (default: output dir)",
"Install the data (specify target)",
"Specify a custom source directory",
"Specify a custom entrypoint name (default: short name)",
"Specify a version when packaging in DLL or static mode",
"Pass the next argument to make(1)",
"Add package to all file names if not present",
"Library name to build (if different than package name)",
"Quite mode. (e.g. Do not output a readme file for static libraries)"
};
const char *progname = "PKGDATA";
int
main(int argc, char* argv[]) {
FileStream *out;
UPKGOptions o;
CharList *tail;
UBool needsHelp = FALSE;
UErrorCode status = U_ZERO_ERROR;
char tmp[1024];
int32_t i;
U_MAIN_INIT_ARGS(argc, argv);
progname = argv[0];
options[MODE].value = "common";
options[MAKEARG].value = "";
argc=u_parseArgs(argc, argv, sizeof(options)/sizeof(options[0]), options);
if(options[HELP].doesOccur || options[HELP_QUESTION_MARK].doesOccur) {
needsHelp = TRUE;
}
else {
if(!needsHelp && argc<0) {
fprintf(stderr,
"%s: error in command line argument \"%s\"\n",
progname,
argv[-argc]);
fprintf(stderr, "Run '%s --help' for help.\n", progname);
return 1;
}
if(!options[BLDOPT].doesOccur) {
fillInMakefileFromICUConfig(&options[1]);
}
#ifdef U_MAKE_IS_NMAKE
else {
fprintf(stderr, "Warning: You are using the deprecated -O option\n"
"\tYou can fix this warning by installing pkgdata, gencmn and genccode\n"
"\tinto the same directory and not specifying the -O option to pkgdata.\n");
}
#endif
if(!options[BLDOPT].doesOccur) {
fprintf(stderr, " required parameter is missing: -O is required \n");
fprintf(stderr, "Run '%s --help' for help.\n", progname);
return 1;
}
if(!options[NAME].doesOccur)
{
fprintf(stderr, " required parameter -p is missing \n");
fprintf(stderr, "Run '%s --help' for help.\n", progname);
return 1;
}
if(argc == 1) {
fprintf(stderr,
"No input files specified.\n"
"Run '%s --help' for help.\n", progname);
return 1;
}
}
if(argc<0 || needsHelp ) {
fprintf(stderr,
"usage: %s [-options] [-] [packageFile] \n"
"\tProduce packaged ICU data from the given list(s) of files.\n"
"\t'-' by itself means to read from stdin.\n"
"\tpackageFile is a text file containing the list of files to package.\n",
progname);
fprintf(stderr, "\n options:\n");
for(i=0;i<(sizeof(options)/sizeof(options[0]));i++) {
fprintf(stderr, "%-5s -%c %s%-10s %s\n",
(i<1?"[REQ]":""),
options[i].shortName,
options[i].longName ? "or --" : " ",
options[i].longName ? options[i].longName : "",
options_help[i]);
}
fprintf(stderr, "modes: (-m option)\n");
for(i=0;i<(sizeof(modes)/sizeof(modes[0]));i++) {
fprintf(stderr, " %-9s ", modes[i].name);
if (modes[i].alt_name) {
fprintf(stderr, "/ %-9s", modes[i].alt_name);
} else {
fprintf(stderr, " ");
}
fprintf(stderr, " %s\n", modes[i].desc);
}
return 1;
}
uprv_memset(&o, 0, sizeof(o));
o.mode = options[MODE].value;
o.version = options[REVISION].doesOccur ? options[REVISION].value : 0;
o.makeArgs = options[MAKEARG].value;
o.fcn = NULL;
for(i=0;i<sizeof(modes)/sizeof(modes[0]);i++) {
if(!uprv_strcmp(modes[i].name, o.mode)) {
o.fcn = modes[i].fcn;
break;
} else if (modes[i].alt_name && !uprv_strcmp(modes[i].alt_name, o.mode)) {
o.mode = modes[i].name;
o.fcn = modes[i].fcn;
break;
}
}
if(o.fcn == NULL) {
fprintf(stderr, "Error: invalid mode '%s' specified. Run '%s --help' to list valid modes.\n", o.mode, progname);
return 1;
}
o.shortName = options[0].value;
{
int32_t len = (int32_t)uprv_strlen(o.shortName);
char *csname, *cp;
const char *sp;
cp = csname = (char *) uprv_malloc((len + 1 + 1) * sizeof(*o.cShortName));
if (*(sp = o.shortName)) {
*cp++ = isalpha(*sp) ? * sp : '_';
for (++sp; *sp; ++sp) {
*cp++ = isalnum(*sp) ? *sp : '_';
}
}
*cp = 0;
o.cShortName = csname;
}
if(options[LIBNAME].doesOccur) {
o.libName = options[LIBNAME].value;
} else {
o.libName = o.shortName;
}
if(options[QUIET].doesOccur) {
o.quiet = TRUE;
} else {
o.quiet = FALSE;
}
o.verbose = options[VERBOSE].doesOccur;
#ifdef U_MAKE_IS_NMAKE
{
char *pathstuff = (char *)options[BLDOPT].value;
if(options[1].value[uprv_strlen(options[BLDOPT].value)-1] == '\\') {
pathstuff[uprv_strlen(options[BLDOPT].value)-1] = '\0';
}
if(*pathstuff == PKGDATA_DERIVED_PATH || *pathstuff == 'R' || *pathstuff == 'D') {
o.options = pathstuff;
pathstuff++;
if(*pathstuff == ':') {
*pathstuff = '\0';
pathstuff++;
}
else {
fprintf(stderr, "Error: invalid windows build mode, should be R (release) or D (debug).\n");
return 1;
}
} else {
fprintf(stderr, "Error: invalid windows build mode, should be R (release) or D (debug).\n");
return 1;
}
o.icuroot = pathstuff;
if (o.verbose) {
fprintf(stdout, "# ICUROOT is %s\n", o.icuroot);
}
}
#else
o.options = options[BLDOPT].value;
#endif
if(options[COPYRIGHT].doesOccur) {
o.comment = U_COPYRIGHT_STRING;
} else if (options[COMMENT].doesOccur) {
o.comment = options[COMMENT].value;
}
if( options[DESTDIR].doesOccur ) {
o.targetDir = options[DESTDIR].value;
} else {
o.targetDir = ".";
}
o.clean = options[CLEAN].doesOccur;
o.nooutput = options[NOOUTPUT].doesOccur;
o.rebuild = options[REBUILD].doesOccur;
if( options[TEMPDIR].doesOccur ) {
o.tmpDir = options[TEMPDIR].value;
} else {
o.tmpDir = o.targetDir;
}
if( options[INSTALL].doesOccur ) {
o.install = options[INSTALL].value;
}
if( options[SOURCEDIR].doesOccur ) {
o.srcDir = options[SOURCEDIR].value;
} else {
o.srcDir = ".";
}
if( options[ENTRYPOINT].doesOccur ) {
o.entryName = options[ENTRYPOINT].value;
} else {
o.entryName = o.cShortName;
}
tail = NULL;
for( i=1; i<argc; i++) {
if ( !uprv_strcmp(argv[i] , "-") ) {
if( o.hadStdin == TRUE ) {
fprintf(stderr, "Error: can't specify '-' twice!\n"
"Run '%s --help' for help.\n", progname);
return 1;
}
o.hadStdin = TRUE;
}
o.fileListFiles = pkg_appendToList(o.fileListFiles, &tail, uprv_strdup(argv[i]));
}
loadLists(&o, &status);
if( U_FAILURE(status) ) {
fprintf(stderr, "error loading input file lists: %s\n", u_errorName(status));
return 2;
}
uprv_strcpy(tmp, o.tmpDir);
#ifdef U_MAKE_IS_NMAKE
uprv_strcat(tmp, U_FILE_SEP_STRING);
#else
uprv_strcat(tmp, U_FILE_ALT_SEP_STRING);
#endif
uprv_strcat(tmp, o.shortName);
uprv_strcat(tmp, "_");
uprv_strcat(tmp, o.mode);
uprv_strcat(tmp, ".mak");
o.makeFile = uprv_strdup(tmp);
out = T_FileStream_open(o.makeFile, "w");
if (out) {
pkg_mak_writeHeader(out, &o);
o.fcn(&o, out, &status);
pkg_mak_writeFooter(out, &o);
T_FileStream_close(out);
} else {
fprintf(stderr, "warning: couldn't create %s, will use existing file if any\n", o.makeFile);
}
if(U_FAILURE(status)) {
fprintf(stderr, "Error creating makefile [%s]: %s\n", o.mode,
u_errorName(status));
return 1;
}
if(o.nooutput == TRUE) {
return 0;
}
return executeMakefile(&o);
}
static int executeMakefile(const UPKGOptions *o)
{
char cmd[1024];
const char *make;
int rc;
make = getenv("MAKE");
if(!make || !make[0]) {
make = U_MAKE;
}
#ifdef U_WINDOWS
sprintf(cmd, "%s %s%s -f \"%s\" %s %s %s %s",
make,
o->install ? "INSTALLTO=" : "",
o->install ? o->install : "",
o->makeFile,
o->clean ? "clean" : "",
o->rebuild ? "rebuild" : "",
o->install ? "install" : "",
o->makeArgs);
#elif defined(OS400)
sprintf(cmd, "CALL GNU/GMAKE PARM(%s%s%s '-f' '%s' %s %s %s %s%s%s)",
o->install ? "'INSTALLTO=" : "",
o->install ? o->install : "",
o->install ? "'" : "",
o->makeFile,
o->clean ? "'clean'" : "",
o->rebuild ? "'rebuild'" : "",
o->install ? "'install'" : "",
o->makeArgs && *o->makeArgs ? "'" : "",
o->makeArgs && *o->makeArgs ? o->makeArgs : "",
o->makeArgs && *o->makeArgs ? "'" : "");
#else
sprintf(cmd, "%s %s%s -f %s %s %s %s %s",
make,
o->install ? "INSTALLTO=" : "",
o->install ? o->install : "",
o->makeFile,
o->clean ? "clean" : "",
o->rebuild ? "rebuild" : "",
o->install ? "install" : "",
o->makeArgs);
#endif
if(o->verbose) {
puts(cmd);
}
rc = system(cmd);
if(rc < 0) {
fprintf(stderr, "# Failed, rc=%d\n", rc);
}
return rc < 128 ? rc : (rc >> 8);
}
static void loadLists(UPKGOptions *o, UErrorCode *status)
{
CharList *l, *tail = NULL, *tail2 = NULL;
FileStream *in;
char line[16384];
char *linePtr, *lineNext;
const uint32_t lineMax = 16300;
char tmp[1024];
char *s;
int32_t ln=0;
for(l = o->fileListFiles; l; l = l->next) {
if(o->verbose) {
fprintf(stdout, "# Reading %s..\n", l->str);
}
in = T_FileStream_open(l->str, "r");
if(!in) {
fprintf(stderr, "Error opening <%s>.\n", l->str);
*status = U_FILE_ACCESS_ERROR;
return;
}
while(T_FileStream_readLine(in, line, sizeof(line))!=NULL) {
ln++;
if(uprv_strlen(line)>lineMax) {
fprintf(stderr, "%s:%d - line too long (over %d chars)\n", l->str, (int)ln, (int)lineMax);
exit(1);
}
linePtr = line;
while(isspace(*linePtr)) {
linePtr++;
}
s=linePtr;
while(*s!=0) {
if(*s=='\r' || *s=='\n') {
*s=0;
break;
}
++s;
}
if((*linePtr == 0) || (*linePtr == '#')) {
continue;
}
lineNext = NULL;
while(linePtr && *linePtr) {
while(*linePtr == ' ') {
linePtr++;
}
if(linePtr[0] == '"')
{
lineNext = uprv_strchr(linePtr+1, '"');
if(lineNext == NULL) {
fprintf(stderr, "%s:%d - missing trailing double quote (\")\n",
l->str, (int)ln);
exit(1);
} else {
lineNext++;
if(*lineNext) {
if(*lineNext != ' ') {
fprintf(stderr, "%s:%d - malformed quoted line at position %d, expected ' ' got '%c'\n",
l->str, (int)ln, (int)(lineNext-line), (*lineNext)?*lineNext:'0');
exit(1);
}
*lineNext = 0;
lineNext++;
}
}
} else {
lineNext = uprv_strchr(linePtr, ' ');
if(lineNext) {
*lineNext = 0;
lineNext++;
}
}
s = (char*)getLongPathname(linePtr);
o->files = pkg_appendToList(o->files, &tail, uprv_strdup(linePtr));
if(uprv_pathIsAbsolute(s)) {
fprintf(stderr, "pkgdata: Error: absolute path encountered. Old style paths are not supported. Use relative paths such as 'fur.res' or 'translit%cfur.res'.\n\tBad path: '%s'\n", U_FILE_SEP_CHAR, s);
exit(U_ILLEGAL_ARGUMENT_ERROR);
}
uprv_strcpy(tmp, o->srcDir);
uprv_strcat(tmp, o->srcDir[uprv_strlen(o->srcDir)-1]==U_FILE_SEP_CHAR?"":U_FILE_SEP_STRING);
uprv_strcat(tmp, s);
o->filePaths = pkg_appendToList(o->filePaths, &tail2, uprv_strdup(tmp));
linePtr = lineNext;
}
}
T_FileStream_close(in);
}
}
static void fillInMakefileFromICUConfig(UOption *option)
{
#if U_HAVE_POPEN
FILE *p;
size_t n;
static char buf[512] = "";
static const char cmd[] = "icu-config --incfile";
if(options[5].doesOccur)
{
fprintf(stderr, "%s: No -O option found, trying '%s'.\n", progname, cmd);
}
p = popen(cmd, "r");
if(p == NULL)
{
fprintf(stderr, "%s: icu-config: No icu-config found. (fix PATH or use -O option)\n", progname);
return;
}
n = fread(buf, 1, 511, p);
pclose(p);
if(n<=0)
{
fprintf(stderr,"%s: icu-config: Could not read from icu-config. (fix PATH or use -O option)\n", progname);
return;
}
if(buf[strlen(buf)-1]=='\n')
{
buf[strlen(buf)-1]=0;
}
if(buf[0] == 0)
{
fprintf(stderr, "%s: icu-config: invalid response from icu-config (fix PATH or use -O option)\n", progname);
return;
}
if(options[5].doesOccur)
{
fprintf(stderr, "%s: icu-config: using '-O %s'\n", progname, buf);
}
option->value = buf;
option->doesOccur = TRUE;
#else
#ifdef U_WINDOWS
char pathbuffer[_MAX_PATH] = {0};
char *fullEXEpath = NULL;
char *pathstuff = NULL;
if (strchr(progname, U_FILE_SEP_CHAR) != NULL || strchr(progname, U_FILE_ALT_SEP_CHAR) != NULL) {
fullEXEpath = _fullpath(pathbuffer, progname, sizeof(pathbuffer));
pathstuff = (char *)options[1].value;
if (fullEXEpath) {
pathstuff = strrchr(fullEXEpath, U_FILE_SEP_CHAR);
if (pathstuff) {
pathstuff[1] = 0;
uprv_memmove(fullEXEpath + 2, fullEXEpath, uprv_strlen(fullEXEpath)+1);
fullEXEpath[0] = PKGDATA_DERIVED_PATH;
fullEXEpath[1] = ':';
option->value = uprv_strdup(fullEXEpath);
option->doesOccur = TRUE;
}
}
}
else {
_searchenv("pkgdata.exe", "PATH", pathbuffer );
if( *pathbuffer != '\0' ) {
fullEXEpath = pathbuffer;
pathstuff = strrchr(pathbuffer, U_FILE_SEP_CHAR);
if (pathstuff) {
pathstuff[1] = 0;
uprv_memmove(fullEXEpath + 2, fullEXEpath, uprv_strlen(fullEXEpath)+1);
fullEXEpath[0] = PKGDATA_DERIVED_PATH;
fullEXEpath[1] = ':';
option->value = uprv_strdup(fullEXEpath);
option->doesOccur = TRUE;
}
}
}
#endif
#endif
}