Files
harbour-core/contrib/make.hb
Viktor Szakats b16bd0a0fb 2017-09-14 23:00 UTC Viktor Szakats (vszakats users.noreply.github.com)
* contrib/hbmisc/tests/pe.prg
  * contrib/hbpost.hbm
  * contrib/hbpre.hbm
  * contrib/make.hb
    * sync text and minor code/comment updates with the 3.4 fork
2017-09-14 23:01:30 +00:00

779 lines
24 KiB
Plaintext
Executable File

#!/usr/bin/env hbmk2
/*
* Package build orchestrator script
*
* Copyright 2010 Viktor Szakats (vszakats.net/harbour)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* (or visit their website at https://www.gnu.org/licenses/).
*
*/
#pragma -w3
#pragma -km+
#pragma -ko+
#include "directry.ch"
#define _ACT_INC_CLEAN 1
#define _ACT_INC 2
#define _ACT_INC_INST 3
#define _ACT_INC_REBUILD 4
#define _ACT_INC_REBUILD_INST 5
STATIC sc_hActions := { ;
_ACT_INC_CLEAN => "clean", ;
_ACT_INC => "build", ;
_ACT_INC_INST => "build and install", ;
_ACT_INC_REBUILD => "rebuild", ;
_ACT_INC_REBUILD_INST => "rebuild and install" }
STATIC s_cBase
STATIC s_cHome
STATIC s_cRoot
STATIC s_cBinDir
STATIC s_cReBase
PROCEDURE Main( ... )
LOCAL hProjectList
LOCAL aParams
hb_cdpSelect( "UTF8EX" )
s_cBase := ""
s_cReBase := ""
IF Empty( GetEnv( "HB_HOST_BIN_DIR" ) )
s_cHome := StrTran( hb_DirBase(), hb_ps(), "/" )
s_cRoot := s_cHome + "../"
ELSE
s_cHome := ""
s_cRoot := "../"
ENDIF
#if defined( __HBSCRIPT__HBSHELL )
s_cBinDir := hbshell_DirBase()
#else
s_cBinDir := hb_DirBase()
#endif
/* For *nixes */
s_cBinDir := hb_PathNormalize( s_cBinDir )
/* Load list of projects */
hProjectList := { => }
LoadProjectListFromFile( hProjectList, s_cHome + "hbplist.txt" )
LoadProjectListFromString( hProjectList, GetEnv( "HB_BUILD_ADDONS" ) )
aParams := hb_AParams()
IF AScanL( aParams, "verbose" ) > 0
hb_SetEnv( "HB_BUILD_VERBOSE", "yes" )
ENDIF
/* Build */
IF Empty( GetEnv( "HB_HOST_BIN_DIR" ) )
Standalone( aParams, hProjectList )
ELSE
GNUMake( aParams, hProjectList )
ENDIF
RETURN
/* Workflow translation for standalone operation:
GNU Make parameter nAction hbmk2 options
-- -------------- -------------- ---------------------- -------------------------
#1 clean clean _ACT_INC_CLEAN -inc -clean
#2 _ACT_INC -inc
#3 clean all clean all _ACT_INC_REBUILD -inc -rebuildall
#4 install install _ACT_INC_INST -inc -instpath=
#5 clean install clean install _ACT_INC_REBUILD_INST -inc -rebuildall -instpath=
*/
STATIC PROCEDURE Standalone( aParams, hProjectList )
LOCAL hProjectReqList
LOCAL cOptionsUser
LOCAL nAction
LOCAL tmp
LOCAL tmp1
LOCAL lCustom
LOCAL cCustomDir
/* Processing command-line options */
DO CASE
CASE AScanL( aParams, "clean" ) > 0 .AND. ;
AScanL( aParams, "all" ) > 0 .AND. ;
AScanL( aParams, "all" ) > AScanL( aParams, "clean" )
nAction := _ACT_INC_REBUILD
CASE AScanL( aParams, "rebuild" ) > 0
nAction := _ACT_INC_REBUILD
CASE AScanL( aParams, "clean" ) > 0 .AND. ;
AScanL( aParams, "install" ) > 0 .AND. ;
AScanL( aParams, "install" ) > AScanL( aParams, "clean" )
nAction := _ACT_INC_REBUILD_INST
CASE AScanL( aParams, "clean" ) > 0
nAction := _ACT_INC_CLEAN
CASE AScanL( aParams, "install" ) > 0
nAction := _ACT_INC_INST
OTHERWISE
nAction := _ACT_INC
ENDCASE
/* Processing user options */
cOptionsUser := ""
lCustom := .F.
FOR EACH tmp IN aParams
IF ! Lower( tmp ) == "install" .AND. ;
! Lower( tmp ) == "clean" .AND. ;
! Lower( tmp ) == "all" .AND. ;
! Lower( tmp ) == "first" .AND. ;
! Lower( tmp ) == "rebuild" .AND. ;
! Lower( tmp ) == "verbose"
cOptionsUser += " " + tmp
/* If anything else is passed than options or GNU Make keywords,
consider it a custom project build, f.e. in tests */
IF ! Left( tmp, 1 ) == "-"
lCustom := .T.
ENDIF
ENDIF
NEXT
/* Assemble list of primary targets (registered projects in or under
current directory) */
hProjectReqList := { => }
IF hb_DirExists( AllTrim( cOptionsUser ) )
cCustomDir := hb_cwd( AllTrim( cOptionsUser ) )
cOptionsUser := ""
lCustom := .F.
s_cHome += "../"
s_cRoot += "../"
ENDIF
IF ! lCustom
/* Find out which projects are in current dir, these will be our
primary targets */
FOR EACH tmp IN hProjectList
tmp1 := hb_ps() + hb_FNameDir( hb_DirSepToOS( tmp:__enumKey() ) )
IF tmp1 == Right( hb_cwd(), Len( tmp1 ) ) /* Not ultimate solution */
hProjectReqList[ tmp:__enumKey() ] := tmp:__enumKey()
s_cReBase := SubStr( tmp1, 2 )
ENDIF
NEXT
IF Empty( hProjectReqList )
lCustom := .T.
ELSE
OutStd( hb_StrFormat( "! Package %1$s... %2$d project(s)", sc_hActions[ nAction ], Len( hProjectReqList ) ) + hb_eol() )
ENDIF
ENDIF
IF lCustom
mk_hb_processRun( s_cBinDir + "hbmk2" + cOptionsUser )
RETURN
ENDIF
/* Start building */
build_projects( nAction, hProjectList, hProjectReqList, cOptionsUser, .T. )
IF ! Empty( cCustomDir )
hb_cwd( cCustomDir )
ENDIF
RETURN
/* Workflow translation from GNU Make to hbmk2:
GNU Make parameter HB_MAKECMDGOALS nAction hbmk2 options
-- -------------- ---------- ---------------- ---------------------- -------------------------
#1 clean clean clean _ACT_INC_CLEAN -inc -clean
#2 all _ACT_INC -inc
#3 install install install _ACT_INC_INST -inc -instpath=
#4 clean all clean clean all _ACT_INC_CLEAN -inc -clean
first clean all _ACT_INC_REBUILD -inc -rebuildall
#5 clean install clean clean install _ACT_INC_CLEAN -inc -clean
install clean install _ACT_INC_REBUILD_INST -inc -rebuildall -instpath=
#6 install clean install install clean _ACT_INC_INST -inc -instpath=
clean install clean _ACT_INC_CLEAN -inc -clean
*/
STATIC PROCEDURE GNUMake( aParams, hProjectList )
LOCAL cProject
LOCAL hProjectReqList
LOCAL cFilter
LOCAL aFilter
LOCAL lFilterNegative
LOCAL aGNUMakeParams
LOCAL nAction
LOCAL tmp
/* Check if the requirements are met and if we have anything to do */
IF Empty( GetEnv( "HB_PLATFORM" ) ) .OR. ;
Empty( GetEnv( "HB_COMPILER" ) ) .OR. ;
Empty( GetEnv( "HB_HOST_BIN_DIR" ) )
ErrorLevel( 9 )
RETURN
ENDIF
/* Determine the mode of operation */
aGNUMakeParams := hb_ATokens( Lower( GetEnv( "HB_MAKECMDGOALS" ) ) )
DO CASE
CASE AScanL( aParams, "clean" ) > 0
IF AScanL( aGNUMakeParams, "clean" ) > 0 .AND. ;
AScanL( aGNUMakeParams, "install" ) > 0 .AND. ;
AScanL( aGNUMakeParams, "install" ) > AScanL( aGNUMakeParams, "clean" )
nAction := _ACT_INC_CLEAN
ELSE
nAction := _ACT_INC_CLEAN
ENDIF
CASE AScanL( aParams, "install" ) > 0
IF AScanL( aGNUMakeParams, "clean" ) > 0 .AND. ;
AScanL( aGNUMakeParams, "install" ) > 0 .AND. ;
AScanL( aGNUMakeParams, "install" ) > AScanL( aGNUMakeParams, "clean" )
/* Use rebuild mode. This is needed because the clean phase might not
have been called previously by core GNU Make, f.e. because hbrun
or hbmk2 wasn't available. -rebuildall is costless, so we do it to
make sure to build cleanly. [vszakats] */
nAction := _ACT_INC_REBUILD_INST
ELSE
nAction := _ACT_INC_INST
ENDIF
CASE AScanL( aParams, "first" ) > 0
IF AScanL( aGNUMakeParams, "clean" ) > 0 .AND. ;
AScanL( aGNUMakeParams, "all" ) > 0 .AND. ;
AScanL( aGNUMakeParams, "all" ) > AScanL( aGNUMakeParams, "clean" )
nAction := _ACT_INC_REBUILD
ELSE
nAction := _ACT_INC
ENDIF
CASE AScanL( aParams, "rebuild" ) > 0
nAction := _ACT_INC_REBUILD
OTHERWISE
nAction := _ACT_INC
ENDCASE
/* Assemble list of projects to be built */
cFilter := GetEnv( "HB_BUILD_CONTRIBS" )
IF ! Empty( cFilter )
OutStd( "! HB_BUILD_CONTRIBS: " + cFilter + hb_eol() )
ENDIF
IF cFilter == "no"
RETURN
ENDIF
aFilter := iif( Empty( cFilter ), {}, hb_ATokens( cFilter,, .T. ) )
IF Len( aFilter ) >= 1 .AND. aFilter[ 1 ] == "no"
hb_ADel( aFilter, 1, .T. )
lFilterNegative := .T.
ELSE
lFilterNegative := .F.
ENDIF
hProjectReqList := { => }
FOR EACH tmp IN hProjectList
hProjectReqList[ tmp:__enumKey() ] := tmp:__enumKey()
NEXT
IF ! Empty( aFilter )
IF ! lFilterNegative
hProjectReqList := { => }
ENDIF
FOR EACH cProject IN aFilter
FOR EACH tmp IN hProjectList
IF hb_FileMatch( hb_DirSepToOS( cProject ), hb_DirSepToOS( tmp:__enumKey() ) ) .OR. ;
hb_FileMatch( hb_DirSepToOS( cProject ), hb_DirSepDel( hb_FNameDir( hb_DirSepToOS( tmp:__enumKey() ) ) ) )
IF lFilterNegative
IF tmp:__enumKey() $ hProjectReqList
hb_HDel( hProjectReqList, tmp:__enumKey() )
ENDIF
ELSE
hProjectReqList[ tmp:__enumKey() ] := tmp:__enumKey()
ENDIF
ENDIF
NEXT
NEXT
ENDIF
IF Empty( hProjectReqList )
RETURN
ENDIF
/* Clearing envvars that may interact with hbmk2 */
/* Saving original install dirs to our own variables */
hb_SetEnv( "_HB_INSTALL_BIN", GetEnv( "HB_INSTALL_BIN" ) )
hb_SetEnv( "_HB_INSTALL_LIB", GetEnv( "HB_INSTALL_LIB" ) )
hb_SetEnv( "_HB_INSTALL_DYN", GetEnv( "HB_INSTALL_DYN" ) )
hb_SetEnv( "_HB_INSTALL_INC", GetEnv( "HB_INSTALL_INC" ) )
hb_SetEnv( "_HB_INSTALL_MAN", GetEnv( "HB_INSTALL_MAN" ) )
hb_SetEnv( "_HB_INSTALL_ETC", GetEnv( "HB_INSTALL_ETC" ) )
hb_SetEnv( "_HB_INSTALL_CONTRIB", GetEnv( "HB_INSTALL_CONTRIB" ) )
/* Override hbmk2 auto-detection. WARNING: Must be in sync with global.mk logic */
hb_SetEnv( "HB_INSTALL_PREFIX", s_cRoot )
hb_SetEnv( "HB_INSTALL_BIN", s_cRoot + "bin/" + GetEnv( "HB_PLATFORM" ) + "/" + GetEnv( "HB_COMPILER" ) + GetEnv( "HB_BUILD_NAME" ) )
hb_SetEnv( "HB_INSTALL_LIB", s_cRoot + "lib/" + GetEnv( "HB_PLATFORM" ) + "/" + GetEnv( "HB_COMPILER" ) + GetEnv( "HB_BUILD_NAME" ) )
hb_SetEnv( "HB_INSTALL_DYN" )
hb_SetEnv( "HB_INSTALL_INC", s_cRoot + "include" )
/* Start building */
OutStd( hb_StrFormat( "! Started package %1$s...", sc_hActions[ nAction ] ) + hb_eol() )
build_projects( nAction, hProjectList, hProjectReqList, "", .F. )
OutStd( hb_StrFormat( "! Finished package %1$s...", sc_hActions[ nAction ] ) + hb_eol() )
RETURN
STATIC PROCEDURE build_projects( nAction, hProjectList, hProjectReqList, cOptionsUser, lStdAlone )
LOCAL aPairList
LOCAL aSortedList
LOCAL cOptions
LOCAL lInstall
LOCAL cMakeFlags
LOCAL cProject
LOCAL cProjectPath
LOCAL lPrimary
LOCAL lContainer
LOCAL cDynSuffix
LOCAL nErrorLevel
/* Signal that we're doing a Harbour build */
hb_SetEnv( "_HB_BUILD_", "yes" )
/* Preprocessing */
IF Len( hProjectReqList ) > 1
OutStd( hb_StrFormat( "! Calculating build order for %1$d projects...", Len( hProjectReqList ) ) + hb_eol() )
ENDIF
aPairList := {}
FOR EACH cProject IN hProjectReqList
call_hbmk2_hbinfo( s_cBase + s_cHome + cProject, hProjectList[ cProject ] )
DeptLinesToDeptPairList( aPairList, cProject, hProjectList[ cProject ][ "aDept" ] )
NEXT
aSortedList := TopoSort( aPairList )
/* Add referenced project not present in our list and featuring an .hbp
file */
FOR EACH cProject IN aSortedList
IF AddProject( hProjectList, @cProject )
call_hbmk2_hbinfo( s_cBase + s_cHome + cProject, hProjectList[ cProject ] )
hProjectList[ cProject ][ "lFromContainer" ] := NIL
ENDIF
NEXT
/* Load project information for dependencies too
(we need "cType" to decide about dynamic build) */
IF GetEnv( "HB_BUILD_CONTRIB_DYN" ) == "yes"
FOR EACH cProject IN aSortedList
IF ! cProject $ hProjectReqList .AND. ;
cProject $ hProjectList .AND. ;
! "lChecked" $ hProjectList[ cProject ]
call_hbmk2_hbinfo( s_cBase + s_cHome + cProject, hProjectList[ cProject ] )
ENDIF
NEXT
ENDIF
/* Convert action to hbmk2 options */
cOptions := " -inc"
SWITCH nAction
CASE _ACT_INC_CLEAN
cOptions += " -clean"
EXIT
CASE _ACT_INC_REBUILD
CASE _ACT_INC_REBUILD_INST
cOptions += " -rebuildall"
EXIT
ENDSWITCH
cMakeFlags := GetEnv( "MAKEFLAGS" )
IF " -j " $ " " + cMakeFlags + " "
/* GNU Make uses job server to limit number of concurrent operations
We cannot read it from MAKEFLAGS so I set it to arbitrary value: 8 */
cOptions += " -jobs=8"
ENDIF
lInstall := nAction == _ACT_INC_INST .OR. ;
nAction == _ACT_INC_REBUILD_INST
hb_SetEnv( iif( lStdAlone, "_HB_BUILD_INSTALL_STDALONE", "_HB_BUILD_INSTALL" ), iif( lInstall, "yes", NIL ) )
/* Build the dependencies and primary targets in sorted order */
FOR EACH cProject IN aSortedList DESCEND
IF cProject $ hProjectList
cProjectPath := s_cBase + s_cHome + cProject
lPrimary := cProject $ hProjectReqList
lContainer := "lFromContainer" $ hProjectList[ cProject ]
IF ( nErrorLevel := call_hbmk2( cProjectPath, iif( lPrimary .OR. lContainer, iif( lContainer, cOptions, cOptions + cOptionsUser ), " -inc" ) + ;
iif( ( lPrimary .OR. lContainer ) .AND. ;
hProjectList[ cProject ][ "cType" ] $ "hblib|hbdyn" .AND. ;
GetEnv( "HB_REBUILD_EXTERN" ) == "yes", " -hbx=" + hb_FNameExtSet( cProjectPath, ".hbx" ), "" ), NIL ) ) == 0
/* Build dynamic lib */
IF GetEnv( "HB_BUILD_CONTRIB_DYN" ) == "yes" .AND. hProjectList[ cProject ][ "cType" ] == "hblib"
/* Is this a platform where import libs are used? */
IF "|" + hProjectList[ cProject ][ "cPlatform" ] + "|" $ "|win|dos|os2|"
IF Empty( hProjectList[ cProject ][ "cDynSuffix" ] )
cDynSuffix := "_dll"
ELSE
cDynSuffix := hProjectList[ cProject ][ "cDynSuffix" ]
ENDIF
ELSE
cDynSuffix := hb_libExt()
ENDIF
call_hbmk2( cProjectPath, iif( lPrimary .OR. lContainer, iif( lContainer, cOptions, cOptions + cOptionsUser ), " -inc" ), cDynSuffix )
ENDIF
IF lPrimary .OR. lContainer
/* Compile documentation */
IF lInstall
mk_hbd( hb_FNameDir( hb_DirSepToOS( cProjectPath ) ) )
ENDIF
ENDIF
ELSE
/* Ignore certain non-fatal hbmk2 return values */
IF nErrorLevel != 10 .AND. ;
nErrorLevel != 20 .AND. ;
nErrorLevel != 50
ErrorLevel( nErrorLevel )
EXIT
ENDIF
ENDIF
ENDIF
NEXT
RETURN
STATIC PROCEDURE call_hbmk2_hbinfo( cProjectPath, hProject )
LOCAL cStdOut
LOCAL cDir
LOCAL cName
LOCAL tmp
LOCAL hInfo
LOCAL nErrorLevel
hProject[ "cType" ] := ""
hProject[ "aDept" ] := {}
hProject[ "lChecked" ] := NIL
IF ( nErrorLevel := call_hbmk2( cProjectPath, " --hbinfo", NIL,, @cStdOut ) ) == 0
IF hb_jsonDecode( cStdOut, @hInfo ) == 0
OutStd( "! Warning: Received invalid result from 'hbmk2 --hbinfo'" + hb_eol() )
ENDIF
hProject[ "cType" ] := hbmk2_hbinfo_getitem( hInfo, "targettype" )
hProject[ "cOutputName" ] := hbmk2_hbinfo_getitem( hInfo, "outputname" )
hProject[ "cDynSuffix" ] := hbmk2_hbinfo_getitem( hInfo, "dynsuffix" )
hProject[ "cPlatform" ] := hbmk2_hbinfo_getitem( hInfo, "platform" )
FOR EACH tmp IN hb_ATokens( hbmk2_hbinfo_getitem( hInfo, "hbctree" ), Chr( 10 ) )
IF ! Empty( tmp )
hb_FNameSplit( LTrim( tmp ), @cDir, @cName )
#ifdef __PLATFORM__DOS
/* Ignore long filenames on MS-DOS hosts */
IF Len( cName ) > 8
LOOP
ENDIF
#endif
AAdd( hProject[ "aDept" ], { "nDepth" => Len( tmp ) - Len( LTrim( tmp ) ), ;
"cFileName_HBP" => StrTran( hb_PathNormalize( hb_PathJoin( s_cRebase, hb_FNameExtSet( hb_DirSepToOS( LTrim( tmp ) ), ".hbp" ) ) ), "\", "/" ) } )
ENDIF
NEXT
ELSE
OutStd( hb_StrFormat( "! Warning: 'hbmk2 --hbinfo' failed with exit code %1$d", nErrorLevel ) + hb_eol() )
ENDIF
RETURN
STATIC FUNCTION hbmk2_hbinfo_getitem( hInfo, cItem )
RETURN iif( HB_ISHASH( hInfo ), hb_HGetDef( hInfo, cItem, "" ), "" )
STATIC FUNCTION call_hbmk2( cProjectPath, cOptionsPre, cDynSuffix, cStdErr, cStdOut )
LOCAL nErrorLevel
LOCAL cOptionsLibDyn := ""
LOCAL cCommand
/* Making sure that user settings do not interfere with the std build process. */
hb_SetEnv( "HBMK_OPTIONS" )
hb_SetEnv( "HARBOUR" )
hb_SetEnv( "HARBOURCMD" )
hb_SetEnv( "CLIPPER" )
hb_SetEnv( "CLIPPERCMD" )
IF cDynSuffix != NIL
hb_SetEnv( "_HB_DYNSUFF", cDynSuffix ) /* Request dll version of Harbour contrib dependencies (the implibs) to be linked (experimental) */
cOptionsPre += " -hbdyn"
IF hb_FileExists( hb_FNameExtSet( cProjectPath, ".hbc" ) )
cOptionsLibDyn += " " + hb_FNameExtSet( cProjectPath, ".hbc" )
ENDIF
ELSE
hb_SetEnv( "_HB_DYNSUFF" )
ENDIF
hb_SetEnv( "_HB_CONTRIB_SUBDIR", hb_FNameDir( hb_DirSepToOS( cProjectPath ) ) )
cCommand := s_cBinDir + "hbmk2" + ;
" -quiet -width=0 -autohbm-" + ;
" @" + StrTran( s_cHome + "hbpre", "\", "/" ) + ;
cOptionsPre + ;
" " + StrTran( cProjectPath, "\", "/" ) + ;
" @" + StrTran( s_cHome, "\", "/" ) + "hbpost" + ;
cOptionsLibDyn
IF PCount() >= 4
nErrorLevel := hb_processRun( cCommand,, @cStdOut, @cStdErr )
ELSE
nErrorLevel := mk_hb_processRun( cCommand )
ENDIF
RETURN nErrorLevel
STATIC FUNCTION mk_hb_processRun( cCommand, ... )
OutStd( cCommand + hb_eol() )
RETURN hb_processRun( cCommand, ... )
STATIC FUNCTION mk_hbd( cDir )
LOCAL cName
LOCAL cDocDir
LOCAL tmp
LOCAL aErrMsg
LOCAL aEntry
IF ! Empty( cDocDir := GetEnv( "HB_INSTALL_DOC" ) ) .AND. ! cDocDir == "no"
cName := DirGetName( cDir )
IF Empty( cName )
cName := "harbour"
ENDIF
aErrMsg := {}
aEntry := __hbdoc_LoadDir( cDir, cName, aErrMsg )
FOR EACH tmp IN aErrMsg
OutErr( hb_StrFormat( "! %1$s", tmp ) + hb_eol() )
NEXT
IF ! Empty( aEntry )
cName := hb_DirSepToOS( cDocDir ) + hb_ps() + cName + ".hbd"
IF __hbdoc_SaveHBD( cName, aEntry )
OutStd( "! Compiled documentation: " + cName + " <= " + cDir + hb_eol() )
RETURN .T.
ELSE
OutErr( hb_StrFormat( "! Error: Saving '%1$s'", cName ) + hb_eol() )
ENDIF
ENDIF
ENDIF
RETURN .F.
STATIC FUNCTION AScanL( aArray, cString )
RETURN AScan( aArray, {| tmp | Lower( tmp ) == cString } )
STATIC FUNCTION DirGetName( cDir )
LOCAL cName
cDir := hb_DirSepDel( cDir )
hb_FNameSplit( cDir,, @cName )
IF Empty( cName ) .OR. cName == "." .OR. cName == ".."
RETURN ""
ENDIF
RETURN cName
/* Convert indented list of line to tree / list of parent-child pairs */
STATIC PROCEDURE DeptLinesToDeptPairList( aPairList, cParent, aFlatTree )
LOCAL hFlatTreeElement
LOCAL hNode, hNewNode, tmp
LOCAL nLevel, nDepth
AddDeptPair( aPairList, "", cParent )
hNode := { "child" => {}, "name" => cParent, "parent" => }
nLevel := 0
FOR EACH hFlatTreeElement IN aFlatTree
/* Min() protects against jumping more than one level down in one step */
nDepth := Min( hFlatTreeElement[ "nDepth" ], nLevel + 1 )
hNewNode := { "child" => {}, "name" => hFlatTreeElement[ "cFileName_HBP" ], "cargo" => hFlatTreeElement }
IF nDepth > nLevel
hNode := ATail( hNode[ "child" ] )
ELSEIF nDepth < nLevel
FOR tmp := nDepth + 1 TO nLevel
hNode := hNode[ "parent" ]
NEXT
ENDIF
hNewNode[ "parent" ] := hNode
AAdd( hNode[ "child" ], hNewNode )
nLevel := nDepth
AddDeptPair( aPairList, hNewNode[ "parent" ][ "name" ], hNewNode[ "name" ] )
NEXT
RETURN
/* Add parent-child dependency to the list */
STATIC PROCEDURE AddDeptPair( aPairList, cParent, cChild )
IF AScan( aPairList, {| tmp | tmp[ 1 ] == cParent .AND. tmp[ 2 ] == cChild } ) == 0
AAdd( aPairList, { cParent, cChild } )
ENDIF
RETURN
/* Topological sort of the dependency graph */
STATIC FUNCTION TopoSort( aEdgeList )
LOCAL aList := {}
LOCAL hTopNodes := { => }
LOCAL n, m
LOCAL tmp
FOR EACH n IN aEdgeList
IF AScan( aEdgeList, {| tmp | tmp[ 2 ] == n[ 1 ] } ) == 0
hTopNodes[ n[ 1 ] ] := NIL
ENDIF
NEXT
DO WHILE ! Empty( hTopNodes )
n := hb_HKeyAt( hTopNodes, 1 )
hb_HDelAt( hTopNodes, 1 )
IF ! Empty( n )
AAdd( aList, n )
ENDIF
FOR EACH tmp IN aEdgeList
IF tmp[ 1 ] == n
m := tmp[ 2 ]
tmp[ 1 ] := tmp[ 2 ] := NIL /* set to invalid value. TOOPT: Delete this member from list */
IF AScan( aEdgeList, {| tmp | tmp[ 2 ] == m } ) == 0
hTopNodes[ m ] := NIL
ENDIF
ENDIF
NEXT
ENDDO
FOR EACH tmp IN aEdgeList
IF !( tmp[ 1 ] == NIL .AND. tmp[ 2 ] == NIL )
OutStd( hb_StrFormat( "! Warning: Circular reference in dependency tree (%1$s - %2$s)", tmp[ 1 ], tmp[ 2 ] ) + hb_eol() )
ENDIF
NEXT
RETURN aList
STATIC FUNCTION AddProject( hProjectList, cFileName )
LOCAL cDir
LOCAL cName
LOCAL cExt
IF ! Empty( cFileName )
cFileName := hb_DirSepToOS( AllTrim( cFileName ) )
hb_FNameSplit( cFileName, @cDir, @cName, @cExt )
DO CASE
CASE Empty( cName )
cName := DirGetName( cDir )
CASE Empty( cDir )
cDir := cName
ENDCASE
IF Empty( cExt )
cExt := ".hbp"
ENDIF
cFileName := hb_FNameMerge( cDir, cName, cExt )
IF hb_FileExists( s_cBase + s_cHome + cFileName )
cFileName := StrTran( cFileName, "\", "/" )
IF ! cFileName $ hProjectList
hProjectList[ cFileName ] := { => }
RETURN .T.
ENDIF
ENDIF
ENDIF
RETURN .F.
STATIC PROCEDURE LoadProjectListFromFile( hProjectList, cFileName )
LOCAL cItem
FOR EACH cItem IN hb_ATokens( StrTran( MemoRead( cFileName ), Chr( 13 ) ), Chr( 10 ) )
IF "#" $ cItem
cItem := Left( cItem, At( "#", cItem ) - 1 )
ENDIF
AddProject( hProjectList, cItem )
NEXT
RETURN
STATIC PROCEDURE LoadProjectListFromString( hProjectList, cString )
LOCAL cItem
FOR EACH cItem IN hb_ATokens( cString,, .T. )
AddProject( hProjectList, cItem )
NEXT
RETURN