#!/usr/bin/hbrun --hb:gtcgi /* * $Id$ */ /* * Harbour Project source code: * Package build orchestrator script * * Copyright 2010 Viktor Szakats (harbour.01 syenar.hu) * www - http://harbour-project.org * * 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., 675 Mass Ave, Cambridge, MA 02139, USA (or visit * their web site at http://www.gnu.org/). * */ /* TODO (long term, low priority): 1. normalize path to hbpre/hbpost to save on cmdline (after normalizer function has been moved to core) 2. Move lib output dir and workdirs inside contrib dirs. 3. Make home project detection more robust in stdalone mode. NOTE: - 'install' is ignored in stdalone mode. It would be needed to replicate the install dir defaulting logic found in global.mk to implement it. */ #pragma warninglevel=3 #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 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 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__HBRUN ) s_cBinDir := hbrun_DirBase() #else s_cBinDir := hb_DirBase() #endif /* Load list of projects */ hProjectList := { => } hb_HKeepOrder( hProjectList, .T. ) LoadProjectListFromFile( hProjectList, s_cHome + "hbplist" ) LoadProjectListFromString( hProjectList, GetEnv( "HB_BUILD_ADDONS" ) ) /* Build */ IF Empty( GetEnv( "HB_HOST_BIN_DIR" ) ) Standalone( hb_AParams(), hProjectList ) ELSE GNUMake( hb_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= */ PROCEDURE Standalone( aParams, hProjectList ) LOCAL hProjectReqList LOCAL cOptionsUser LOCAL nAction LOCAL tmp LOCAL tmp1 LOCAL lCustom /* Processing cmdline 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, "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 /* Strip install action */ DO CASE CASE nAction == _ACT_INC_REBUILD_INST ; nAction := _ACT_INC_REBUILD CASE nAction == _ACT_INC_INST ; 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" ) 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 current directory) */ hProjectReqList := { => } hb_HKeepOrder( hProjectReqList, .T. ) 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( PathSepToSelf( 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$s project(s)", hActions[ nAction ], hb_ntos( 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 ) 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 */ 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 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 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 := { => } hb_HKeepOrder( hProjectReqList, .T. ) 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( PathSepToSelf( cProject ), PathSepToSelf( tmp:__enumKey() ) ) .OR. ; hb_FileMatch( PathSepToSelf( cProject ), hb_DirSepDel( hb_FNameDir( PathSepToSelf( 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" ) ) /* Override hbmk2 autodetection. 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...", hActions[ nAction ] ) + hb_eol() ) build_projects( nAction, hProjectList, hProjectReqList, "" ) OutStd( hb_StrFormat( "! Finished package %1$s...", hActions[ nAction ] ) + hb_eol() ) RETURN STATIC PROCEDURE build_projects( nAction, hProjectList, hProjectReqList, cOptionsUser ) LOCAL aPairList LOCAL aSortedList LOCAL cOptions LOCAL lInstall 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$s projects...", hb_ntos( 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 on our list and featuring an .hbp file */ FOR EACH cProject IN aSortedList IF !( cProject $ hProjectList ) IF hb_FileExists( s_cBase + s_cHome + cProject ) AddProject( hProjectList, cProject ) call_hbmk2_hbinfo( s_cBase + s_cHome + cProject, hProjectList[ cProject ] ) hProjectList[ cProject ][ "lFromContainer" ] := NIL ENDIF 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" IF nAction == _ACT_INC_CLEAN cOptions += " -clean" ELSEIF nAction == _ACT_INC_REBUILD .OR. ; nAction == _ACT_INC_REBUILD_INST cOptions += " -rebuildall" ENDIF lInstall := nAction == _ACT_INC_INST .OR. ; nAction == _ACT_INC_REBUILD_INST hb_setenv( "_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( PathSepToSelf( 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 FUNCTION call_hbmk2_hbinfo( cProjectPath, hProject ) LOCAL cStdOut LOCAL cDir LOCAL cName LOCAL tmp LOCAL nErrorLevel hProject[ "cType" ] := "" hProject[ "aDept" ] := {} hProject[ "lChecked" ] := NIL IF ( nErrorLevel := call_hbmk2( cProjectPath, " --hbinfo", NIL,, @cStdOut ) ) == 0 hProject[ "cType" ] := hbmk2_hbinfo_getitem( cStdOut, "targettype" ) hProject[ "cOutputName" ] := hbmk2_hbinfo_getitem( cStdOut, "outputname" ) hProject[ "cDynSuffix" ] := hbmk2_hbinfo_getitem( cStdOut, "dynsuffix" ) hProject[ "cPlatform" ] := hbmk2_hbinfo_getitem( cStdOut, "platform" ) FOR EACH tmp IN hb_ATokens( hbmk2_hbinfo_getitem( cStdOut, "hbctree", .T. ), 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( PathSepToSelf( LTrim( tmp ) ), ".hbp" ) ) ), "\", "/" ) } ) ENDIF NEXT ENDIF RETURN nErrorLevel STATIC FUNCTION hbmk2_hbinfo_getitem( cString, cItem, lAll ) LOCAL cRetVal := "" LOCAL nPos := 1 LOCAL tmp DO WHILE ( tmp := hb_At( cItem + "{{", cString, nPos ) ) > 0 nPos := tmp + Len( cItem + "{{" ) IF ( tmp := hb_At( "}}", cString, nPos ) ) > 0 tmp := StrTran( SubStr( cString, nPos, tmp - nPos ), Chr( 13 ) ) IF lAll != NIL .AND. lAll cRetVal += tmp ELSE /* Find the last occurrence, which is the root project */ cRetVal := tmp ENDIF ENDIF ENDDO RETURN cRetVal 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) */ hb_setenv( "_HB_BUILD_LIBDYN", "yes" ) IF hb_FileExists( hb_FNameExtSet( cProjectPath, ".hbc" ) ) cOptionsLibDyn += " " + hb_FNameExtSet( cProjectPath, ".hbc" ) ENDIF ELSE hb_setenv( "_HB_DYNSUFF" ) hb_setenv( "_HB_BUILD_LIBDYN" ) ENDIF cCommand := s_cBinDir + "hbmk2" +; " -quiet -width=0" +; " @" + 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 IF nErrorLevel != 0 OutStd( hb_StrFormat( "! '%1$s' returned status: %2$s", cProjectPath, hb_ntos( nErrorLevel ) ) + hb_eol() ) 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 := PathSepToSelf( 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 STATIC FUNCTION PathSepToSelf( cFileName ) RETURN StrTran( cFileName, iif( hb_ps() == "\", "/", "\" ), hb_ps() ) /* 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" => NIL } 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 hb_HKeepOrder( hTopNodes, .T. ) 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 PROCEDURE AddProject( hProjectList, cFileName ) LOCAL cDir LOCAL cName LOCAL cExt IF ! Empty( cFileName ) cFileName := PathSepToSelf( AllTrim( cFileName ) ) hb_FNameSplit( cFileName, @cDir, @cName, @cExt ) IF ! Empty( cName ) .AND. Empty( cDir ) cDir := cName ENDIF IF Empty( cName ) cName := DirGetName( cDir ) ENDIF IF Empty( cExt ) cExt := ".hbp" ENDIF cFileName := hb_FNameMerge( cDir, cName, cExt ) hProjectList[ StrTran( cFileName, "\", "/" ) ] := { => } ENDIF RETURN 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 PROCEDURE LoadProjectListFromString( hProjectList, cString ) LOCAL cItem FOR EACH cItem IN hb_ATokens( cString,, .T. ) AddProject( hProjectList, cItem ) NEXT RETURN