# setup.py# A distutils setup script to install TortoiseHg in Windows and Posix# environments.## On Windows, this script is mostly used to build a stand-alone# TortoiseHg package. See installer\build.txt for details. The other# use is to report the current version of the TortoiseHg source.importtimeimportsysimportosimportshutilimportsubprocessimportcgiimporttempfileimportrefromfnmatchimportfnmatchfromdistutilsimportlogfromdistutils.coreimportsetup,Commandfromdistutils.command.buildimportbuildas_build_origfromdistutils.command.cleanimportcleanas_clean_origfromdistutils.dep_utilimportnewer,newer_groupfromdistutils.spawnimportspawn,find_executablefromos.pathimportisdir,exists,join,walk,splitextfromi18n.msgfmtimportMsgfmtthgcopyright='Copyright (C) 2010 Steve Borho and others'hgcopyright='Copyright (C) 2005-2010 Matt Mackall and others'classbuild_mo(Command):description="build translations (.mo files)"user_options=[]definitialize_options(self):passdeffinalize_options(self):passdefrun(self):podir='i18n/tortoisehg'ifnotos.path.isdir(podir):self.warn("could not find %s/ directory"%podir)returnjoin=os.path.joinforpoinos.listdir(podir):ifnotpo.endswith('.po'):continuepofile=join(podir,po)modir=join('locale',po[:-3],'LC_MESSAGES')mofile=join(modir,'tortoisehg.mo')modata=Msgfmt(pofile).get()self.mkpath(modir)open(mofile,"wb").write(modata)classupdate_pot(Command):description="extract translatable strings to tortoisehg.pot"user_options=[]definitialize_options(self):passdeffinalize_options(self):passdefrun(self):ifnotfind_executable('xgettext'):self.warn("could not find xgettext executable, tortoisehg.pot""won't be built")returndirlist=['.','contrib','contrib/win32','tortoisehg','tortoisehg/hgqt','tortoisehg/hgtk','tortoisehg/hgtk/logview','tortoisehg/util','tortoisehg/thgutil/iniparse',]filelist=[]forpathnameindirlist:ifnotos.path.exists(pathname):continueforfilenameinos.listdir(pathname):iffilename.endswith('.py'):filelist.append(os.path.join(pathname,filename))filelist.sort()potfile='tortoisehg.pot'cmd=['xgettext','--package-name','TortoiseHg','--msgid-bugs-address','<thg-devel@googlegroups.com>','--copyright-holder',thgcopyright,'--from-code','ISO-8859-1','--keyword=_:1,2c,2t','--add-comments=i18n:','-d','.','-o',potfile,]cmd+=filelistself.make_file(filelist,potfile,spawn,(cmd,))classbuild_qt(Command):description="build PyQt GUIs (.ui) and resources (.qrc)"user_options=[('force','f','forcibly compile everything'' (ignore file timestamps)'),('frozen',None,'include resources for frozen exe')]boolean_options=('force','frozen')definitialize_options(self):self.force=Noneself.frozen=Falsedeffinalize_options(self):self.set_undefined_options('build',('force','force'))defcompile_ui(self,ui_file,py_file=None):# Search for pyuic4 in python bin dir, then in the $Path.ifpy_fileisNone:py_file=splitext(ui_file)[0]+"_ui.py"ifnot(self.forceornewer(ui_file,py_file)):returntry:fromPyQt4importuicfp=open(py_file,'w')uic.compileUi(ui_file,fp)fp.close()log.info('compiled %s into %s'%(ui_file,py_file))exceptException,e:self.warn('Unable to compile user interface %s: %s'%(py_file,e))ifnotexists(py_file)ornotfile(py_file).read():raiseSystemExit(1)returndefcompile_rc(self,qrc_file,py_file=None):# Search for pyuic4 in python bin dir, then in the $Path.ifpy_fileisNone:py_file=splitext(qrc_file)[0]+"_rc.py"ifnot(self.forceornewer(qrc_file,py_file)):returnimportPyQt4origpath=os.getenv('PATH')path=origpath.split(os.pathsep)pyqtfolder=os.path.dirname(PyQt4.__file__)path.append(os.path.join(pyqtfolder,'bin'))os.putenv('PATH',os.pathsep.join(path))ifos.system('pyrcc4 "%s" -o "%s"'%(qrc_file,py_file))>0:self.warn("Unable to generate python module %s for resource file %s"%(py_file,qrc_file))ifnotexists(py_file)ornotfile(py_file).read():raiseSystemExit(1)else:log.info('compiled %s into %s'%(qrc_file,py_file))os.putenv('PATH',origpath)def_generate_qrc(self,qrc_file,srcfiles,prefix):basedir=os.path.dirname(qrc_file)f=open(qrc_file,'w')try:f.write('<!DOCTYPE RCC><RCC version="1.0">\n')f.write(' <qresource prefix="%s">\n'%cgi.escape(prefix))foreinsrcfiles:relpath=e[len(basedir)+1:]f.write(' <file>%s</file>\n'%cgi.escape(relpath.replace(os.path.sep,'/')))f.write(' </qresource>\n')f.write('</RCC>\n')finally:f.close()defbuild_rc(self,py_file,basedir,prefix='/'):"""Generate compiled resource including any files under basedir"""# For details, see http://doc.qt.nokia.com/latest/resources.htmlqrc_file=os.path.join(basedir,'%s.qrc'%os.path.basename(basedir))srcfiles=[os.path.join(root,e)forroot,_dirs,filesinos.walk(basedir)foreinfiles]# NOTE: Here we cannot detect deleted files. In such case, we need# to remove .qrc manually.ifnot(self.forceornewer_group(srcfiles,py_file)):returntry:self._generate_qrc(qrc_file,srcfiles,prefix)self.compile_rc(qrc_file,py_file)finally:os.unlink(qrc_file)def_build_translations(self,basepath):"""Build translations_rc.py which inclues qt_xx.qm"""fromPyQt4.QtCoreimportQLibraryInfotrpath=unicode(QLibraryInfo.location(QLibraryInfo.TranslationsPath))d=tempfile.mkdtemp()try:foreinos.listdir(trpath):ifre.match(r'qt_[a-z]{2}(_[A-Z]{2})?\.ts$',e):r=os.system('lrelease "%s" -qm "%s"'%(os.path.join(trpath,e),os.path.join(d,e[:-3]+'.qm')))ifr>0:self.warn('Unable to generate Qt message file'' from %s'%e)self.build_rc(os.path.join(basepath,'translations_rc.py'),d,'/translations')finally:shutil.rmtree(d)defrun(self):self._wrapuic()basepath=join(os.path.dirname(__file__),'tortoisehg','hgqt')self.build_rc(os.path.join(basepath,'icons_rc.py'),os.path.join(os.path.dirname(__file__),'icons'),'/icons')ifself.frozen:self._build_translations(basepath)fordirpath,_,filenamesinos.walk(basepath):forfilenameinfilenames:iffilename.endswith('.ui'):self.compile_ui(join(dirpath,filename))eliffilename.endswith('.qrc'):self.compile_rc(join(dirpath,filename))_wrappeduic=False@classmethoddef_wrapuic(cls):"""wrap uic to use gettext's _() in place of tr()"""ifcls._wrappeduic:returnfromPyQt4.uic.Compilerimportcompiler,qtproxies,indenterclass_UICompiler(compiler.UICompiler):defcreateToplevelWidget(self,classname,widgetname):o=indenter.getIndenter()o.level=0o.write('from tortoisehg.hgqt.i18n import _')returnsuper(_UICompiler,self).createToplevelWidget(classname,widgetname)compiler.UICompiler=_UICompilerclass_i18n_string(qtproxies.i18n_string):def__str__(self):return"_('%s')"%self.string.encode('string-escape')qtproxies.i18n_string=_i18n_stringcls._wrappeduic=Trueclassclean_local(Command):pats=['*.py[co]','*_ui.py','*_rc.py','*.mo','*.orig','*.rej']excludedirs=['.hg','build','dist']description='clean up generated files (%s)'%', '.join(pats)user_options=[]definitialize_options(self):passdeffinalize_options(self):passdefrun(self):foreinself._walkpaths('.'):log.info("removing '%s'"%e)os.remove(e)def_walkpaths(self,path):forroot,_dirs,filesinos.walk(path):ifany(root==join(path,e)orroot.startswith(join(path,e,''))foreinself.excludedirs):continueforeinfiles:fpath=join(root,e)ifany(fnmatch(fpath,p)forpinself.pats):yieldfpathclassbuild(_build_orig):sub_commands=[('build_qt',None),('build_mo',None),]+_build_orig.sub_commandsclassclean(_clean_orig):sub_commands=[('clean_local',None),]+_clean_orig.sub_commandsdefrun(self):_clean_orig.run(self)foreinself.get_sub_commands():self.run_command(e)cmdclass={'build':build,'build_qt':build_qt,'build_mo':build_mo,'clean':clean,'clean_local':clean_local,'update_pot':update_pot,}defsetup_windows(version):# Specific definitios for Windows NT-alike installations_scripts=[]_data_files=[]_packages=['tortoisehg.hgqt','tortoisehg.util','tortoisehg']extra={}hgextmods=[]# py2exe needs to be installed to worktry:importpy2exe# Help py2exe to find win32com.shelltry:importmodulefinderimportwin32comforpinwin32com.__path__[1:]:# Take the path to win32comextmodulefinder.AddPackagePath("win32com",p)pn="win32com.shell"__import__(pn)m=sys.modules[pn]forpinm.__path__[1:]:modulefinder.AddPackagePath(pn,p)exceptImportError:passexceptImportError:if'--version'notinsys.argv:raise# Allow use of environment variables to specify the location of Mercurialimportmodulefinderpath=os.getenv('MERCURIAL_PATH')ifpath:modulefinder.AddPackagePath('mercurial',path)path=os.getenv('HGEXT_PATH')ifpath:modulefinder.AddPackagePath('hgext',path)if'py2exe'insys.argv:importhgexthgextdir=os.path.dirname(hgext.__file__)hgextmods=set(["hgext."+os.path.splitext(f)[0]forfinos.listdir(hgextdir)])_data_files=[(root,[os.path.join(root,file_)forfile_infiles])forroot,dirs,filesinos.walk('icons')]# for PyQt, see http://www.py2exe.org/index.cgi/Py2exeAndPyQtincludes=['sip']# Qt4 plugins, see http://stackoverflow.com/questions/2206406/defqt4_plugins(subdir,*dlls):importPyQt4pluginsdir=join(os.path.dirname(PyQt4.__file__),'plugins')return(subdir,[join(pluginsdir,subdir,e)foreindlls])_data_files.append(qt4_plugins('imageformats','qico4.dll','qsvg4.dll'))# Manually include other modules py2exe can't find by itself.if'hgext.highlight'inhgextmods:includes+=['pygments.*','pygments.lexers.*','pygments.formatters.*','pygments.filters.*','pygments.styles.*']if'hgext.patchbomb'inhgextmods:includes+=['email.*','email.mime.*']extra['options']={"py2exe":{"skip_archive":0,# Don't pull in all this MFC stuff used by the makepy UI."excludes":"pywin,pywin.dialogs,pywin.dialogs.list"",setup,distutils",# required only for in-place use"includes":includes,"optimize":1}}shutil.copyfile('thg','thgw')extra['console']=[{'script':'thg','icon_resources':[(0,'icons/thg_logo.ico')],'description':'TortoiseHg GUI tools for Mercurial SCM','copyright':thgcopyright,'product_version':version},{'script':'contrib/hg','icon_resources':[(0,'icons/hg.ico')],'description':'Mercurial Distributed SCM','copyright':hgcopyright,'product_version':version},{'script':'win32/docdiff.py','icon_resources':[(0,'icons/TortoiseMerge.ico')],'copyright':thgcopyright,'product_version':version}]extra['windows']=[{'script':'thgw','icon_resources':[(0,'icons/thg_logo.ico')],'description':'TortoiseHg GUI tools for Mercurial SCM','copyright':thgcopyright,'product_version':version},{'script':'TortoiseHgOverlayServer.py','icon_resources':[(0,'icons/thg_logo.ico')],'description':'TortoiseHg Overlay Icon Server','copyright':thgcopyright,'product_version':version}]return_scripts,_packages,_data_files,extradefsetup_posix():# Specific definitios for Posix installations_extra={}_scripts=['thg']_packages=['tortoisehg','tortoisehg.hgqt','tortoisehg.util']_data_files=[(os.path.join('share/pixmaps/tortoisehg',root),[os.path.join(root,file_)forfile_infiles])forroot,dirs,filesinos.walk('icons')]_data_files+=[(os.path.join('share',root),[os.path.join(root,file_)forfile_infiles])forroot,dirs,filesinos.walk('locale')]_data_files+=[('lib/nautilus/extensions-2.0/python',['contrib/nautilus-thg.py'])]# Create a config.py. Distributions will need to supply their owncfgfile=os.path.join('tortoisehg','util','config.py')ifnotos.path.exists(cfgfile)andnotos.path.exists('.hg/requires'):f=open(cfgfile,"w")f.write('bin_path = "/usr/bin"\n')f.write('license_path = "/usr/share/doc/tortoisehg/Copying.txt.gz"\n')f.write('locale_path = "/usr/share/locale"\n')f.write('icon_path = "/usr/share/pixmaps/tortoisehg/icons"\n')f.write('nofork = True\n')f.close()return_scripts,_packages,_data_files,_extradefruncmd(cmd,env):p=subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,env=env)out,err=p.communicate()# If root is executing setup.py, but the repository is owned by# another user (as in "sudo python setup.py install") we will get# trust warnings since the .hg/hgrc file is untrusted. That is# fine, we don't want to load it anyway.err=[eforeinerr.splitlines()ifnote.startswith('Not trusting file')]iferr:return''returnoutif__name__=='__main__':version=''ifos.path.isdir('.hg'):fromtortoisehg.utilimportversionas_versionbranch,version=_version.liveversion()ifversion.endswith('+'):version+=time.strftime('%Y%m%d')elifos.path.exists('.hg_archival.txt'):kw=dict([t.strip()fortinl.split(':',1)]forlinopen('.hg_archival.txt'))if'tag'inkw:version=kw['tag']elif'latesttag'inkw:version='%(latesttag)s+%(latesttagdistance)s-%(node).12s'%kwelse:version=kw.get('node','')[:12]ifversion:f=open("tortoisehg/util/__version__.py","w")f.write('# this file is autogenerated by setup.py\n')f.write('version = "%s"\n'%version)f.close()try:importtortoisehg.util.__version__version=tortoisehg.util.__version__.versionexceptImportError:version='unknown'ifos.name=="nt":(scripts,packages,data_files,extra)=setup_windows(version)desc='Windows shell extension for Mercurial VCS'# Windows binary file versions for exe/dll files must have the# form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535fromtortoisehg.util.versionimportpackage_versionsetupversion=package_version()productname='TortoiseHg'else:(scripts,packages,data_files,extra)=setup_posix()desc='TortoiseHg dialogs for Mercurial VCS'setupversion=versionproductname='tortoisehg'setup(name=productname,version=setupversion,author='Steve Borho',author_email='steve@borho.org',url='http://tortoisehg.org',description=desc,license='GNU GPL2',scripts=scripts,packages=packages,data_files=data_files,cmdclass=cmdclass,**extra)