From 3d72060bf570e523b08576de0954222520b8974b Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 29 Aug 2017 08:08:24 +0000 Subject: [PATCH] N.890.1 and N.890.2: build a new run time environment into a separate "build" directory, then commit it by the mean of quick and bullet proof file copies/moves. Not yet used in the setup. SVN:trunk[4881] --- core/metamodel.class.php | 6 +- setup/applicationinstaller.class.inc.php | 2 +- setup/compiler.class.inc.php | 13 +- setup/runtimeenv.class.inc.php | 172 +++++++++++++++++++++-- 4 files changed, 174 insertions(+), 19 deletions(-) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index fa1642235..10e4218e8 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -3267,8 +3267,8 @@ abstract class MetaModel { if (!method_exists($sClass, $actionHandler)) { - $aErrors[$sClass][] = "Unknown function '$sActionHandler' in transition [$sStateCode/$sStimulusCode] for state attribute '$sStateAttCode'"; - $aSugFix[$sClass][] = "Specify a function which prototype is in the form [public function $sActionHandler(\$sStimulusCode){return true;}]"; + $aErrors[$sClass][] = "Unknown function '$actionHandler' in transition [$sStateCode/$sStimulusCode] for state attribute '$sStateAttCode'"; + $aSugFix[$sClass][] = "Specify a function which prototype is in the form [public function $actionHandler(\$sStimulusCode){return true;}]"; } } else // if(is_array($actionHandler)) @@ -4597,7 +4597,7 @@ abstract class MetaModel protected static $m_aExtensionClasses = array(); - protected static function IncludeModule($sToInclude, $sModuleType = null) + public static function IncludeModule($sToInclude, $sModuleType = null) { $sFirstChar = substr($sToInclude, 0, 1); $sSecondChar = substr($sToInclude, 1, 1); diff --git a/setup/applicationinstaller.class.inc.php b/setup/applicationinstaller.class.inc.php index 9342c9b03..3590b3285 100644 --- a/setup/applicationinstaller.class.inc.php +++ b/setup/applicationinstaller.class.inc.php @@ -1025,7 +1025,7 @@ class ApplicationInstaller // Record which modules are installed... $oProductionEnv = new RunTimeEnvironment($sTargetEnvironment); $oProductionEnv->InitDataModel($oConfig, true); // load data model and connect to the database - if (!$oProductionEnv->RecordInstallation($oConfig, $sDataModelVersion, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sModulesDir)) + if (!$oProductionEnv->RecordInstallation($oConfig, $sDataModelVersion, $aSelectedModuleCodes, $aSelectedExtensionCodes)) { throw new Exception("Failed to record the installation information"); } diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index e3df34c70..664a37345 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -93,13 +93,14 @@ class MFCompiler * @param string $sTargetDir The target directory where to put the resulting files * @param Page $oP For some output... * @param bool $bUseSymbolicLinks + * @param bool $bSkipTempDir * @throws Exception * @return void */ - public function Compile($sTargetDir, $oP = null, $bUseSymbolicLinks = false) + public function Compile($sTargetDir, $oP = null, $bUseSymbolicLinks = false, $bSkipTempDir = false) { $sFinalTargetDir = $sTargetDir; - if ($bUseSymbolicLinks) + if ($bUseSymbolicLinks || $bSkipTempDir) { // Skip the creation of a temporary dictionary, not compatible with symbolic links $sTempTargetDir = $sFinalTargetDir; @@ -480,16 +481,16 @@ EOF; { $this->Log("Compilation of module $sModuleName in version $sModuleVersion produced not code at all. No file written."); } - + // files to include (PHP datamodels) foreach($oModule->GetFilesToInclude('business') as $sRelFileName) { - $aDataModelFiles[] = "MetaModel::IncludeModule('".basename($sFinalTargetDir).'/'.$sRelativeDir.'/'.$sRelFileName."');"; + $aDataModelFiles[] = "MetaModel::IncludeModule(MODULESROOT.'/$sRelativeDir/$sRelFileName');"; } // files to include (PHP webservices providers) foreach($oModule->GetFilesToInclude('webservices') as $sRelFileName) { - $aWebservicesFiles[] = "MetaModel::IncludeModule('".basename($sFinalTargetDir).'/'.$sRelativeDir.'/'.$sRelFileName."');"; + $aWebservicesFiles[] = "MetaModel::IncludeModule(MODULESROOT.'/$sRelativeDir/$sRelFileName');"; } } // foreach module @@ -577,7 +578,7 @@ EOF; EOF ; - $sPHPFileContent .= "\nMetaModel::IncludeModule('".basename($sFinalTargetDir)."/core/main.php');\n"; + $sPHPFileContent .= "\nMetaModel::IncludeModule(MODULESROOT.'/core/main.php');\n"; $sPHPFileContent .= implode("\n", $aDataModelFiles); $sPHPFileContent .= implode("\n", $aWebservicesFiles); $sPHPFileContent .= "\nfunction GetModulesInfo()\n{\nreturn ".var_export($aModulesInfo, true).";\n}\n"; diff --git a/setup/runtimeenv.class.inc.php b/setup/runtimeenv.class.inc.php index abbfbd356..5d2dcd021 100644 --- a/setup/runtimeenv.class.inc.php +++ b/setup/runtimeenv.class.inc.php @@ -40,20 +40,55 @@ define ('DATAMODEL_MODULE', 'datamodel'); // Convention to store the version of class RunTimeEnvironment { + /** + * The name of the environment that the caller wants to build + * @var string sFinalEnv + */ + protected $sFinalEnv; + + /** + * Environment into which the build will be performed + * @var string sTargetEnv + */ protected $sTargetEnv; - + /** * Extensions map of the source environment * @var iTopExtensionsMap */ protected $oExtensionsMap; - - public function __construct($sEnvironment = 'production') + + /** + * Toolset for building a run-time environment + * + * @param string $sEnvironment (e.g. 'test') + * @param bool $bAutoCommit (make the target environment directly, or build a temporary one) + */ + public function __construct($sEnvironment = 'production', $bAutoCommit = true) { - $this->sTargetEnv = $sEnvironment; + $this->sFinalEnv = $sEnvironment; + if ($bAutoCommit) + { + // Build directly onto the requested environment + $this->sTargetEnv = $sEnvironment; + } + else + { + // Build into a temporary target + $this->sTargetEnv = $sEnvironment.'-build'; + } $this->oExtensionsMap = null; } + /** + * Return the full path to the compiled code (do not use after commit) + * @return string + */ + public function GetBuildDir() + { + return APPROOT.'env-'.$this->sTargetEnv; + } + /** * Callback function for logging the queries run by the setup. * According to the documentation the function must be defined before passing it to call_user_func... @@ -429,7 +464,7 @@ class RunTimeEnvironment } } while($bModuleAdded); - + $sDeltaFile = APPROOT.'data/'.$this->sTargetEnv.'.delta.xml'; if (file_exists($sDeltaFile)) { @@ -495,11 +530,12 @@ class RunTimeEnvironment // No delta was loaded, let's save the datamodel now $oFactory->SaveToFile(APPROOT.'data/datamodel-'.$this->sTargetEnv.'.xml'); } - + $sTargetDir = APPROOT.'env-'.$this->sTargetEnv; self::MakeDirSafe($sTargetDir); + $bSkipTempDir = ($this->sFinalEnv != $this->sTargetEnv); // No need for a temporary directory if sTargetEnv is already a temporary directory $oMFCompiler = new MFCompiler($oFactory); - $oMFCompiler->Compile($sTargetDir, null, $bUseSymLinks); + $oMFCompiler->Compile($sTargetDir, null, $bUseSymLinks, $bSkipTempDir); $sCacheDir = APPROOT.'data/cache-'.$this->sTargetEnv; SetupUtils::builddir($sCacheDir); @@ -653,7 +689,7 @@ class RunTimeEnvironment $oConfig->Set('access_mode', $iPrevAccessMode); } - public function RecordInstallation(Config $oConfig, $sDataModelVersion, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sModulesRelativePath, $sShortComment = null) + public function RecordInstallation(Config $oConfig, $sDataModelVersion, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sShortComment = null) { // Have it work fine even if the DB has been set in read-only mode for the users $iPrevAccessMode = $oConfig->Get('access_mode'); @@ -698,7 +734,7 @@ class RunTimeEnvironment // Record installed modules and extensions // $aAvailableExtensions = array(); - $aAvailableModules = $this->AnalyzeInstallation($oConfig, APPROOT.$sModulesRelativePath); + $aAvailableModules = $this->AnalyzeInstallation($oConfig, $this->GetBuildDir()); foreach($aSelectedModuleCodes as $sModuleId) { $aModuleData = $aAvailableModules[$sModuleId]; @@ -870,4 +906,122 @@ class RunTimeEnvironment } return $oLatestDM->Get('version'); } + + public function Commit() + { + if ($this->sFinalEnv != $this->sTargetEnv) + { + $this->CommitFile( + APPROOT.'data/'.$this->sTargetEnv.'.delta.xml', + APPROOT.'data/'.$this->sFinalEnv.'.delta.xml', + false + ); + $this->CommitFile( + APPROOT.'data/datamodel-'.$this->sTargetEnv.'.xml', + APPROOT.'data/datamodel-'.$this->sFinalEnv.'.xml' + ); + $this->CommitFile( + APPROOT.'data/datamodel-'.$this->sTargetEnv.'-with-delta.xml', + APPROOT.'data/datamodel-'.$this->sFinalEnv.'-with-delta.xml', + false + ); + $this->CommitDir( + APPROOT.'data/'.$this->sTargetEnv.'-modules/', + APPROOT.'data/'.$this->sFinalEnv.'-modules/', + false + ); + $this->CommitDir( + APPROOT.'data/cache-'.$this->sTargetEnv, + APPROOT.'data/cache-'.$this->sFinalEnv, + false + ); + $this->CommitDir( + APPROOT.'env-'.$this->sTargetEnv, + APPROOT.'env-'.$this->sFinalEnv + ); + + $sTargetConfig = APPCONF.$this->sTargetEnv.'/config-itop.php'; + $sFinalConfig = APPCONF.$this->sFinalEnv.'/config-itop.php'; + @chmod($sFinalConfig, 0770); // In case it exists: RWX for owner and group, nothing for others + $this->CommitFile($sTargetConfig, $sFinalConfig); + @chmod($sFinalConfig, 0440); // Read-only for owner and group, nothing for others + } + } + + /** + * Overwrite or create the destination file + * + * @param $sSource + * @param $sDest + * @param bool $bSourceMustExist + * @throws Exception + */ + protected function CommitFile($sSource, $sDest, $bSourceMustExist = true) + { + if (file_exists($sSource)) + { + SetupUtils::builddir(dirname($sDest)); + if (file_exists($sDest)) + { + $bRes = @unlink($sDest); + if (!$bRes) + { + throw new Exception('Commit - Failed to cleanup destination file: '.$sDest); + } + } + rename($sSource, $sDest); + } + else + { + // The file does not exist + if ($bSourceMustExist) + { + throw new Exception('Commit - Missing file: '.$sSource); + } + else + { + // Align the destination with the source... make sure there is NO file + if (file_exists($sDest)) + { + $bRes = @unlink($sDest); + if (!$bRes) + { + throw new Exception('Commit - Failed to cleanup destination file: '.$sDest); + } + } + } + } + } + + /** + * Overwrite or create the destination directory + * + * @param $sSource + * @param $sDest + * @param bool $bSourceMustExist + * @throws Exception + */ + protected function CommitDir($sSource, $sDest, $bSourceMustExist = true) + { + if (file_exists($sSource)) + { + SetupUtils::movedir($sSource, $sDest); + } + else + { + // The file does not exist + if ($bSourceMustExist) + { + throw new Exception('Commit - Missing directory: '.$sSource); + } + else + { + // Align the destination with the source... make sure there is NO file + if (file_exists($sDest)) + { + SetupUtils::rrmdir($sDest); + } + } + } + } } // End of class