diff --git a/harbour/ChangeLog b/harbour/ChangeLog index 72293dd0fb..00dfe14e58 100644 --- a/harbour/ChangeLog +++ b/harbour/ChangeLog @@ -16,6 +16,26 @@ The license applies to all entries newer than 2009-04-28. */ +2010-06-25 01:05 UTC+0200 Viktor Szakats (harbour.01 syenar.hu) + + external/patchup.prg + + Added new 'patchup' Harbour script written by Tamas Tevesz. + The script serves as a tool to automate downloading, + file renaming, patching and recreating local .dif for + source packages we host locally in external directory. + The tool is also able to automatically recreate .dif file + after making local modifications in these sources, + don't forget to run 'hbrun patchup -rediff' in such case. + [ It replaces prev ren_sfn.prg tool created by me. ] + Please make sure to read the documentation and tool + requirements included in the .prg itself. + + Very nice job, thanks a lot Tamas. + + ; TODO: Updating Makefiles with new metadata. + + - config/ren_sfn.prg + - Deleted tool now superceded by patchup.prg + 2010-06-25 00:33 UTC+0200 Viktor Szakats (harbour.01 syenar.hu) * include/hbdefs.h + Added two new abstract types: HB_AREANO, HB_FIELDNO diff --git a/harbour/config/ren_sfn.prg b/harbour/config/ren_sfn.prg deleted file mode 100644 index 1aa5018c7c..0000000000 --- a/harbour/config/ren_sfn.prg +++ /dev/null @@ -1,137 +0,0 @@ -/* - * $Id$ - */ - -/* - Copyright 2009-2010 Viktor Szakats (harbour.01 syenar.hu) - See COPYING for licensing terms. - - NOTE: Purpose of this script is to take the source files - in Harbour repo and convert them back to the filenames - used in the original source distribution. - This is to aid finding local modifications and - apply them after an original source update. - [vszakats] - - DISCLAIMER: This tool is targeted only to Harbour core - maintainers. If you're not one of them you - don't have to mess with it. - */ - -#pragma warninglevel=3 - -#define _REN_PREFIX "# RENAME " - -PROCEDURE Main( cMode ) - LOCAL files := {} - LOCAL cFile := MemoRead( "Makefile" ) - LOCAL cLine - - IF ! Empty( cFile ) - - cFile := StrTran( cFile, Chr( 13 ) + Chr( 10 ), Chr( 10 ) ) - cFile := StrTran( cFile, Chr( 9 ), " " ) - - FOR EACH cLine IN hb_ATokens( cFile, Chr( 10 ) ) - IF ! Empty( cLine ) .AND. Left( AllTrim( cLine ), Len( _REN_PREFIX ) ) == _REN_PREFIX - cLine := SubStr( cLine, Len( _REN_PREFIX ) + 1 ) - IF Len( hb_ATokens( cLine ) ) == 2 - AAdd( files, hb_ATokens( cLine ) ) - ELSEIF Len( hb_ATokens( cLine ) ) == 1 - AAdd( files, { hb_ATokens( cLine )[ 1 ], hb_ATokens( cLine )[ 1 ] } ) - ENDIF - ENDIF - NEXT - - IF ! Empty( files ) .AND. cMode != NIL - SWITCH cMode - CASE "T" ; original_to_hb( files ) ; EXIT - CASE "F" ; hb_to_original( files ) ; EXIT - ENDSWITCH - ENDIF - ENDIF - - RETURN - -STATIC PROCEDURE original_to_hb( files ) - LOCAL cDir := "ori_src" - LOCAL file - LOCAL changes := files_to_changes( files ) - LOCAL change - - FOR EACH file IN files - hb_FCopy( cDir + hb_osPathSeparator() + file[ 2 ], file[ 1 ] ) - hb_FileEOLToNative( file[ 1 ] ) - FOR EACH change IN changes - hb_FileTran( file[ 1 ], Chr( 34 ) + change[ 2 ] + Chr( 34 ), Chr( 34 ) + change[ 1 ] + Chr( 34 ) ) - hb_FileTran( file[ 1 ], "<" + change[ 2 ] + ">", "<" + change[ 1 ] + ">" ) - NEXT - NEXT - - RETURN - -STATIC PROCEDURE hb_to_original( files ) - LOCAL cDir := "ori_dst" - LOCAL file - LOCAL changes := files_to_changes( files ) - LOCAL change - - MakeDir( cDir ) - hb_FEval( cDir + hb_osPathSeparator() + "*", {| cFileName | FErase( cFileName ) } ) - - FOR EACH file IN files - hb_FCopy( file[ 1 ], cDir + hb_osPathSeparator() + file[ 2 ] ) - FOR EACH change IN changes - hb_FileTran( cDir + hb_osPathSeparator() + file[ 2 ], Chr( 34 ) + change[ 1 ] + Chr( 34 ), Chr( 34 ) + change[ 2 ] + Chr( 34 ) ) - hb_FileTran( cDir + hb_osPathSeparator() + file[ 2 ], "<" + change[ 1 ] + ">", "<" + change[ 2 ] + ">" ) - NEXT - NEXT - - RETURN - -STATIC FUNCTION files_to_changes( files ) - LOCAL changes := {} - LOCAL file - - FOR EACH file IN files - IF Lower( FN_ExtGet( file[ 1 ] ) ) == ".h" .OR. ; - Lower( FN_ExtGet( file[ 2 ] ) ) == ".h" - IF !( file[ 1 ] == file[ 2 ] ) - AAdd( changes, file ) - ENDIF - ENDIF - NEXT - - RETURN changes - -STATIC FUNCTION hb_FileEOLToNative( cFileName ) - LOCAL cFile := hb_MemoRead( cFileName ) - - cFile := StrTran( cFile, Chr( 13 ) + Chr( 10 ), Chr( 10 ) ) - - RETURN hb_MemoWrit( cFileName, StrTran( cFile, Chr( 10 ), hb_osNewLine() ) ) - -STATIC FUNCTION hb_FileTran( cFileName, cFrom, cTo ) - RETURN hb_MemoWrit( cFileName, StrTran( hb_MemoRead( cFileName ), cFrom, cTo ) ) - -STATIC FUNCTION FN_ExtGet( cFileName ) - LOCAL cExt - - hb_FNameSplit( cFileName,,, @cExt ) - - RETURN cExt - -/* TOFIX: Ugly hack to avoid #include "directry.ch" */ -#define F_NAME 1 /* File name */ - -STATIC PROCEDURE hb_FEval( cMask, bBlock ) - LOCAL cDir - LOCAL tmp - - hb_FNameSplit( cMask, @cDir ) - - FOR EACH tmp IN Directory( cMask ) - Eval( bBlock, hb_FNameMerge( cDir, tmp[ F_NAME ] ) ) - NEXT - - RETURN diff --git a/harbour/external/patchup.prg b/harbour/external/patchup.prg new file mode 100644 index 0000000000..b07c63f0dc --- /dev/null +++ b/harbour/external/patchup.prg @@ -0,0 +1,787 @@ +#!/usr/bin/hbrun //gt:cgi +/* + * $Id$ + */ + +/* + * Copyright 2010 Tamas Tevesz + * See COPYING for licensing terms. + */ + +/* + * patchup - a tool to help update external components while keeping local fixes + * + * 1. CONFIGURATION + * ---------------- + * + * For proper operation, the following external tools are required to be present + * somewhere in your $PATH: + * + * - The GNU version of `patch', `diff' and `tar' (patchup will figure it out + * if you have them by the names of `gpatch', `gdiff' or `gtar') + * + * - curl, gzip, bzip2 and unzip (only the Info-ZIP version of unzip has been + * tested) + * + * patchup requires several meta data (in the form of specially formatted lines) + * in the component's Makefile. Formatting rules are as follows: + * + * - The first character on the line is a hash mark (`#'), followed by any number + * of white spaces. + * - Next comes a keyword, followed by any number of white spaces. + * - The keyword is followed by one or more arguments, separated by white spaces. + * The number of arguments taken depends on the keyword itself. + * + * The keywords themselves are case sensitive (only upper case keywords are + * recognized). The arguments are case senstive as well. + * + * Currently recognized keywords and their arguments are as follows: + * + * ORIGIN + * Takes one argument, the URL of component's home page. Not currently used, + * but greatly helps locating resources regarding the component. + * Example: for PCRE, it is `http://www.pcre.org/'. + * + * VER + * Takes one argument, the version number of the component currently in the + * Harbour tree. Not currently used, but greatly helps checking whether the + * component needs an update. + * Example: for PCRE, at the time of this writing, it is `8.02'. + * + * URL + * Takes one argument, the URL to the archive to the currently installed + * version of the component. Used by patchup. + * Example: for PCRE, at the time of this writing, it is + * `ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.02.zip'. + * patchup can currently unpack only `tar.gz', `tar.bz2', `tgz', `tbz', + * `tbz2' or `zip' archives -- one of these must be chosen. + * + * DIFF + * Takes one argument, the file name of the diff file containing local changes + * needed by Harbour. + * Example: for PCRE, it is `pcre.dif'. + * + * MAP + * Takes one or two arguments, specifying the correspondence of the file names + * between the original sources and the Harbour sources (which are reduced to + * 8+3 format, in order to stay compatible with DOS). + * If a particular file name is the same both in the upstream and the Harbour + * trees, it is sufficient to specify it only once, but every file that needs + * to be brought over to the Harbour tree must be specified. + * The very first `MAP' occurrence is treated specially: it's argument is used + * by patchup to locate the root of the extracted upstream source tree. + * Examples: + * + * # MAP LICENCE + * + * The file named `LICENCE' needs to be brought over from the upstream tree + * to the Harbour tree unchanged. In case of PCRE, `MAP LICENCE' being the + * first `MAP' line also means that patchup will use the directory + * containing this file as a base for all other files occurring later. + * + * # MAP config.h.generic config.h + * + * The file named `config.h.generic' in the upstream source tree needs to + * be brought over to the Harbour tree by the name `config.h'. + * + * # MAP foo/bar.h + * + * If the upstream source tree is not flat, relative paths may also be + * specified. The above means "bring `foo/bar.h' from upstream as `bar.h' + * into the Harbour tree". Note that the Harbour component tree is always + * flat, it is illegal to specify - for example - `MAP foo/bar.h zoink/baz.h'. + * + * # MAP foo/bar.h baz.h + * + * The upstream source file `foo/bar.h' needs to be brought over to the + * Harbour tree as `baz.h'. All notes above about hierarchical and flat + * trees strictly apply. + * + * For hierarchical source trees, the path separator must always be the UNIX + * forward slash (`/'). DOS-style backslash separators are not recognized and + * will produce undefined results. + * + * 2. MODES OF OPERATION + * --------------------- + * + * By default, patchup operates in `component version updating' mode - that is, + * refreshing the component version to a newer upstream version. + * + * If patchup is called with the `-rediff' command line argument, it switches + * to a `local diff refresh' mode. This mode is used to refresh the local diff + * after Harbour-specific modifications have been made to the component's + * source. + * + * It is strongly advised not to try to mix the two modes. If there are any + * pending local modifications, a rediff should be done before a component + * version update is performed. + * + * 3. TYPICAL WORKFLOW + * ------------------- + * + * Once it has been determined that a particular component needs an update, the URL + * argument has to be modified to point to the new source tree archive. VER should + * also be updated. While residing in the component's directory, patchup needs + * to be run. The rest is mostly automatic - patchup retrieves, unpacks and + * otherwise prepares the updated source tree, applies any local modifications, + * and copies any changes back to the Harbour tree (the current working directory). + * After some inspection and a test, it is ready to be committed. + * + * In rediff mode, care must be taken for the URL keyword to contain a reference + * to the version that is in the current Harbour tree (that basically means `do not + * touch anything', assuming correct information in the first place). After patchup + * is finished rediffing, the new `local changes' file (see `DIFF') may be inspected, + * and is ready to be committed. + * + * If errors are encountered during rediff, the contents of the temporary directory + * may be used as a starting basis for manual re-diffing. + * + * 4. TROUBLESHOOTING + * ------------------ + * + * Several things can go wrong, and patchup tries hard handle them as gracefully as + * possible. First and foremost, in case of even the slightest sign of something + * not happening as intended, patchup will not modify the Harbour tree at all. + * Everything is happening inside a temporary directory, which is not erased when + * patchup exits (not even when it exits normally), and where certain log files are + * created. These log files may contain information to help debugging in case of + * an (unhandled) error. + * + * The organization of the temporary directory is as follows: + * + * $(component) These directories are the extracted and modified versions + * $(component).orig of the upstream source tree. The `.orig' tree consists of + * only renamed (and renames followed up in the files), + * whereas the $(component) tree will have the local + * modifications (see the `DIFF' keyword) applied. These two + * directories are used for re-creating the local changes. + * + * root The new upstream source is unpacked in this directory. + * + * fetch.log The output `curl' produced while fetching the source + * archive. + * + * extract.log The output the extractor (gzip, bzip2) produced while + * uncompressing the archive. + * + * archive.log The output the archiver (tar, unzip) produced while + * unpacking the archive. + * + * patch.log The output `patch' produced while applying the local + * changes to $(component). + * + * diff.log The standard error of `diff' produced while re-creating + * the local changes (the standard output is the diff itself + * and is placed in the local changes file, see `DIFF'). + * + * some archive file The new source tree archive. + * + * In all error cases patchup will produce a meaningful error message. Armed with + * that and the information here, troubleshooting should not be much of a problem. + * + * 4. BUGS + * ------- + * + * None known. More testing on non-UNIX systems is desired. + * + */ + +#if 0 + #include "directry.ch" +#else + #define F_NAME 1 /* directry.ch */ + #define F_ATTR 5 /* directry.ch */ +#endif + +#if defined( _TRACE ) + #define TRACE( str ) OutStd( "T: " + str + OSNL ) +#else + #define TRACE( str ) +#endif + +#define OSPS hb_osPathSeparator() +#define OSNL hb_osNewLine() + +#define ONEARG_KW 2 /* one-arg line keyword */ +#define ONEARG_ARG 3 /* one-arg line argument */ +#define TWOARG_KW 2 /* two-arg line keyword */ +#define TWOARG_ARG1 3 /* two-arg line first argument */ +#define TWOARG_ARG2 4 /* two-arg line second argument */ + +#define FN_ORIG 1 /* original file name in maps */ +#define FN_HB 2 /* hb file name in maps */ + +STATIC s_aChangeMap := {} /* from-to file name map */ +STATIC s_cTempDir := NIL +STATIC s_nErrors := 0 /* error indicator */ +STATIC s_cSourceRoot := NIL /* top directory of the newly-unpacked source tree */ + +STATIC s_aTools := { ; + "patch" => NIL, ; + "diff" => NIL, ; + "curl" => NIL, ; + "tar" => NIL, ; + "gzip" => NIL, ; + "bzip2" => NIL, ; + "unzip" => NIL ; +} + +PROCEDURE Main( ... ) + + LOCAL cFile /* memoized Makefile */ + LOCAL aRegexMatch /* regex match results */ + LOCAL cMemoLine /* MemoLine */ + LOCAL cDiffFile /* Local modifications */ + LOCAL cCWD + LOCAL cThisComponent /* component being processed */ + LOCAL aOneMap /* one pair from s_aChangeMap */ + LOCAL cCommand /* patch/diff commands */ + LOCAL nRunResult /* patch/diff exit vals */ + LOCAL cDiffText /* diff will return the new diff in this */ + LOCAL nDiffFD /* for writing newly created diff file */ + LOCAL cArchiveURL /* URL for the component */ + LOCAL cTopIndicator /* file signifying the top of the component's source tree */ + LOCAL cStdOut /* stdout and stderr for various externally-run apps */ + LOCAL cStdErr + LOCAL lRediff := .F. /* whether or not operating as rediff */ + LOCAL aArgv + + LOCAL hRegexTake1Line := hb_regexComp( "^#[[:blank:]]*(ORIGIN|VER|URL|DIFF)[[:blank:]]+(.+?)[[:blank:]]*$" ) + LOCAL hRegexTake2Line := hb_regexComp( "^#[[:blank:]]*(MAP)[[:blank:]]+(.+?)[[:blank:]]+(.+?)[[:blank:]]*$" ) + + aArgv := hb_AParams() + IF ! Empty( aArgv ) + SWITCH aArgv[ 1 ] + CASE "-rediff" + lRediff := .T. + EXIT + CASE "-h" + CASE "-help" + CASE "--help" + CASE "/?" + Usage( 0 ) + OTHERWISE + Usage( 1 ) + ENDSWITCH + ENDIF + + IF ! hb_FileExists( "Makefile" ) + OutStd( "No `Makefile' in the current directory." + OSNL ) + ErrorLevel( 1 ) + QUIT + ENDIF + + SetupTools() + + cFile := MemoRead( "Makefile" ) + cDiffFile := NIL /* default to `no local diff' */ + + FOR EACH cMemoLine IN hb_ATokens( cFile, OSNL ) + + cMemoLine := AllTrim( cMemoLine ) + + IF ! Empty( aRegexMatch := hb_regex( hRegexTake1Line, cMemoLine ) ) + /* Process one-arg keywords */ + IF aRegexMatch[ ONEARG_KW ] == "DIFF" + cDiffFile := AllTrim( aRegexMatch[ ONEARG_ARG ] ) + ELSEIF aRegexMatch[ ONEARG_KW ] == "URL" + cArchiveURL := AllTrim( aRegexMatch[ ONEARG_ARG ] ) + ENDIF + + ELSEIF ! Empty( aRegexMatch := hb_regex( hRegexTake2Line, cMemoLine ) ) + /* Process two-arg keywords */ + IF aRegexMatch[ TWOARG_KW ] == "MAP" + /* Do not allow implicit destination with non-flat source spec */ + IF Empty( aRegexMatch[ TWOARG_ARG1 ] ) .AND. "/" $ aRegexMatch[ TWOARG_ARG2 ] + OutStd( "E: Non-flat source spec with implicit destination, offending line:" + OSNL ) + OutStd( aRegexMatch[ 1 ] + OSNL ) + ErrorLevel( 2 ) + QUIT + ENDIF + /* Do not allow tree spec in the destination ever */ + IF "/" $ aRegexMatch[ TWOARG_ARG2 ] + OutStd( "E: Non-flat destination, offending line:" + OSNL ) + OutStd( aRegexMatch[ 1 ] + OSNL ) + ErrorLevel( 2 ) + QUIT + ENDIF + /* If the source argument indicates the source tree is not flat, convert + * path separator to native. The HB tree is always flattened. */ + IF "/" $ aRegexMatch[ TWOARG_ARG1 ] + aRegexMatch[ TWOARG_ARG1 ] := StrTran( aRegexMatch[ TWOARG_ARG1 ], "/", OSPS ) + ENDIF + /* In case the priginal and the HB file names are identical, the + * second argument to `MAP' is optional. Due to the way the regex is + * constructed, in this case the last backref will contain the only + * file name, so shuffle arguments around accordingly + */ + AAdd( s_aChangeMap, { ; + iif( Empty( aRegexMatch[ TWOARG_ARG1 ] ), ; + aRegexMatch[ TWOARG_ARG2 ], ; + aRegexMatch[ TWOARG_ARG1 ] ), aRegexMatch[ TWOARG_ARG2 ] ; + } ) + /* If this is the first MAP entry, treat the original part as the + * source tree root indicator */ + IF Len( s_aChangeMap ) == 1 + cTopIndicator := s_aChangeMap[ 1 ][ FN_ORIG ] + ENDIF + ENDIF + ENDIF + NEXT + + IF lRediff .AND. cDiffFile == NIL + OutStd( "Requested rediff mode with no local diff, nothing to do." + OSNL ) + QUIT + ENDIF + + IF Empty( s_aChangeMap ) .AND. cDiffFile == NIL + OutStd( "No file name changes and no local diff, nothing to do." + OSNL ) + QUIT + ENDIF + + IF cDiffFile != NIL .AND. ! hb_FileExists( cDiffFile ) + OutStd( "E: `" + cDiffFile + "' does not exist" + OSNL ) + ErrorLevel( 2 ) + QUIT + ENDIF + + FClose( hb_FTempCreateEx( @s_cTempDir, NIL, FN_NameGet( hb_ProgName() ) + "_" ) ) + FErase( s_cTempDir ) + MakeDir( s_cTempDir ) + + cCWD := hb_CurDrive() + hb_osDriveSeparator() + OSPS + CurDir() + cThisComponent := FN_NameGet( cCWD ) + + MakeDir( CombinePath( s_cTempDir, cThisComponent ) ) + MakeDir( CombinePath( s_cTempDir, cThisComponent + ".orig" ) ) + MakeDir( CombinePath( s_cTempDir, "root" ) ) + + IF ! FetchAndExtract( cArchiveURL ) + OutStd( "E: Fetching or extracting the source archive failed." + OSNL ) + OutStd( " Inspect `" + s_cTempDir + "' for further clues." + OSNL ) + ErrorLevel( 2 ) + QUIT + ENDIF + + s_cSourceRoot := WalkAndFind( CombinePath( s_cTempDir, "root" ), cTopIndicator ) + IF s_cSourceRoot == NIL + OutStd( "E: Unable to find the new tree's root" + OSNL ) + OutStd( " Inspect `" + s_cTempDir + "'" + OSNL ) + ErrorLevel( 2 ) + QUIT + ENDIF + + /* + * Create two copies of the relevant portions of the source archive. + * The pristine tree is for reference, used as the left component of the diff + * Our tree will have the local diff applied, and used as the right component of the diff + */ + FOR EACH aOneMap IN s_aChangeMap + IF ! hb_FileExists( CombinePath( s_cSourceRoot, aOneMap[ FN_ORIG ] ) ) + OutStd( "W: `" + aOneMap[ FN_ORIG ] + "' does not exist in the source tree" + OSNL ) + OutStd( " I will do what i can, but you'd better check the results manually." + OSNL ) + s_nErrors++ + ELSE + /* Create the `pristine tree' */ + hb_FCopy( CombinePath( s_cSourceRoot, aOneMap[ FN_ORIG ] ), ; + CombinePath( s_cTempDir, cThisComponent + ".orig", aOneMap[ FN_HB ] ) ) + + /* Munch the file, applying the appropriate xforms */ + hb_FileTran( CombinePath( s_cTempDir, cThisComponent + ".orig", aOneMap[ FN_HB ] ) ) + + /* If operating in `rediff' mode, copy the current Harbour component tree; + * otherwise, duplicate the pristine tree */ + + IF lRediff + hb_FCopy( aOneMap[ FN_HB ], ; + CombinePath( s_cTempDir, cThisComponent, aOneMap[ FN_HB ] ) ) + + ELSE + /* Copy it to `our tree' */ + hb_FCopy( CombinePath( s_cTempDir, cThisComponent + ".orig", aOneMap[ FN_HB ] ),; + CombinePath( s_cTempDir, cThisComponent, aOneMap[ FN_HB ] ) ) + ENDIF + + ENDIF + NEXT + + IF cDiffFile != NIL + + IF ! lRediff /* If we have a local diff, and are not to re-create it, apply */ + cCommand := hb_strFormat( "%s -d %s -p 1 -i %s", ; + s_aTools[ "patch" ], ; + CombinePath( s_cTempDir, cThisComponent ), ; + CombinePath( cCWD, cDiffFile ) ) + TRACE( "Running " + cCommand ) + nRunResult := hb_processRun( cCommand, , @cStdOut, @cStdErr, .F. ) + SaveLog( "patch", cStdOut, cStdErr ) + IF nRunResult != 0 + OutStd( "W: Unexpected events happened during patching, inspect " + s_cTempDir + OSNL ) + s_nErrors++ + ENDIF + ENDIF + + /* Re-create the diff */ + cCommand := hb_strFormat( "%s -urN %s %s", ; + s_aTools[ "diff" ], cThisComponent + ".orig", cThisComponent ) + + DirChange( s_cTempDir ) + TRACE( "Running " + cCommand ) + nRunResult := hb_processRun( cCommand, , @cDiffText, @cStdErr, .F. ) + DirChange( cCWD ) + + SaveLog( "diff", NIL, cStdErr ) + + nDiffFD := FCreate( cDiffFile ) + FWrite( nDiffFD, cDiffText ) + FClose( nDiffFD ) + + ENDIF + + /* Only copy files back to the live tree if no errors were encountered */ + IF s_nErrors == 0 + IF ! lRediff + /* Only copy the complete new tree back if not in Rediff mode */ + FOR EACH aOneMap IN s_aChangeMap + hb_FCopy( CombinePath( s_cTempDir, cThisComponent, aOneMap[ FN_HB ] ), aOneMap[ FN_HB ] ) + NEXT + ENDIF + + IF cDiffFile != NIL + /* Copy the diff back to the live tree */ + hb_FCopy( CombinePath( s_cTempDir, cDiffFile ), cDiffFile ) + ENDIF + + ELSE + OutStd( "Errors were encountered, no changes are made to your Harbour tree." + OSNL ) + OutStd( "Inspect " + s_cTempDir + " for further clues." + OSNL ) + ENDIF + + IF ! lRediff + OutStd( "Don't forget to update Makefile with the new version and URL information." + OSNL ) + ENDIF + OutStd( "The temporary directory `" + s_cTempDir + "' has not been removed." +OSNL ) + + RETURN + +/* Utility functions */ + +STATIC PROCEDURE SetupTools() + +#if defined( __PLATFORM__UNIX ) + LOCAL cExeExt := "" +#else + LOCAL cExeExt := ".exe" +#endif + LOCAL cPathComp + LOCAL cTool + + /* Look for g$tool first, only attempt raw name if it isn't found + * Helps non-GNU userland systems with GNU tools installed. + * Only several of the tools are known to have GNU variants. */ + FOR EACH cPathComp IN hb_ATokens( hb_GetEnv( "PATH" ), hb_osPathListSeparator() ) + FOR EACH cTool IN hb_HKeys( s_aTools ) + IF cTool $ "patch|diff|tar" .AND. hb_FileExists( CombinePath( cPathComp, "g" + cTool ) + cExeExt ) + s_aTools[ cTool ] := CombinePath( cPathComp, "g" + cTool ) + ENDIF + NEXT + NEXT + + FOR EACH cPathComp in hb_ATokens( hb_GetEnv( "PATH" ), hb_osPathListSeparator() ) + FOR EACH cTool IN hb_HKeys( s_aTools ) + IF s_aTools[ cTool ] == NIL .AND. hb_FileExists( CombinePath( cPathComp, cTool ) + cExeExt ) + s_aTools[ cTool ] := CombinePath( cPathComp, cTool ) + ENDIF + NEXT + NEXT + + FOR EACH cTool in hb_HKeys( s_aTools ) + IF s_aTools[ cTool ] == NIL + OutStd( "E: Can not find " + cTool + OSNL ) + ErrorLevel( 1 ) + QUIT + ENDIF + NEXT + + RETURN + +STATIC FUNCTION CombinePath( ... ) + + LOCAL aArguments := hb_AParams() + LOCAL cRetVal := "" + LOCAL nI + + IF Len( aArguments ) == 2 + cRetVal := aArguments[ 1 ] + OSPS + aArguments[ 2 ] + ELSE + cRetVal := aArguments[ 1 ] + OSPS + FOR nI := 2 TO Len( aArguments ) - 1 + cRetVal += aArguments[ nI ] + OSPS + NEXT + cRetVal += aArguments[ Len( aArguments ) ] + ENDIF + + RETURN cRetVal + +STATIC FUNCTION WalkAndFind( cTop, cLookFor ) + + LOCAL aDir + LOCAL aDirEntry + LOCAL cRetVal := NIL + + cTop += iif( Right( cTop, 1 ) $ "/\", "", hb_osPathSeparator() ) + aDir := Directory( cTop + hb_osFileMask(), "D" ) + + ASORT( aDir,,, { |aLeft| !( aLeft[ F_ATTR ] $ "D" ) } ) /* Files first */ + + FOR EACH aDirEntry IN aDir + IF !( aDirEntry[ F_ATTR ] == "D" ) + IF aDirEntry[ F_NAME ] == cLookFor + cRetVal := cTop + EXIT + ENDIF + ELSEIF !( aDirEntry[ F_NAME ] == "." ) .AND. !( aDirEntry[ F_NAME ] == ".." ) + cRetVal := WalkAndFind( cTop + aDirEntry[ F_NAME ], cLookFor ) + IF ! Empty( cRetVal ) + EXIT + ENDIF + ENDIF + NEXT + + RETURN cRetVal + +STATIC FUNCTION FetchAndExtract( cArchiveURL ) + + LOCAL cCommand + LOCAL cExtractor := NIL + LOCAL cExtractorArgs := NIL + LOCAL cExtractedFileName := NIL + LOCAL cArchiver := NIL + LOCAL cArchiverArgs := NIL + LOCAL nResult + LOCAL cStdOut + LOCAL cStdErr + LOCAL cPattern + LOCAL cMatchedPattern + LOCAL cFileName + LOCAL cFrag + + /* Any given package is surely available in at least one of these formats, + * pick one of these, refrain from the more exotic ones. */ + + LOCAL aActionMap := { ; + '.tar.gz|.tgz' => { ; + 'Extractor' => 'gzip', ; + 'ExtractorArgs' => '-d', ; + 'ExtractedFile' => '.tar', ; + 'Archiver' => 'tar', ; + 'ArchiverArgs' => '-C %s -xvf' ; + }, ; + '.tar.bz2|.tbz|.tbz2' => { ; + 'Extractor' => 'bzip2', ; + 'ExtractorArgs' => '-d', ; + 'ExtractedFile' => '.tar', ; + 'Archiver' => 'tar', ; + 'ArchiverArgs' => '-C %s -xvf' ; + }, ; + '.zip' => { ; + 'Extractor' => NIL, ; + 'ExtractorArgs' => NIL, ; + 'ExtractedFile' => NIL, ; + 'Archiver' => 'unzip', ; + 'ArchiverArgs' => '-d %s' ; + } ; + } + + cFileName := URL_GetFileName( cArchiveURL ) + + FOR EACH cPattern IN hb_HKeys( aActionMap ) + FOR EACH cFrag IN HB_ATokens( cPattern, "|" ) + IF At( cFrag, cFileName ) != 0 + cMatchedPattern := cFrag + cExtractor := aActionMap[ cPattern ][ 'Extractor' ] + cExtractorArgs := aActionMap[ cPattern ][ 'ExtractorArgs' ] + cExtractedFileName := iif( aActionMap[ cPattern ][ 'ExtractedFile' ] == NIL, ; + NIL, ; + Left( cFileName, Len( cFileName ) - ; + Len( cMatchedPattern ) ) + ; + aActionMap[ cPattern ][ 'ExtractedFile' ] ; + ) + cArchiver := aActionMap[ cPattern ][ 'Archiver' ] + cArchiverArgs := aActionMap[ cPattern ][ 'ArchiverArgs' ] + EXIT + ENDIF + NEXT + NEXT + + IF cArchiver == NIL + OutStd( "E: Can not find archiver for `" + ; + FN_NameExtGet( cArchiveURL ) + "'" + OSNL ) + RETURN .F. + ELSE + /* Fetch */ + cCommand := hb_strFormat( "%s -L -# -o %s %s", s_aTools[ "curl" ], ; + CombinePath( s_cTempDir, cFileName ), ; + FN_Escape( cArchiveURL ) ) + TRACE( "Running " + cCommand ) + nResult := hb_processRun( cCommand, , , @cStdErr, .F. ) + SaveLog( "fetch", cStdOut, cStdErr ) + IF nResult != 0 + OutStd( "E: Error fetching " + cArchiveURL + OSNL ) + RETURN .F. + ENDIF + + /* Extract */ + IF cExtractor != NIL /* May not need extraction */ + cCommand := hb_strFormat( "%s " + cExtractorArgs + " %s", ; + cExtractor, CombinePath( s_cTempDir, cFileName ) ) + TRACE( "Running " + cCommand ) + nResult := hb_processRun( cCommand, , @cStdOut, @cStdErr, .F. ) + SaveLog( "extract", cStdOut, cStdErr ) + IF nResult != 0 + OutStd( "E: Error extracting " + cFileName + OSNL ) + RETURN .F. + ENDIF + ELSE + cExtractedFileName := cFileName + ENDIF + + /* Unarchive */ + cCommand := hb_strFormat( "%s " + cArchiverArgs + " %s", ; + cArchiver, CombinePath( s_cTempDir, "root" ), ; + CombinePath( s_cTempDir, cExtractedFileName ) ) + TRACE( "Running " + cCommand ) + nResult := hb_processRun( cCommand, , @cStdOut, @cStdErr, .F. ) + SaveLog( "archive", cStdOut, cStdErr ) + IF nResult != 0 + OutStd( "E: Error unarchiving " + cFileName + OSNL ) + RETURN .F. + ENDIF + ENDIF + + RETURN .T. + +PROCEDURE SaveLog( cFNTemplate, cStdOut, cStdErr ) + + LOCAL nLogFD + + nLogFD := FCreate( CombinePath( s_cTempDir, cFNTemplate + ".log" ) ) + + IF cStdOut != NIL + FWrite( nLogFd, "stdout:" + OSNL ) + FWrite( nLogFD, cStdOut ) + ENDIF + IF cStdErr != NIL + FWrite( nLogFd, "stderr:" + OSNL ) + FWrite( nLogFD, cStdErr ) + ENDIF + FClose( nLogFD ) + + RETURN + +PROCEDURE Usage( nExitVal ) + + OutStd( "Usage: " + FN_NameExtGet( hb_ProgName() ) + " [-h|-help|-rediff]" + OSNL ) + OutStd( " Documentation is provided in the source code." + OSNL ) + ErrorLevel( nExitVal ) + QUIT + + RETURN + +/* from hbmk2 */ + +STATIC FUNCTION FN_DirGet( cFileName ) + + LOCAL cDir + + hb_FNameSplit( cFileName, @cDir ) + + RETURN cDir + +STATIC FUNCTION FN_NameGet( cFileName ) + + LOCAL cName + + hb_FNameSplit( cFileName,, @cName ) + + RETURN cName + +STATIC FUNCTION FN_NameExtGet( cFileName ) + + LOCAL cName, cExt + + hb_FNameSplit( cFileName,, @cName, @cExt ) + + RETURN hb_FNameMerge( NIL, cName, cExt ) + +STATIC FUNCTION FN_ExtGet( cFileName ) + + LOCAL cExt + + hb_FNameSplit( cFileName,,, @cExt ) + + RETURN cExt + +STATIC FUNCTION URL_GetFileName( cURL ) + + LOCAL aComponents + LOCAL cName + LOCAL nIdx + + aComponents := hb_ATokens( cURL, "/" ) + nIdx := Len( aComponents ) + + IF nIdx < 4 /* now what */ + RETURN .F. + ENDIF + + cName := aComponents[ nIdx ] + cName := Left( cName, At( "?", cName ) - 1 ) /* strip params */ + + DO WHILE !( "." $ cName ) + cName := aComponents[ --nIdx ] + IF nIdx < 4 /* don't drain all components */ + RETURN .F. + ENDIF + ENDDO + + RETURN cName + +STATIC FUNCTION hb_FileTran( cFileName ) + + LOCAL cFileContent + LOCAL cTransformedContent + LOCAL aChange + + cFileContent := hb_MemoRead( cFileName ) + + /* CRLF -> LF */ + cTransformedContent := StrTran( cFileContent, Chr( 13 ) + Chr( 10 ), Chr( 10 ) ) + + /* LF -> native */ + cTransformedContent := StrTran( cTransformedContent, Chr( 10 ), OSNL ) + + FOR EACH aChange IN s_aChangeMap + /* Local-style includes */ + cTransformedContent := StrTran( cTransformedContent, ; + Chr( 34 ) + aChange[ 1 ] + Chr( 34 ), ; + Chr( 34 ) + aChange[ 2 ] + Chr( 34 ) ) + + /* System-style include */ + cTransformedContent := StrTran( cTransformedContent, ; + "<" + aChange[ 1 ] + ">", ; + "<" + aChange[ 2 ] + ">" ) + NEXT + + RETURN hb_MemoWrit( cFileName, cTransformedContent ) + +STATIC FUNCTION FN_Escape( cFileName ) +#if defined( __PLATFORM__UNIX ) + RETURN cFileName +#else + RETURN Chr( 34 ) + cFileName + Chr( 34 ) +#endif + +/* + * vim: ts=3 expandtab + */