N°2982: theme precompilation fix work even when coming from /extensions or /datamodels/X.x

This commit is contained in:
odain
2021-02-04 14:04:43 +01:00
parent 2e2b7f96f2
commit 54f8ef924f
2 changed files with 209 additions and 14 deletions

View File

@@ -51,6 +51,8 @@ class DOMFormatException extends Exception
*/
class MFCompiler
{
const DATA_PRECOMPILED_FOLDER = 'data' . DIRECTORY_SEPARATOR . 'precompiled_styles' . DIRECTORY_SEPARATOR;
/** @var \ModelFactory */
protected $oFactory;
@@ -2862,6 +2864,11 @@ EOF;
$aThemes[$aDefaultThemeInfo['name']] = $aDefaultThemeInfo['parameters'];
}
$sPostCompilationPrecompiledThemeFolder = APPROOT . self::DATA_PRECOMPILED_FOLDER;
if (! is_dir($sPostCompilationPrecompiledThemeFolder)){
mkdir($sPostCompilationPrecompiledThemeFolder);
}
// Compile themes
$fStart = microtime(true);
foreach($aThemes as $sThemeId => $aThemeParameters)
@@ -2871,30 +2878,87 @@ EOF;
{
SetupUtils::builddir($sThemeDir);
}
// Check if a precompiled version of the theme is supplied
$sPrecompiledFile = $sTempTargetDir.$aThemeParameters['precompiled_stylesheet'];
if (file_exists($sPrecompiledFile) && !is_dir($sPrecompiledFile))
{
copy($sPrecompiledFile, $sThemeDir.'/main.css');
$sPostCompilationLatestPrecompiledFile = $sPostCompilationPrecompiledThemeFolder . $sThemeId . ".css";
$sPrecompiledFileToUse = $this->UseLatestPrecompiledFile($sTempTargetDir, $aThemeParameters['precompiled_stylesheet'], $sPostCompilationLatestPrecompiledFile, $sThemeId);
if ($sPrecompiledFileToUse != null){
copy($sPrecompiledFileToUse, $sThemeDir.'/main.css');
// Make sure that the copy of the precompiled file is older than any other files to force a validation of the signature
touch($sThemeDir.'/main.css', 1577836800 /* 2020-01-01 00:00:00 */);
}
else if ($sPrecompiledFile != '')
{
$this->Log("Precompiled file not found: '$sPrecompiledFile'");
}
$bHasCompiled = ThemeHandler::CompileTheme($sThemeId, true, $this->sCompilationTimeStamp, $aThemeParameters, $aImportsPaths, $sTempTargetDir);
$sInitialPrecompiledFilePath = APPROOT.'datamodels/2.x/'.$aThemeParameters['precompiled_stylesheet'];
if ($bHasCompiled && is_file($sInitialPrecompiledFilePath))
if ($bHasCompiled)
{
SetupLog::Info("Replacing precompiled file $sInitialPrecompiledFilePath for theme $sThemeId for next setup.");
copy($sThemeDir.'/main.css', $sInitialPrecompiledFilePath);
SetupLog::Info("Replacing theme '$sThemeId' precompiled file in file $sPostCompilationLatestPrecompiledFile for next setup.");
copy($sThemeDir.'/main.css', $sPostCompilationLatestPrecompiledFile);
}
}
$this->Log(sprintf('Themes compilation took: %.3f ms for %d themes.', (microtime(true) - $fStart)*1000.0, count($aThemes)));
}
/**
* Choose between precompiled files declared in datamodel XMLs or latest precompiled files generated after latest setup.
* @param $sTempTargetDir
* @param $sPrecompiledFileUri
* @param $sPostCompilationLatestPrecompiledFile
* @param $sThemeId
*
* @return string : file path of latest precompiled file to use for setup
*/
public function UseLatestPrecompiledFile($sTempTargetDir, $sPrecompiledFileUri, $sPostCompilationLatestPrecompiledFile, $sThemeId) : ?string {
$bDataXmlPrecompiledFileExists = false;
clearstatcache();
if (!empty($sPrecompiledFileUri)){
$sDataXmlProvidedPrecompiledFile = $sTempTargetDir . DIRECTORY_SEPARATOR . $sPrecompiledFileUri;
$bDataXmlPrecompiledFileExists = file_exists($sDataXmlProvidedPrecompiledFile) ;
if (!$bDataXmlPrecompiledFileExists){
SetupLog::Warning("Missing defined theme '$sThemeId' precompiled file configured with: '$sPrecompiledFileUri'");
} else {
$sSourceDir = APPROOT . utils::GetConfig()->Get('source_dir');
$aDirToCheck = [
$sSourceDir,
APPROOT . DIRECTORY_SEPARATOR . 'extensions/'
];
$iDataXmlFileLastModified = 0;
foreach ($aDirToCheck as $sDir){
$sCurrentFile = $sDir . DIRECTORY_SEPARATOR . $sPrecompiledFileUri;
if (is_file($sCurrentFile)){
$iDataXmlFileLastModified = max($iDataXmlFileLastModified, @filemtime($sCurrentFile));
}
}
if ($iDataXmlFileLastModified == 0){
SetupLog::Warning("Missing defined theme '$sThemeId' precompiled file in datamodels/X.x or extensions directory configured with: '$sPrecompiledFileUri'. That should not happen!");
$bDataXmlPrecompiledFileExists = false;
}
}
}
$this->Log(sprintf('Themes compilation took: %.3f ms for %d themes.', (microtime(true) - $fStart)*1000.0, count($aThemes)));
$bPostCompilationPrecompiledFileExists = file_exists($sPostCompilationLatestPrecompiledFile);
if (!$bDataXmlPrecompiledFileExists && !$bPostCompilationPrecompiledFileExists){
return null;
}
if (!$bDataXmlPrecompiledFileExists){
$sPrecompiledFileToUse = $sPostCompilationLatestPrecompiledFile;
} else if (!$bPostCompilationPrecompiledFileExists){
$sPrecompiledFileToUse = $sDataXmlProvidedPrecompiledFile;
} else{
$iPostCompilationFileLastModified = @filemtime($sPostCompilationLatestPrecompiledFile);
SetupLog::Debug("Theme '$sThemeId' check mtime between data XML file " . $iDataXmlFileLastModified . " and latest postcompilation file: " . $iPostCompilationFileLastModified);
$sPrecompiledFileToUse = $iDataXmlFileLastModified > $iPostCompilationFileLastModified ? $sDataXmlProvidedPrecompiledFile : $sPostCompilationLatestPrecompiledFile;
}
SetupLog::Info("For theme '$sThemeId' precompiled file used: '$sPrecompiledFileToUse'");
return $sPrecompiledFileToUse;
}
/**

View File

@@ -0,0 +1,131 @@
<?php
use Combodo\iTop\Test\UnitTest\ItopTestCase;
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
* @covers utils
*/
class MFCompilerTest extends ItopTestCase {
/** @var array */
private static $aFoldersToCleanup;
/** @var array */
private static $aRessources;
/** @var \MFCompiler */
private $oMFCompiler;
public function setUp()
{
@include_once '/home/combodo/workspace/iTop/approot.inc.php';
parent::setUp();
require_once(APPROOT.'setup/compiler.class.inc.php');
$this->oMFCompiler = new MFCompiler($this->createMock(\ModelFactory::class), '');
}
public static function Init(){
if (!is_null(self::$aFoldersToCleanup)){
return;
}
clearstatcache();
$sPrefix = 'scsstest_';
$sAppRootForProvider = dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR;
$sTempTargetDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'UseLatestPrecompiledFileProvider';
$sExtensionTargetDir = $sAppRootForProvider . 'extensions/UseLatestPrecompiledFileProvider';
$sSourceDir = $sAppRootForProvider . 'datamodels' . DIRECTORY_SEPARATOR . '2.x';
$sDatamodel2xTargetDir = $sSourceDir . DIRECTORY_SEPARATOR . '/UseLatestPrecompiledFileProvider';
mkdir($sTempTargetDir);
mkdir($sExtensionTargetDir);
mkdir($sDatamodel2xTargetDir);
self::$aFoldersToCleanup = [ $sTempTargetDir, $sExtensionTargetDir, $sDatamodel2xTargetDir ];
self::$aRessources['sPostCompilation1'] = tempnam($sTempTargetDir, $sPrefix);
sleep(1);
//datamodel XML file in extension folder
self::$aRessources['sPrecompiledInExtensionFile1'] = tempnam($sExtensionTargetDir, $sPrefix);
self::$aRessources['sPrecompiledInExtensionFileUri1'] = "UseLatestPrecompiledFileProvider" . DIRECTORY_SEPARATOR . basename(self::$aRessources['sPrecompiledInExtensionFile1']);
//datamodel XML file in source dir /datamodels/2.x folder
self::$aRessources['sPrecompiledInDataModelXXFile1'] = tempnam($sDatamodel2xTargetDir, $sPrefix);
self::$aRessources['sPrecompiledInDataModelXXFileUri1'] = "UseLatestPrecompiledFileProvider" . DIRECTORY_SEPARATOR . basename(self::$aRessources['sPrecompiledInDataModelXXFile1']);
sleep(1);
//generate ressources from a previous setup: called postcompiled
self::$aRessources['sPostCompilation2'] = tempnam($sTempTargetDir, $sPrefix);
sleep(1);
//simulate copy of /data/models.2.x or extensions ressources during setup in a temp directory
self::$aRessources['sCopiedExtensionFile1'] = $sTempTargetDir . DIRECTORY_SEPARATOR . basename(self::$aRessources['sPrecompiledInExtensionFile1']);
copy(self::$aRessources['sPrecompiledInExtensionFile1'], self::$aRessources['sCopiedExtensionFile1']);
self::$aRessources['sCopiedDataModelXXFile1'] = $sTempTargetDir . DIRECTORY_SEPARATOR . basename(self::$aRessources['sPrecompiledInDataModelXXFile1']);
copy(self::$aRessources['sPrecompiledInDataModelXXFile1'], self::$aRessources['sCopiedDataModelXXFile1']);
self::$aRessources['sMissingFile'] = tempnam($sTempTargetDir, $sPrefix);
unlink(self::$aRessources['sMissingFile']);
/*foreach (self::$aRessources as $sKey => $sRessource){
if (is_file($sRessource)) {
var_dump("$sKey $sRessource:" . filemtime($sRessource));
}
}*/
}
public static function tearDownAfterClass()
{
if (is_null(self::$aFoldersToCleanup)){
return;
}
foreach (self::$aFoldersToCleanup as $sFolder){
if (is_dir($sFolder)){
foreach (glob("$sFolder/**") as $sFile){
unlink($sFile);
}
rmdir($sFolder);
}
}
}
/**
* @dataProvider UseLatestPrecompiledFileProvider
*/
public function testUseLatestPrecompiledFile(string $sTempTargetDir, string $sPrecompiledFileUri, string $sPostCompilationLatestPrecompiledFile, string $sThemeDir, $sExpectedReturn){
$sRes = $this->oMFCompiler->UseLatestPrecompiledFile($sTempTargetDir, $sPrecompiledFileUri, $sPostCompilationLatestPrecompiledFile, $sThemeDir);
$this->assertEquals($sExpectedReturn, $sRes);
}
public function UseLatestPrecompiledFileProvider(){
self::init();
return [
'no precompiled file configured in precompiled_stylesheet XM section' => $this->BuildProviderUseCaseArray('', self::$aRessources['sPostCompilation1'], self::$aRessources['sPostCompilation1']),
'missing precompiled file in precompiled_stylesheet section' => $this->BuildProviderUseCaseArray(self::$aRessources['sMissingFile'], self::$aRessources['sPostCompilation1'], self::$aRessources['sPostCompilation1'] ),
'no precompiled file generated in previous setup in /data/precompiled_styles' => $this->BuildProviderUseCaseArray(self::$aRessources['sPrecompiledInExtensionFileUri1'], self::$aRessources['sMissingFile'], self::$aRessources['sCopiedExtensionFile1'] ),
'(extensions) XML precompiled_stylesheet file older than last post setup generated file in /data/precompiled_styles' => $this->BuildProviderUseCaseArray(self::$aRessources['sPrecompiledInExtensionFileUri1'], self::$aRessources['sPostCompilation2'], self::$aRessources['sPostCompilation2'] ),
'last post setup generated file in /data/precompiled_styles older than (extensions) XML precompiled_stylesheet file' => $this->BuildProviderUseCaseArray(self::$aRessources['sPrecompiledInExtensionFileUri1'], self::$aRessources['sPostCompilation1'], self::$aRessources['sCopiedExtensionFile1'] ),
'(datamodels/N.x) XML precompiled_stylesheet file older than last post setup generated file in /data/precompiled_styles' => $this->BuildProviderUseCaseArray(self::$aRessources['sPrecompiledInDataModelXXFileUri1'], self::$aRessources['sPostCompilation2'], self::$aRessources['sPostCompilation2'] ),
'(datamodels/N.x) last post setup generated file in /data/precompiled_styles older than (extensions) XML precompiled_stylesheet file' => $this->BuildProviderUseCaseArray(self::$aRessources['sPrecompiledInDataModelXXFileUri1'], self::$aRessources['sPostCompilation1'], self::$aRessources['sCopiedDataModelXXFile1'] ),
];
}
private function BuildProviderUseCaseArray(string $sPrecompiledFileUri, string $sPostCompilationLatestPrecompiledFile, $sExpectedReturn) : array{
return [
"sTempTargetDir" => sys_get_temp_dir(),
"sPrecompiledFileUri" => $sPrecompiledFileUri,
"sPostCompilationLatestPrecompiledFile" => $sPostCompilationLatestPrecompiledFile,
"sThemeDir" => "test",
"sExpectedReturn" => $sExpectedReturn
];
}
}