300 lines
15 KiB
Python
300 lines
15 KiB
Python
'''This script allows to create a brand new read-to-use Plone/Zone instance.
|
|
As prerequisite, you must have installed Plone through the Unifier installer
|
|
available at http://plone.org.'''
|
|
|
|
# ------------------------------------------------------------------------------
|
|
import os, os.path, sys, shutil
|
|
from optparse import OptionParser
|
|
from appy.shared.utils import cleanFolder, copyFolder
|
|
|
|
# ------------------------------------------------------------------------------
|
|
class NewError(Exception): pass
|
|
ERROR_CODE = 1
|
|
WRONG_NB_OF_ARGS = 'Wrong number of args.'
|
|
WRONG_PLONE_VERSION = 'Plone version must be among %s.'
|
|
WRONG_PLONE_PATH = 'Path "%s" is not an existing folder.'
|
|
PYTHON_NOT_FOUND = 'Python interpreter was not found in "%s". Are you sure ' \
|
|
'we are in the folder hierarchy created by the Plone installer?'
|
|
PYTHON_EXE_NOT_FOUND = '"%s" does not exist.'
|
|
MKZOPE_NOT_FOUND = 'Script mkzopeinstance.py not found in "%s and ' \
|
|
'subfolders. Are you sure we are in the folder hierarchy created by ' \
|
|
'the Plone installer?'
|
|
WRONG_INSTANCE_PATH = '"%s" must be an existing folder for creating the ' \
|
|
'instance in it.'
|
|
|
|
# ------------------------------------------------------------------------------
|
|
class NewScript:
|
|
'''usage: %prog ploneVersion plonePath instancePath
|
|
|
|
"ploneVersion" can be plone25, plone30, or plone3x
|
|
(plone3x can be Plone 3.2.x, Plone 3.3.5...)
|
|
|
|
"plonePath" is the (absolute) path to you plone installation.
|
|
Plone 2.5 and 3.0 are typically installed in
|
|
/opt/Plone-x.x.x, while Plone 3 > 3.0 is typically
|
|
installed in in /usr/local/Plone.
|
|
"instancePath" is the (absolute) path where you want to create your
|
|
instance (should not already exist).'''
|
|
ploneVersions = ('plone25', 'plone30', 'plone3x')
|
|
|
|
def createInstance(self, linksForProducts):
|
|
'''Calls the Zope script that allows to create a Zope instance and copy
|
|
into it all the Plone packages and products.'''
|
|
# Find the Python interpreter running Zope
|
|
for elem in os.listdir(self.plonePath):
|
|
pythonPath = None
|
|
elemPath = os.path.join(self.plonePath, elem)
|
|
if elem.startswith('Python-') and os.path.isdir(elemPath):
|
|
pythonPath = elemPath + '/bin/python'
|
|
if not os.path.exists(pythonPath):
|
|
raise NewError(PYTHON_EXE_NOT_FOUND % pythonPath)
|
|
break
|
|
if not pythonPath:
|
|
raise NewError(PYTHON_NOT_FOUND % self.plonePath)
|
|
# Find the Zope script mkzopeinstance.py
|
|
makeInstancePath = None
|
|
for dirname, dirs, files in os.walk(self.plonePath):
|
|
# Do not browse the buildout-cache
|
|
for fileName in files:
|
|
if (fileName == 'mkzopeinstance.py') and \
|
|
('/buildout-cache/' not in dirname):
|
|
makeInstancePath = os.path.join(dirname, fileName)
|
|
if not makeInstancePath:
|
|
raise NewError(MKZOPE_NOT_FOUND % self.plonePath)
|
|
# Execute mkzopeinstance.py with the right Python interpreter
|
|
cmd = '%s %s -d %s' % (pythonPath, makeInstancePath, self.instancePath)
|
|
print cmd
|
|
os.system(cmd)
|
|
# Now, make the instance Plone-ready
|
|
action = 'Copying'
|
|
if linksForProducts:
|
|
action = 'Symlinking'
|
|
print '%s Plone stuff in the Zope instance...' % action
|
|
if self.ploneVersion in ('plone25', 'plone30'):
|
|
self.installPlone25or30Stuff(linksForProducts)
|
|
elif self.ploneVersion == 'plone3x':
|
|
self.installPlone3Stuff()
|
|
# Clean the copied folders
|
|
cleanFolder(os.path.join(self.instancePath, 'Products'))
|
|
cleanFolder(os.path.join(self.instancePath, 'lib/python'))
|
|
|
|
def installPlone25or30Stuff(self, linksForProducts):
|
|
'''Here, we will copy all Plone2-related stuff in the Zope instance
|
|
we've created, to get a full Plone-ready Zope instance. If
|
|
p_linksForProducts is True, we do not perform a real copy: we will
|
|
create symlinks to products lying within Plone installer files.'''
|
|
j = os.path.join
|
|
if self.ploneVersion == 'plone25':
|
|
sourceFolders = ('zeocluster/Products',)
|
|
else:
|
|
sourceFolders = ('zinstance/Products', 'zinstance/lib/python')
|
|
for sourceFolder in sourceFolders:
|
|
sourceBase = j(self.plonePath, sourceFolder)
|
|
destBase = j(self.instancePath,
|
|
sourceFolder[sourceFolder.find('/')+1:])
|
|
for name in os.listdir(sourceBase):
|
|
folderName = j(sourceBase, name)
|
|
if os.path.isdir(folderName):
|
|
destFolder = j(destBase, name)
|
|
# This is a Plone product. Copy it to the instance.
|
|
if linksForProducts:
|
|
# Create a symlink to this product in the instance
|
|
cmd = 'ln -s %s %s' % (folderName, destFolder)
|
|
os.system(cmd)
|
|
else:
|
|
# Copy thre product into the instance
|
|
copyFolder(folderName, destFolder)
|
|
|
|
uglyChunks = ('pkg_resources', '.declare_namespace(')
|
|
def findPythonPackageInEgg(self, currentFolder):
|
|
'''Finds the Python package that is deeply hidden into the egg.'''
|
|
# Find the file __init__.py
|
|
isFinalPackage = False
|
|
for elem in os.listdir(currentFolder):
|
|
elemPath = os.path.join(currentFolder, elem)
|
|
if elem == '__init__.py':
|
|
f = file(elemPath)
|
|
content = f.read()
|
|
f.close()
|
|
# Is it a awful egg init ?
|
|
for chunk in self.uglyChunks:
|
|
if content.find(chunk) == -1:
|
|
isFinalPackage = True # It is not an ugly egg init.
|
|
break
|
|
if not isFinalPackage:
|
|
# Maybe we are wrong: our way to identify egg-viciated __init__
|
|
# files is approximative. If we believe it is not the final package,
|
|
# but we find other Python files in the folder, we must admit that
|
|
# we've nevertheless found the final Python package.
|
|
otherPythonFiles = False
|
|
for elem in os.listdir(currentFolder):
|
|
if elem.endswith('.py') and (elem != '__init__.py'):
|
|
otherPythonFiles = True
|
|
break
|
|
if otherPythonFiles:
|
|
# Ok, this is the final Python package
|
|
return currentFolder
|
|
# Find the subfolder and find the Python package into it.
|
|
for elem in os.listdir(currentFolder):
|
|
elemPath = os.path.join(currentFolder, elem)
|
|
if os.path.isdir(elemPath):
|
|
return self.findPythonPackageInEgg(elemPath)
|
|
else:
|
|
return currentFolder
|
|
|
|
def getSubFolder(self, folder):
|
|
'''In p_folder, we now that there is only one subfolder. This method
|
|
returns the subfolder's absolute path.'''
|
|
for elem in os.listdir(folder):
|
|
elemPath = os.path.join(folder, elem)
|
|
if (elem != 'EGG-INFO') and os.path.isdir(elemPath):
|
|
return elemPath
|
|
return None
|
|
|
|
viciatedFiles = {'meta.zcml': 'includePlugins',
|
|
'configure.zcml': 'includePlugins',
|
|
'overrides.zcml': 'includePluginsOverrides'}
|
|
def patchPlone(self, productsFolder, libFolder):
|
|
'''Auto-proclaimed ugly code in z3c forces us to patch some files
|
|
in Products.CMFPlone because these guys make the assumption that
|
|
"plone.xxx" packages are within eggs when they've implemented their
|
|
ZCML directives "includePlugins" and "includePluginsOverrides".
|
|
So in this method, I remove every call to those directives in
|
|
CMFPlone files. It does not seem to affect Plone behaviour. Indeed,
|
|
these directives seem to be useful only when adding sad (ie, non
|
|
Appy) Plone plug-ins.'''
|
|
ploneFolder = os.path.join(productsFolder, 'CMFPlone')
|
|
# Patch viciated files
|
|
for fileName, uglyDirective in self.viciatedFiles.iteritems():
|
|
filePath = os.path.join(ploneFolder, fileName)
|
|
f = file(filePath)
|
|
fileContent = f.read()
|
|
f.close()
|
|
if fileContent.find(uglyDirective) != -1:
|
|
toReplace = '<%s package="plone" file="%s" />' % \
|
|
(uglyDirective, fileName)
|
|
fileContent = fileContent.replace(toReplace, '')
|
|
f = file(filePath, 'w')
|
|
f.write(fileContent)
|
|
f.close()
|
|
|
|
def installPlone3Stuff(self):
|
|
'''Here, we will copy all Plone3-related stuff in the Zope instance
|
|
we've created, to get a full Plone-ready Zope instance.'''
|
|
# All Plone 3 eggs are in buildout-cache/eggs. We will extract from
|
|
# those silly overstructured folder hierarchies the standard Python
|
|
# packages that lie in it, and copy them in the instance. Within these
|
|
# eggs, we need to distinguish:
|
|
# - standard Python packages that will be copied in
|
|
# <zopeInstance>/lib/python (ie, like Appy applications)
|
|
# - Zope products that will be copied in
|
|
# <zopeInstance>/Products (ie, like Appy generated Zope products)
|
|
j = os.path.join
|
|
eggsFolder = j(self.plonePath, 'buildout-cache/eggs')
|
|
productsFolder = j(self.instancePath, 'Products')
|
|
libFolder = j(self.instancePath, 'lib/python')
|
|
for name in os.listdir(eggsFolder):
|
|
eggMainFolder = j(eggsFolder, name)
|
|
if name.startswith('Products.'):
|
|
# A Zope product. Copy its content in Products.
|
|
innerFolder= self.getSubFolder(self.getSubFolder(eggMainFolder))
|
|
destFolder = j(productsFolder, os.path.basename(innerFolder))
|
|
copyFolder(innerFolder, destFolder)
|
|
else:
|
|
# A standard Python package. Copy its content in lib/python.
|
|
# Go into the subFolder that is not EGG-INFO.
|
|
eggFolder = self.getSubFolder(eggMainFolder)
|
|
if not eggFolder:
|
|
# This egg is malformed and contains basic Python files.
|
|
# Copy those files directly in libFolder.
|
|
for fileName in os.listdir(eggMainFolder):
|
|
if fileName.endswith('.py'):
|
|
fullFileName= j(eggMainFolder, fileName)
|
|
shutil.copy(fullFileName, libFolder)
|
|
continue
|
|
eggFolderName = os.path.basename(eggFolder)
|
|
if eggFolderName == 'Products':
|
|
# Goddamned. This should go in productsFolder and not in
|
|
# libFolder.
|
|
innerFolder = self.getSubFolder(eggFolder)
|
|
destFolder = j(productsFolder,os.path.basename(innerFolder))
|
|
copyFolder(innerFolder, destFolder)
|
|
else:
|
|
packageFolder = self.findPythonPackageInEgg(eggFolder)
|
|
# Create the destination folder(s) in the instance,
|
|
# within libFolder
|
|
destFolders = []
|
|
if packageFolder != eggFolder:
|
|
destFolders = [eggFolderName]
|
|
remFolders = packageFolder[len(eggFolder):]
|
|
remFolders = remFolders.strip(os.sep)
|
|
if remFolders.find(os.sep) != -1:
|
|
# There are more subfolders
|
|
destFolders += remFolders.split(os.sep)[:-1]
|
|
if destFolders:
|
|
# We must create the subfolders (if not yet created)
|
|
# before copying the Python package.
|
|
baseFolder = libFolder
|
|
for subFolder in destFolders:
|
|
subFolderPath = j(baseFolder,subFolder)
|
|
if not os.path.exists(subFolderPath):
|
|
os.mkdir(subFolderPath)
|
|
# Create an empty __init__.py in it.
|
|
init = j(subFolderPath,'__init__.py')
|
|
f = file(init, 'w')
|
|
f.write('# Makes me a Python package.')
|
|
f.close()
|
|
baseFolder = subFolderPath
|
|
destFolder = os.sep.join(destFolders)
|
|
destFolder = j(libFolder, destFolder)
|
|
if not os.path.exists(destFolder):
|
|
os.makedirs(destFolder)
|
|
else:
|
|
destFolder = libFolder
|
|
destFolder = j(destFolder, os.path.basename(packageFolder))
|
|
copyFolder(packageFolder, destFolder)
|
|
self.patchPlone(productsFolder, libFolder)
|
|
|
|
def manageArgs(self, args):
|
|
'''Ensures that the script was called with the right parameters.'''
|
|
if len(args) != 3: raise NewError(WRONG_NB_OF_ARGS)
|
|
self.ploneVersion, self.plonePath, self.instancePath = args
|
|
# Check Plone version
|
|
if self.ploneVersion not in self.ploneVersions:
|
|
raise NewError(WRONG_PLONE_VERSION % str(self.ploneVersions))
|
|
# Check Plone path
|
|
if not os.path.exists(self.plonePath) \
|
|
or not os.path.isdir(self.plonePath):
|
|
raise NewError(WRONG_PLONE_PATH % self.plonePath)
|
|
# Check instance path
|
|
parentFolder = os.path.dirname(self.instancePath)
|
|
if not os.path.exists(parentFolder) or not os.path.isdir(parentFolder):
|
|
raise NewError(WRONG_INSTANCE_PATH % parentFolder)
|
|
|
|
def run(self):
|
|
optParser = OptionParser(usage=NewScript.__doc__)
|
|
optParser.add_option("-l", "--links", action="store_true",
|
|
help="[Linux, plone25 or plone30 only] Within the created " \
|
|
"instance, symlinks to Products lying within the Plone " \
|
|
"installer files are created instead of copying them into " \
|
|
"the instance. This avoids duplicating the Products source " \
|
|
"code and is interesting if you create a lot of Zope " \
|
|
"instances.")
|
|
(options, args) = optParser.parse_args()
|
|
linksForProducts = options.links
|
|
try:
|
|
self.manageArgs(args)
|
|
print 'Creating new %s instance...' % self.ploneVersion
|
|
self.createInstance(linksForProducts)
|
|
except NewError, ne:
|
|
optParser.print_help()
|
|
print
|
|
sys.stderr.write(str(ne))
|
|
sys.stderr.write('\n')
|
|
sys.exit(ERROR_CODE)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
if __name__ == '__main__':
|
|
NewScript().run()
|
|
# ------------------------------------------------------------------------------
|