Mercurial > libervia-backend
comparison setup.py @ 2562:26edcf3a30eb
core, setup: huge cleaning:
- moved directories from src and frontends/src to sat and sat_frontends, which is the recommanded naming convention
- move twisted directory to root
- removed all hacks from setup.py, and added missing dependencies, it is now clean
- use https URL for website in setup.py
- removed "Environment :: X11 Applications :: GTK", as wix is deprecated and removed
- renamed sat.sh to sat and fixed its installation
- added python_requires to specify Python version needed
- replaced glib2reactor which use deprecated code by gtk3reactor
sat can now be installed directly from virtualenv without using --system-site-packages anymore \o/
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 02 Apr 2018 19:44:50 +0200 |
parents | 06ff33052354 |
children | 6e5ab7bebd11 |
comparison
equal
deleted
inserted
replaced
2561:bd30dc3ffe5a | 2562:26edcf3a30eb |
---|---|
16 # GNU Affero General Public License for more details. | 16 # GNU Affero General Public License for more details. |
17 | 17 |
18 # You should have received a copy of the GNU Affero General Public License | 18 # You should have received a copy of the GNU Affero General Public License |
19 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 19 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
20 | 20 |
21 from ez_setup import use_setuptools | 21 from setuptools import setup, find_packages |
22 use_setuptools() | |
23 from setuptools.command.install import install | |
24 from setuptools import setup | |
25 from distutils.file_util import copy_file | |
26 import os | 22 import os |
27 import os.path | |
28 import sys | 23 import sys |
29 import subprocess | |
30 from stat import ST_MODE | |
31 import shutil | |
32 import re | |
33 | 24 |
34 # seen here: http://stackoverflow.com/questions/7275295 | 25 NAME = 'sat' |
35 try: | |
36 from setuptools.command import egg_info | |
37 egg_info.write_toplevel_names | |
38 except (ImportError, AttributeError): | |
39 pass | |
40 else: | |
41 def _top_level_package(name): | |
42 return name.split('.', 1)[0] | |
43 | 26 |
44 def _hacked_write_toplevel_names(cmd, basename, filename): | 27 install_requires = [ |
45 pkgs = dict.fromkeys( | 28 'babel', |
46 [_top_level_package(k) | 29 'dbus-python', |
47 for k in cmd.distribution.iter_distribution_names() | 30 'html2text', |
48 if _top_level_package(k) != "twisted" | 31 'jinja2', |
49 ] | 32 'langid', |
50 ) | 33 'lxml >= 3.1.0', |
51 cmd.write_file("top-level names", filename, '\n'.join(pkgs) + '\n') | 34 'markdown', |
52 | 35 'miniupnpc', |
53 egg_info.write_toplevel_names = _hacked_write_toplevel_names | 36 'mutagen', |
37 'netifaces', | |
38 'pillow', | |
39 'progressbar', | |
40 'pycrypto >= 2.6.1', | |
41 'pygments', | |
42 'pygobject', | |
43 'PyOpenSSL', | |
44 'python-potr', | |
45 'pyxdg', | |
46 'sat_tmp', | |
47 'service_identity', | |
48 'shortuuid', | |
49 'twisted >= 15.2.0', | |
50 'urwid >= 1.2.0', | |
51 'urwid-satext >= 0.6.1', | |
52 'wokkel >= 0.7.1', | |
53 ] | |
54 | 54 |
55 | 55 |
56 NAME = 'sat' | |
57 LAUNCH_DAEMON_COMMAND = 'sat' | |
58 | |
59 ENV_SAT_INSTALL = "SAT_INSTALL" # environment variable to customise installation | |
60 NO_PREINSTALL_OPT = 'nopreinstall' # skip all preinstallation checks | |
61 AUTO_DEB_OPT = 'autodeb' # automaticaly install debs | |
62 CLEAN_OPT = 'clean' # remove previous installation directories | |
63 PURGE_OPT = 'purge' # remove building and previous installation directories | |
64 DBUS_DIR = 'dbus-1/services' | 56 DBUS_DIR = 'dbus-1/services' |
65 DBUS_FILE = 'misc/org.goffi.SAT.service' | 57 DBUS_FILE = 'misc/org.goffi.SAT.service' |
66 | 58 |
67 # Following map describe file to adapt with installation path: | |
68 # key is the self attribute to get (e.g.: sh_script_path will modify self.sh_script_path file) | |
69 # value is a dict where key is the regex of the part to change, and value is either the string | |
70 # to replace or a tuple with a template and values to replace (if value to replace is a string, | |
71 # the attribute from self with that name will be used). | |
72 FILE_ADJ = {'sh_script_path': {r'PYTHON *=.*': 'PYTHON="{}"'.format(sys.executable)}, | |
73 'dbus_service_path': {r'Exec *=.*': ('Exec={}', 'sh_script_path_final')}, | |
74 } | |
75 | |
76 | |
77 class MercurialException(Exception): | |
78 pass | |
79 | |
80 | |
81 def module_installed(module_name): | |
82 """Try to import module_name, and return False if it failed | |
83 @param module_name: name of the module to test | |
84 @return: True if successful""" | |
85 try: | |
86 __import__(module_name) | |
87 except ImportError: | |
88 return False | |
89 return True | |
90 | |
91 | |
92 class CustomInstall(install): | |
93 | |
94 def adapt_files(self): | |
95 """Adapt files to installed environments | |
96 | |
97 Mainly change the paths | |
98 """ | |
99 def adapter(ordered_replace, match_obj): | |
100 """do file adjustment, getting self attribute when needed""" | |
101 idx = match_obj.lastindex - 1 | |
102 repl_data = ordered_replace[idx][1] | |
103 if isinstance(repl_data, tuple): | |
104 template = repl_data[0] | |
105 args = [getattr(self, arg) if isinstance(arg, basestring) else arg for arg in repl_data[1:]] | |
106 return template.format(*args) | |
107 return repl_data | |
108 | |
109 for file_attr, replace_data in FILE_ADJ.iteritems(): | |
110 file_path = getattr(self, file_attr) | |
111 ordered_replace = [(regex, repl) for regex, repl in replace_data.iteritems()] | |
112 regex = '|'.join(('({})'.format(regex) for regex, dummy in ordered_replace)) | |
113 with open(file_path, 'r') as f: | |
114 buff = f.read() | |
115 buff = re.sub(regex, lambda match_obj: adapter(ordered_replace, match_obj), buff) | |
116 with open(file_path, 'w') as f: | |
117 f.write(buff) | |
118 | |
119 def custom_create_links(self): | |
120 """Create symbolic links to executables""" | |
121 # the script which launch the daemon | |
122 for source, dest in self.sh_script_links: | |
123 if self.root is None: | |
124 if os.path.islink(dest) and os.readlink(dest) != source: | |
125 os.remove(dest) # copy_file doesn't force the link update | |
126 dest_name, copied = copy_file(source, dest, link='sym') | |
127 assert copied | |
128 # we change the perm in the same way as in the original install_scripts | |
129 mode = ((os.stat(dest_name)[ST_MODE]) | 0555) & 07777 | |
130 os.chmod(dest_name, mode) | |
131 else: | |
132 # if root is not None, source probably doesn't exist yet | |
133 # this is not managed by copy_file, so we must use os.symlink directly | |
134 if os.path.islink(dest): | |
135 os.remove(dest) # symlink doesn't force the link update | |
136 os.symlink(source, dest) | |
137 | |
138 def run(self): | |
139 if not self.root: | |
140 ignore_idx = 0 | |
141 else: | |
142 ignore_idx = len(self.root) | |
143 if self.root[-1] == '/': | |
144 ignore_idx-=1 # we dont want to remove the first '/' in _final paths | |
145 # _final suffixed attributes are the ones without the self.root prefix path | |
146 # it's used at least on Arch linux installation as install is made on a local $pkgdir | |
147 # which is later moved to user's FS root | |
148 self.install_lib_final = self.install_lib[ignore_idx:] | |
149 self.sh_script_path = os.path.join(self.install_lib, NAME, 'sat.sh') | |
150 self.sh_script_path_final = os.path.join(self.install_lib_final, NAME, 'sat.sh') | |
151 self.sh_script_links = [(self.sh_script_path_final, os.path.join(self.install_scripts, LAUNCH_DAEMON_COMMAND))] | |
152 self.dbus_service_path = os.path.join(self.install_data, 'share', DBUS_DIR, os.path.basename(DBUS_FILE)) | |
153 sys.stdout.write('running pre installation stuff\n') | |
154 sys.stdout.flush() | |
155 if PURGE_OPT in install_opt: | |
156 self.purge() | |
157 elif CLEAN_OPT in install_opt: | |
158 self.clean() | |
159 install.run(self) | |
160 sys.stdout.write('running post installation stuff\n') | |
161 sys.stdout.flush() | |
162 self.adapt_files() | |
163 self.custom_create_links() | |
164 | |
165 def confirm(self, message): | |
166 """Ask the user for a confirmation""" | |
167 message += 'Proceed' | |
168 while True: | |
169 res = raw_input("%s (y/n)? " % message) | |
170 if res not in ['y', 'Y', 'n', 'N']: | |
171 print "Your response ('%s') was not one of the expected responses: y, n" % res | |
172 message = 'Proceed' | |
173 continue | |
174 if res in ('y', 'Y'): | |
175 return True | |
176 return False | |
177 | |
178 def clean(self, message=None, to_remove=None): | |
179 """Clean previous installation directories | |
180 | |
181 @param message (str): to use a non-default confirmation message | |
182 @param to_remove (str): extra files/directories to remove | |
183 """ | |
184 if message is None: | |
185 message = "Cleaning previous installation directories" | |
186 if to_remove is None: | |
187 to_remove = [] | |
188 for path in [os.path.join(self.install_lib, NAME), | |
189 os.path.join(self.install_lib, "%s_frontends" % NAME), | |
190 os.path.join(self.install_data, 'share', 'doc', NAME), | |
191 os.path.join(self.install_lib, "%s.egg-info" % self.config_vars['dist_fullname']), | |
192 os.path.join(self.install_lib, "%s-py%s.egg-info" % (self.config_vars['dist_fullname'], self.config_vars['py_version_short'])), | |
193 ]: | |
194 if os.path.isdir(path): | |
195 to_remove.append(path) | |
196 for source, dest in self.sh_script_links: | |
197 if os.path.islink(dest): | |
198 to_remove.append(dest) | |
199 | |
200 for script in ('jp', 'primitivus'): | |
201 dest = os.path.join(self.install_scripts, script) | |
202 if os.path.exists(dest): | |
203 to_remove.append(dest) | |
204 | |
205 message = "%s:\n%s\n" % (message, "\n".join([" %s" % path for path in to_remove])) | |
206 if not self.confirm(message): | |
207 return | |
208 sys.stdout.write('cleaning previous installation directories...\n') | |
209 sys.stdout.flush() | |
210 for path in to_remove: | |
211 if os.path.isdir(path): | |
212 shutil.rmtree(path, ignore_errors=True) | |
213 else: | |
214 os.remove(path) | |
215 | |
216 def purge(self): | |
217 """Clean building and previous installation directories""" | |
218 message = "Cleaning building and previous installation directories" | |
219 to_remove = [os.path.join(os.getcwd(), 'build')] | |
220 self.clean(message, to_remove) | |
221 | |
222 | |
223 def preinstall_check(install_opt): | |
224 """Check presence of problematic dependencies, and try to install them with package manager | |
225 This ugly stuff is necessary as distributions are not installed correctly with setuptools/distribute | |
226 Hope to remove this at some point""" | |
227 | |
228 #modules_tocheck = ['twisted', 'twisted.words', 'twisted.web', 'urwid'] | |
229 modules_tocheck = ['gobject'] # XXX: python-gobject is not up-to-date in PyPi | |
230 | |
231 package = {'twisted': 'python-twisted-core', | |
232 'twisted.words': 'python-twisted-words', | |
233 'twisted.web': 'python-twisted-web', | |
234 'urwid': 'python-urwid', | |
235 'gobject': 'python-gobject', | |
236 'mercurial': 'mercurial'} # this dict map dependencies to packages names for debian distributions | |
237 | |
238 sys.stdout.write("Running pre-installation dependencies check\n") | |
239 | |
240 # which modules are not installed ? | |
241 modules_toinstall = [mod for mod in modules_tocheck if not module_installed(mod)] | |
242 """# is mercurial available ? | |
243 hg_installed = subprocess.call('which hg', stdout=open('/dev/null', 'w'), shell=True) == 0 | |
244 if not hg_installed: | |
245 modules_toinstall.append('mercurial')""" # hg can be installed from pypi | |
246 | |
247 if modules_toinstall: | |
248 if AUTO_DEB_OPT in install_opt: # auto debian installation is requested | |
249 # are we on a distribution using apt ? | |
250 apt_path = subprocess.Popen('which apt-get', stdout=subprocess.PIPE, shell=True).communicate()[0][:-1] | |
251 else: | |
252 apt_path = None | |
253 | |
254 not_installed = set() | |
255 if apt_path: | |
256 # we have apt, we'll try to use it | |
257 for module_name in modules_toinstall: | |
258 package_name = package[module_name] | |
259 sys.stdout.write("Installing %s\n" % package_name) | |
260 # TODO: use sudo to get root rights | |
261 success = subprocess.call('%s -qy install %s' % (apt_path, package_name), shell=True) == 0 | |
262 if not success: | |
263 not_installed.add(module_name) | |
264 else: | |
265 not_installed = set(modules_toinstall) | |
266 | |
267 if not_installed: | |
268 # some packages can't be automatically installed, we print their name for manual installation | |
269 sys.stdout.write("You should install the following dependencies with your distribution recommanded tool before installing %s:\n" % NAME) | |
270 for module_name in not_installed: | |
271 sys.stdout.write("- %s (Debian name: %s)\n" % (module_name, package[module_name])) | |
272 sys.exit(2) | |
273 | |
274 | |
275 if sys.argv[1].lower() in ['egg_info', 'install']: | |
276 # we only check dependencies if egg_info or install is used | |
277 install_opt = os.environ.get(ENV_SAT_INSTALL, "").split() | |
278 if not NO_PREINSTALL_OPT in install_opt: # user can force preinstall skipping | |
279 preinstall_check(install_opt) | |
280 | |
281 setup(name=NAME, | 59 setup(name=NAME, |
282 version='0.6.1.1', | 60 version='0.6.1.1', |
283 description=u'Salut à Toi multi-frontend XMPP client', | 61 description=u'Salut à Toi multipurpose and multi frontend XMPP client', |
284 long_description=u'Salut à Toi (SàT) is a XMPP client based on a daemon/frontend architecture. Its multi-frontends (desktop, web, console interface, CLI, etc) and multi-purposes (instant messaging, microblogging, games, file sharing, etc).', | 62 long_description=u'Salut à Toi (SàT) is a XMPP client based on a daemon/frontend architecture. Its multi frontend (desktop, web, console interface, CLI, etc) and multipurpose (instant messaging, microblogging, games, file sharing, etc).', |
285 author='Association « Salut à Toi »', | 63 author='Association « Salut à Toi »', |
286 author_email='contact@goffi.org', | 64 author_email='contact@goffi.org', |
287 url='http://salut-a-toi.org', | 65 url='https://salut-a-toi.org', |
288 classifiers=['Development Status :: 3 - Alpha', | 66 classifiers=['Development Status :: 3 - Alpha', |
289 'Environment :: Console', | 67 'Environment :: Console', |
290 'Environment :: X11 Applications :: GTK', | |
291 'Framework :: Twisted', | 68 'Framework :: Twisted', |
292 'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)', | 69 'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)', |
293 'Operating System :: POSIX :: Linux', | 70 'Operating System :: POSIX :: Linux', |
294 'Topic :: Communications :: Chat'], | 71 'Topic :: Communications :: Chat'], |
295 package_dir={'sat': 'src', 'sat_frontends': 'frontends/src', 'twisted.plugins': 'src/twisted/plugins'}, | 72 packages=find_packages() + ['twisted.plugins'], |
296 packages=['sat', 'sat.tools', 'sat.tools.common', 'sat.bridge', 'sat.plugins', 'sat.test', 'sat.core', 'sat.memory', | |
297 'sat_frontends', 'sat_frontends.bridge', 'sat_frontends.quick_frontend', 'sat_frontends.jp', | |
298 'sat_frontends.primitivus', 'sat_frontends.tools', 'sat.stdui', 'twisted.plugins'], | |
299 package_data={'sat': ['sat.sh'], }, | |
300 data_files=[(os.path.join(sys.prefix, 'share/locale/fr/LC_MESSAGES'), ['i18n/fr/LC_MESSAGES/sat.mo']), | 73 data_files=[(os.path.join(sys.prefix, 'share/locale/fr/LC_MESSAGES'), ['i18n/fr/LC_MESSAGES/sat.mo']), |
301 ('share/doc/%s' % NAME, ['CHANGELOG', 'COPYING', 'INSTALL', 'README', 'README4TRANSLATORS']), | 74 (os.path.join('share/doc', NAME), ['CHANGELOG', 'COPYING', 'INSTALL', 'README', 'README4TRANSLATORS']), |
302 (os.path.join('share', DBUS_DIR), (DBUS_FILE,)), | 75 (os.path.join('share', DBUS_DIR), [DBUS_FILE]), |
303 ], | 76 ], |
304 scripts=['frontends/src/jp/jp', 'frontends/src/primitivus/primitivus', ], | 77 scripts=['sat_frontends/jp/jp', 'sat_frontends/primitivus/primitivus', 'bin/sat'], |
305 zip_safe=False, | 78 zip_safe=False, |
306 install_requires=['twisted >= 15.2.0', 'wokkel >= 0.7.1', 'sat_tmp', 'progressbar', 'urwid >= 1.2.0', 'urwid-satext >= 0.6.1', 'mutagen', 'pillow', 'lxml >= 3.1.0', 'pyxdg', 'markdown', 'html2text', 'pycrypto >= 2.6.1', 'python-potr', 'PyOpenSSL', 'service_identity', 'shortuuid', 'babel', 'pygments'], | 79 install_requires=install_requires, |
307 cmdclass={'install': CustomInstall}, | 80 python_requires='~=2.7', |
308 ) | 81 ) |