New symlink flag set by the compiler, new compile with symlinks checkbox in the setup (#190)

The flag consists of the new `data/.compilation-symlinks` file, which is included in the code base (but not available in the Combodo packages).

It will be used and generated if and only if : 
* we are on a dev env (`\utils::IsDevelopmentEnvironment`)
* the `symlink` PHP function exists

The flag is generated low level in the compiler (\MFCompiler::DoCompile)

In the setup, if the flag is present and all conditions are met then a new option will be displayed in the "Miscellaneous Parameters" wizard step. When unchecking/checking the flag will be updated accordingly by an ajax query.

All other compiler consumers (Designer / Hub connectors, Core Update, scripts using `RunTimeEnvironment` class) will : 
* if they provide a value for the symlink option, it will be used
* otherwise the flag will be used instead, if conditions are met
This commit is contained in:
Pierre Goiffon
2021-06-14 16:44:34 +02:00
committed by GitHub
parent d8e2a1cc7c
commit c417a454d6
8 changed files with 157 additions and 74 deletions

2
.gitignore vendored
View File

@@ -32,6 +32,8 @@ test/vendor/*
!/data/.htaccess
!/data/index.php
!/data/web.config
!/data/exclude.txt
!/data/.compilation-symlinks
# iTop extensions
/extensions/**

View File

5
data/exclude.txt Normal file
View File

@@ -0,0 +1,5 @@
#
# The following source files are not re-distributed with the "build" of the application
# since they are used solely for debugging. The minified version is normally used instead.
#
.compilation-symlinks

View File

@@ -164,21 +164,25 @@ try
/** @var WizardStep $oStep */
$oStep = new $sClass($oDummyController, $sState);
$sConfigFile = utils::GetConfigFilePath();
if (file_exists($sConfigFile) && !is_writable($sConfigFile) && $oStep->RequiresWritableConfig())
{
if (file_exists($sConfigFile) && !is_writable($sConfigFile) && $oStep->RequiresWritableConfig()) {
$sRelativePath = utils::GetConfigFilePathRelative();
$oPage->error("<b>Error:</b> the configuration file '".$sRelativePath."' already exists and cannot be overwritten.");
$oPage->p("The wizard cannot modify the configuration file for you. If you want to upgrade ".ITOP_APPLICATION.", make sure that the file '<b>".$sRelativePath."</b>' can be modified by the web server.");
$oPage->output();
}
else
{
} else {
$oStep->AsyncAction($oPage, $sActionCode, $aParams);
}
}
$oPage->output();
break;
case 'toggle_use_symbolic_links':
$sUseSymbolicLinks = Utils::ReadParam('bUseSymbolicLinks', false);
$bUseSymbolicLinks = ($sUseSymbolicLinks === 'true');
MFCompiler::SetUseSymbolicLinksFlag($bUseSymbolicLinks);
echo "toggle useSymbolicLInks file : $bUseSymbolicLinks";
break;
default:
throw(new Exception("Error unsupported operation '$sOperation'"));
}

View File

@@ -274,17 +274,14 @@ class ApplicationInstaller
$sExtensionDir = $this->oParams->Get('extensions_dir', 'extensions');
$sTargetEnvironment = $this->GetTargetEnv();
$sTargetDir = $this->GetTargetDir();
$bUseSymbolicLinks = false;
$aMiscOptions = $this->oParams->Get('options', array());
if (isset($aMiscOptions['symlinks']) && $aMiscOptions['symlinks'])
{
if (function_exists('symlink'))
{
$bUseSymbolicLinks = null;
if ((isset($aMiscOptions['symlinks']) && $aMiscOptions['symlinks'])) {
if (function_exists('symlink')) {
$bUseSymbolicLinks = true;
SetupLog::Info("Using symbolic links instead of copying data model files (for developers only!)");
}
else
{
} else {
SetupLog::Info("Symbolic links (function symlinks) does not seem to be supported on this platform (OS/PHP version).");
}
}
@@ -504,8 +501,7 @@ class ApplicationInstaller
{
$oBackup = new SetupDBBackup($oConfig);
$sTargetFile = $oBackup->MakeName($sBackupFileFormat);
if (!empty($sMySQLBinDir))
{
if (!empty($sMySQLBinDir)) {
$oBackup->SetMySQLBinDir($sMySQLBinDir);
}
@@ -514,7 +510,7 @@ class ApplicationInstaller
}
protected static function DoCompile($aSelectedModules, $sSourceDir, $sExtensionDir, $sTargetDir, $sEnvironment, $bUseSymbolicLinks = false)
protected static function DoCompile($aSelectedModules, $sSourceDir, $sExtensionDir, $sTargetDir, $sEnvironment, $bUseSymbolicLinks = null)
{
SetupLog::Info("Compiling data model.");
@@ -522,8 +518,7 @@ class ApplicationInstaller
require_once(APPROOT.'setup/modelfactory.class.inc.php');
require_once(APPROOT.'setup/compiler.class.inc.php');
if (empty($sSourceDir) || empty($sTargetDir))
{
if (empty($sSourceDir) || empty($sTargetDir)) {
throw new Exception("missing parameter source_dir and/or target_dir");
}

View File

@@ -53,6 +53,14 @@ class MFCompiler
{
const DATA_PRECOMPILED_FOLDER = 'data'.DIRECTORY_SEPARATOR.'precompiled_styles'.DIRECTORY_SEPARATOR;
/**
* Path to the "use symlinks" file
* If this file is present, then we will compile to symlink !
*
* @var string
*/
public const USE_SYMBOLIC_LINKS_FILE_PATH = APPROOT.'data/.compilation-symlinks';
/** @var \ThemeHandlerService */
protected static $oThemeHandlerService;
@@ -93,8 +101,7 @@ class MFCompiler
protected function DumpLog($oPage)
{
foreach ($this->aLog as $sText)
{
foreach ($this->aLog as $sText) {
$oPage->p($sText);
}
}
@@ -104,43 +111,102 @@ class MFCompiler
return $this->aLog;
}
/**
* @param bool $bForce if true then will just check file existence
*
* @return bool possible return values :
* * always false if not in dev env
* * `symlink` function non existent : false
* * if flag is present true, false otherwise
*
* @uses utils::IsDevelopmentEnvironment()
* @uses \function_exists()
* @uses \file_exists()
* @uses USE_SYMBOLIC_LINKS_FILE_PATH
*
* @since 3.0.0
*/
public static function IsUseSymbolicLinksFlagPresent(bool $bForce = false): bool
{
if (!$bForce) {
if (!utils::IsDevelopmentEnvironment()) {
return false;
}
if (!function_exists('symlink')) {
return false;
}
}
return (file_exists(static::USE_SYMBOLIC_LINKS_FILE_PATH));
}
/**
* @param bool $bUseSymbolicLinks
*
* @since 3.0.0 method creation
* @uses USE_SYMBOLIC_LINKS_FILE_PATH
*/
public static function SetUseSymbolicLinksFlag(bool $bUseSymbolicLinks): void
{
$bHasUseSymlinksFile = static::IsUseSymbolicLinksFlagPresent(true);
if ($bUseSymbolicLinks) {
if ($bHasUseSymlinksFile) {
return;
}
touch(static::USE_SYMBOLIC_LINKS_FILE_PATH);
return;
}
if (!$bHasUseSymlinksFile) {
return;
}
unlink(static::USE_SYMBOLIC_LINKS_FILE_PATH);
}
/**
* Compile the data model into PHP files and data structures
*
* @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
* @throws Exception
*/
public function Compile($sTargetDir, $oP = null, $bUseSymbolicLinks = false, $bSkipTempDir = false)
public function Compile($sTargetDir, $oP = null, $bUseSymbolicLinks = null, $bSkipTempDir = false)
{
if (is_null($bUseSymbolicLinks)) {
$bUseSymbolicLinks = false;
if (self::IsUseSymbolicLinksFlagPresent()) {
// We are only overriding the useSymLinks option if the consumer didn't specify anything
// The toolkit always send this parameter for example, but not the Designer Connector
$bUseSymbolicLinks = true;
}
}
$sFinalTargetDir = $sTargetDir;
$bIsAlreadyInMaintenanceMode = SetupUtils::IsInMaintenanceMode();
$sConfigFilePath = utils::GetConfigFilePath($this->sEnvironment);
if (is_file($sConfigFilePath))
{
if (is_file($sConfigFilePath)) {
$oConfig = new Config($sConfigFilePath);
}
else
{
} else {
$oConfig = null;
}
if (($this->sEnvironment == 'production') && !$bIsAlreadyInMaintenanceMode)
{
if (($this->sEnvironment == 'production') && !$bIsAlreadyInMaintenanceMode) {
SetupUtils::EnterMaintenanceMode($oConfig);
}
if ($bUseSymbolicLinks || $bSkipTempDir)
{
if ($bUseSymbolicLinks || $bSkipTempDir) {
// Skip the creation of a temporary dictionary, not compatible with symbolic links
$sTempTargetDir = $sFinalTargetDir;
SetupUtils::rrmdir($sFinalTargetDir);
SetupUtils::builddir($sFinalTargetDir); // Here is the directory
}
else
{
} else {
// Create a temporary directory
// Once the compilation is 100% successful, then move the results into the target directory
$sTempTargetDir = tempnam(SetupUtils::GetTmpDir(), 'itop-');
@@ -226,17 +292,14 @@ class MFCompiler
// The bullet proof implementation would be to compile in a separate directory as it has been done with the dictionaries... that's another story
$aModules = $this->oFactory->GetLoadedModules();
$sUserRightsModule = '';
foreach($aModules as $foo => $oModule)
{
if ($oModule->GetName() == 'itop-profiles-itil')
{
foreach ($aModules as $foo => $oModule) {
if ($oModule->GetName() == 'itop-profiles-itil') {
$sUserRightsModule = 'itop-profiles-itil';
break;
}
}
$oUserRightsNode = $this->oFactory->GetNodes('user_rights')->item(0);
if ($oUserRightsNode && ($sUserRightsModule == ''))
{
if ($oUserRightsNode && ($sUserRightsModule == '')) {
// Legacy algorithm (itop <= 2.0.3)
$sUserRightsModule = $oUserRightsNode->getAttribute('_created_in');
}
@@ -245,8 +308,7 @@ class MFCompiler
// List root classes
//
$this->aRootClasses = array();
foreach ($this->oFactory->ListRootClasses() as $oClass)
{
foreach ($this->oFactory->ListRootClasses() as $oClass) {
$this->Log("Root class (with child classes): ".$oClass->getAttribute('id'));
$this->aRootClasses[$oClass->getAttribute('id')] = $oClass;
}
@@ -264,33 +326,25 @@ class MFCompiler
$this->WriteStaticOnlyHtaccess($sTempTargetDir);
$this->WriteStaticOnlyWebConfig($sTempTargetDir);
foreach($aModules as $foo => $oModule)
{
static::SetUseSymbolicLinksFlag($bUseSymbolicLinks);
foreach ($aModules as $foo => $oModule) {
$sModuleName = $oModule->GetName();
$sModuleVersion = $oModule->GetVersion();
$sModuleRootDir = $oModule->GetRootDir();
if ($sModuleRootDir != '')
{
if ($sModuleRootDir != '') {
$sModuleRootDir = realpath($sModuleRootDir);
$sRelativeDir = basename($sModuleRootDir);
if ($bUseSymbolicLinks)
{
if ($bUseSymbolicLinks) {
$sRealRelativeDir = substr($sModuleRootDir, $iStart);
}
else
{
} else {
$sRealRelativeDir = $sRelFinalTargetDir.'/'.$sRelativeDir;
}
// Push the other module files
SetupUtils::copydir($sModuleRootDir, $sTempTargetDir.'/'.$sRelativeDir, $bUseSymbolicLinks);
}
else
{
} else {
$sRelativeDir = $sModuleName;
$sRealRelativeDir = $sModuleName;
}

View File

@@ -490,11 +490,13 @@ class RunTimeEnvironment
* The list of modules to be installed in the target environment is:
* - the list of modules present in the "source_dir" (defined by the source environment) which are marked as "installed" in the source environment's database
* - plus the list of modules present in the "extra" directory of the target environment: data/<target_environment>-modules/
*
* @param string $sSourceEnv The name of the source environment to 'imitate'
* @param bool $bUseSymLinks Whether to create symbolic links instead of copies
*
* @return string[]
*/
public function CompileFrom($sSourceEnv, $bUseSymLinks = false)
public function CompileFrom($sSourceEnv, $bUseSymLinks = null)
{
$oSourceConfig = new Config(utils::GetConfigFilePath($sSourceEnv));
$sSourceDir = $oSourceConfig->Get('source_dir');
@@ -516,13 +518,10 @@ class RunTimeEnvironment
}
if ($oModule instanceof MFDeltaModule)
{
if ($oModule instanceof MFDeltaModule) {
// A delta was loaded, let's save a second copy of the datamodel
$oFactory->SaveToFile(APPROOT.'data/datamodel-'.$this->sTargetEnv.'-with-delta.xml');
}
else
{
} else {
// No delta was loaded, let's save the datamodel now
$oFactory->SaveToFile(APPROOT.'data/datamodel-'.$this->sTargetEnv.'.xml');
}

View File

@@ -1161,6 +1161,30 @@ class WizStepUpgradeMiscParams extends WizardStep
});
EOF
);
if (MFCompiler::IsUseSymbolicLinksFlagPresent()) {
$oPage->add('<fieldset>');
$oPage->add('<legend>Dev parameters</legend>');
$oPage->p('<input id="use-symbolic-links" type="checkbox" checked><label for="use-symbolic-links">&nbsp;Create symbolic links instead of creating a copy in env-production (useful for debugging extensions)');
$oPage->add('</fieldset>');
$oPage->add_ready_script(<<<'JS'
$("#use-symbolic-links").on("click", function() {
var $this = $(this),
bUseSymbolicLinks = $this.prop("checked");
if (!bUseSymbolicLinks){
if (!window.confirm("This will disable symbolic links generation.\nYou'll need the toolkit to restore this option.\n\nAre you sure ?")) {
$this.prop("checked", true);
return;
}
}
var sAuthent = $('#authent_token').val();
var oAjaxParams = { operation: 'toggle_use_symbolic_links', bUseSymbolicLinks: bUseSymbolicLinks, authent: sAuthent};
$.post(GetAbsoluteUrlAppRoot()+'setup/ajax.dataloader.php', oAjaxParams);
});
JS
);
}
}
public function AsyncAction(WebPage $oPage, $sCode, $aParameters)