1 #!/usr/bin/env python3 |
1 #!/usr/bin/env python3 |
2 # -*- coding: utf-8 -*- |
2 # -*- coding: utf-8 -*- |
3 |
3 |
4 # Copyright (c) 2019 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
4 # Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
5 # |
5 # |
6 |
6 |
7 """ |
7 """ |
8 Module to prepare a distribution package for uploading to PyPI. |
8 Minimum module to allow 'pip' to perform editable installs. |
9 """ |
9 """ |
10 |
10 |
11 import os |
11 from setuptools import setup |
12 import sys |
|
13 import subprocess # secok |
|
14 import shutil |
|
15 import fnmatch |
|
16 import datetime |
|
17 import json |
|
18 import contextlib |
|
19 |
12 |
20 from setuptools import setup, find_packages |
13 setup() |
21 |
|
22 installInfoName = "eric7installpip.json" |
|
23 |
|
24 ###################################################################### |
|
25 ## some helper functions below |
|
26 ###################################################################### |
|
27 |
|
28 |
|
29 def getVersion(): |
|
30 """ |
|
31 Function to get the version from file. |
|
32 |
|
33 @return string containing the version |
|
34 @rtype str |
|
35 """ |
|
36 version = "<unknown>" |
|
37 if sys.argv[-1].startswith(("1", "2")): |
|
38 # assume it is a version info starting with year |
|
39 version = sys.argv[-1] |
|
40 del sys.argv[-1] |
|
41 else: |
|
42 # calculate according our version scheme (year.month) |
|
43 today = datetime.date.today() |
|
44 version = "{0}.{1}".format(today.year - 2000, today.month) |
|
45 return version |
|
46 |
|
47 |
|
48 def getPackageData(package, extensions): |
|
49 """ |
|
50 Function to return data files of a package with given extensions. |
|
51 |
|
52 @param package name of the package directory |
|
53 @type str |
|
54 @param extensions list of extensions to test for |
|
55 @type list of str |
|
56 @return list of package data files |
|
57 @rtype list of str |
|
58 """ |
|
59 filesList = [] |
|
60 for dirpath, _dirnames, filenames in os.walk(package): |
|
61 for fname in filenames: |
|
62 if ( |
|
63 not fname.startswith('.') and |
|
64 os.path.splitext(fname)[1] in extensions |
|
65 ): |
|
66 filesList.append( |
|
67 os.path.relpath(os.path.join(dirpath, fname), package)) |
|
68 return filesList |
|
69 |
|
70 |
|
71 def getDataFiles(): |
|
72 """ |
|
73 Function to return data_files in a platform dependent manner. |
|
74 |
|
75 @return list containing the platform specific data files |
|
76 @rtype list of tuples of (str, list of str) |
|
77 """ |
|
78 if sys.platform.startswith('linux'): |
|
79 dataFiles = [ |
|
80 ('share/applications', [ |
|
81 'linux/eric7.desktop', |
|
82 'linux/eric7_browser.desktop', |
|
83 ]), |
|
84 ('share/icons', [ |
|
85 'eric7/icons/breeze-dark/eric.svg', |
|
86 'eric7/icons/breeze-dark/ericWeb48.svg', |
|
87 'eric7/pixmaps/eric48_icon.png', |
|
88 'eric7/pixmaps/ericWeb48_icon.png' |
|
89 ]), |
|
90 ('share/appdata', ['linux/eric7.appdata.xml']), |
|
91 ('share/metainfo', ['linux/eric7.appdata.xml']), |
|
92 ] |
|
93 elif sys.platform.startswith(("win", "cygwin")): |
|
94 dataFiles = [ |
|
95 ('scripts', [ |
|
96 'eric7/pixmaps/eric7.ico', |
|
97 'eric7/pixmaps/ericWeb48.ico']) |
|
98 ] |
|
99 else: |
|
100 dataFiles = [] |
|
101 return dataFiles |
|
102 |
|
103 |
|
104 def getLongDescription(): |
|
105 """ |
|
106 Function to get the long description via the README file. |
|
107 |
|
108 @return long description |
|
109 @rtype str |
|
110 """ |
|
111 with open(os.path.join(os.path.dirname(__file__), "docs", "README.rst"), |
|
112 "r") as f: |
|
113 longDescription = f.read() |
|
114 |
|
115 if not longDescription: |
|
116 longDescription = ( |
|
117 "eric is an integrated development environment for the Python" |
|
118 " programming language. It uses the PyQt6 bindings and the" |
|
119 " QScintilla2 editor widget. See" |
|
120 " https://eric-ide.python-projects.org for more details." |
|
121 ) |
|
122 |
|
123 return longDescription |
|
124 |
|
125 ###################################################################### |
|
126 ## functions to prepare the sources for building |
|
127 ###################################################################### |
|
128 |
|
129 |
|
130 def prepareInfoFile(fileName, version): |
|
131 """ |
|
132 Function to prepare an Info.py file. |
|
133 |
|
134 @param fileName name of the Python file containing the info (string) |
|
135 @param version version string for the package (string) |
|
136 """ |
|
137 if not fileName: |
|
138 return |
|
139 |
|
140 with contextlib.suppress(OSError): |
|
141 os.rename(fileName, fileName + ".orig") |
|
142 try: |
|
143 hgOut = subprocess.run( # secok |
|
144 ["hg", "identify", "-i"], check=True, |
|
145 capture_output=True, text=True |
|
146 ).stdout |
|
147 except (OSError, subprocess.CalledProcessError): |
|
148 hgOut = "" |
|
149 if hgOut: |
|
150 hgOut = hgOut.strip() |
|
151 if hgOut.endswith("+"): |
|
152 hgOut = hgOut[:-1] |
|
153 with open(fileName + ".orig", "r", encoding="utf-8") as f: |
|
154 text = f.read() |
|
155 text = ( |
|
156 text.replace("@@REVISION@@", hgOut) |
|
157 .replace("@@VERSION@@", version) |
|
158 ) |
|
159 with open(fileName, "w") as f: |
|
160 f.write(text) |
|
161 else: |
|
162 shutil.copy(fileName + ".orig", fileName) |
|
163 |
|
164 |
|
165 def prepareAppdataFile(fileName, version): |
|
166 """ |
|
167 Function to prepare a .appdata.xml file. |
|
168 |
|
169 @param fileName name of the .appdata.xml file (string) |
|
170 @param version version string for the package (string) |
|
171 """ |
|
172 if not fileName: |
|
173 return |
|
174 |
|
175 with contextlib.suppress(OSError): |
|
176 os.rename(fileName, fileName + ".orig") |
|
177 with open(fileName + ".orig", "r", encoding="utf-8") as f: |
|
178 text = f.read() |
|
179 text = ( |
|
180 text.replace("@VERSION@", version) |
|
181 .replace("@DATE@", datetime.date.today().isoformat()) |
|
182 ) |
|
183 with open(fileName, "w") as f: |
|
184 f.write(text) |
|
185 |
|
186 |
|
187 def cleanupSource(dirName): |
|
188 """ |
|
189 Cleanup the sources directory to get rid of leftover files |
|
190 and directories. |
|
191 |
|
192 @param dirName name of the directory to prune (string) |
|
193 """ |
|
194 # step 1: delete all Ui_*.py files without a corresponding |
|
195 # *.ui file |
|
196 dirListing = os.listdir(dirName) |
|
197 for formName, sourceName in [ |
|
198 (f.replace('Ui_', "").replace(".py", ".ui"), f) |
|
199 for f in dirListing if fnmatch.fnmatch(f, "Ui_*.py")]: |
|
200 if not os.path.exists(os.path.join(dirName, formName)): |
|
201 os.remove(os.path.join(dirName, sourceName)) |
|
202 if os.path.exists(os.path.join(dirName, sourceName + "c")): |
|
203 os.remove(os.path.join(dirName, sourceName + "c")) |
|
204 |
|
205 # step 2: delete the __pycache__ directory and all remaining *.pyc files |
|
206 if os.path.exists(os.path.join(dirName, "__pycache__")): |
|
207 shutil.rmtree(os.path.join(dirName, "__pycache__")) |
|
208 for name in [f for f in os.listdir(dirName) |
|
209 if fnmatch.fnmatch(f, "*.pyc")]: |
|
210 os.remove(os.path.join(dirName, name)) |
|
211 |
|
212 # step 3: delete *.orig files |
|
213 for name in [f for f in os.listdir(dirName) |
|
214 if fnmatch.fnmatch(f, "*.orig")]: |
|
215 os.remove(os.path.join(dirName, name)) |
|
216 |
|
217 # step 4: descent into subdirectories and delete them if empty |
|
218 for name in os.listdir(dirName): |
|
219 name = os.path.join(dirName, name) |
|
220 if os.path.isdir(name): |
|
221 cleanupSource(name) |
|
222 if len(os.listdir(name)) == 0: |
|
223 os.rmdir(name) |
|
224 |
|
225 |
|
226 def __pyName(py_dir, py_file): |
|
227 """ |
|
228 Local function to create the Python source file name for the compiled |
|
229 .ui file. |
|
230 |
|
231 @param py_dir suggested name of the directory (string) |
|
232 @param py_file suggested name for the compile source file (string) |
|
233 @return tuple of directory name (string) and source file name (string) |
|
234 """ |
|
235 return py_dir, "Ui_{0}".format(py_file) |
|
236 |
|
237 |
|
238 def compileUiFiles(dirName): |
|
239 """ |
|
240 Compile the .ui files to Python sources. |
|
241 |
|
242 @param dirName name of the directory to compile UI files for (string) |
|
243 """ |
|
244 from PyQt6.uic import compileUiDir |
|
245 compileUiDir(dirName, True, __pyName) |
|
246 |
|
247 |
|
248 def createInstallInfoFile(dirName): |
|
249 """ |
|
250 Create a file containing some rudimentary install information. |
|
251 |
|
252 @param dirName name of the directory to compile UI files for |
|
253 @type str |
|
254 """ |
|
255 global installInfoName |
|
256 |
|
257 installInfo = { |
|
258 "sudo": False, |
|
259 "user": "", |
|
260 "exe": "", |
|
261 "argv": "", |
|
262 "install_cwd": "", |
|
263 "eric": "", |
|
264 "virtualenv": False, |
|
265 "installed": False, |
|
266 "installed_on": "", |
|
267 "guessed": False, |
|
268 "edited": False, |
|
269 "pip": True, |
|
270 "remarks": "", |
|
271 "install_cwd_edited": False, |
|
272 "exe_edited": False, |
|
273 "argv_edited": False, |
|
274 "eric_edited": False, |
|
275 } |
|
276 with open(os.path.join(dirName, installInfoName), "w") as infoFile: |
|
277 json.dump(installInfo, infoFile, indent=2) |
|
278 |
|
279 ###################################################################### |
|
280 ## setup() below |
|
281 ###################################################################### |
|
282 |
|
283 Version = getVersion() |
|
284 sourceDir = os.path.join(os.path.dirname(__file__), "eric7") |
|
285 infoFileName = os.path.join(sourceDir, "UI", "Info.py") |
|
286 appdataFileName = os.path.join(os.path.dirname(__file__), "linux", |
|
287 "eric7.appdata.xml") |
|
288 if sys.argv[1].startswith("bdist"): |
|
289 # prepare the sources under "eric7" for building the wheel file |
|
290 print("preparing the sources...") # __IGNORE_WARNING_M801__ |
|
291 cleanupSource(sourceDir) |
|
292 compileUiFiles(sourceDir) |
|
293 prepareInfoFile(infoFileName, Version) |
|
294 prepareAppdataFile(appdataFileName, Version) |
|
295 createInstallInfoFile(sourceDir) |
|
296 print("Preparation finished") # __IGNORE_WARNING_M801__ |
|
297 |
|
298 setup( |
|
299 name="eric-ide", |
|
300 version=Version, |
|
301 description="eric-ide is an integrated development environment for the" |
|
302 " Python language.", |
|
303 long_description=getLongDescription(), |
|
304 long_description_content_type="text/x-rst", |
|
305 author="Detlev Offenbach", |
|
306 author_email="detlev@die-offenbachs.de", |
|
307 url="https://eric-ide.python-projects.org", |
|
308 project_urls={ |
|
309 "Source Code": "https://hg.die-offenbachs.homelinux.org/eric/", |
|
310 "Issues Tracker": "https://tracker.die-offenbachs.homelinux.org/", |
|
311 "Funding": "https://www.paypal.com/donate/?hosted_button_id=XG3RSPKE3YAJ2", |
|
312 }, |
|
313 platforms=["Linux", "Windows", "macOS"], |
|
314 license="GPLv3+", |
|
315 classifiers=[ |
|
316 "License :: OSI Approved ::" |
|
317 " GNU General Public License v3 or later (GPLv3+)", |
|
318 "Environment :: MacOS X", |
|
319 "Environment :: Win32 (MS Windows)", |
|
320 "Environment :: X11 Applications", |
|
321 "Environment :: X11 Applications :: Qt", |
|
322 "Intended Audience :: Developers", |
|
323 "Intended Audience :: End Users/Desktop", |
|
324 "Natural Language :: English", |
|
325 "Natural Language :: German", |
|
326 "Natural Language :: Russian", |
|
327 "Natural Language :: Spanish", |
|
328 "Operating System :: MacOS :: MacOS X", |
|
329 "Operating System :: Microsoft :: Windows :: Windows 10", |
|
330 "Operating System :: POSIX :: Linux", |
|
331 "Programming Language :: Python", |
|
332 "Programming Language :: Python :: 3.7", |
|
333 "Programming Language :: Python :: 3.8", |
|
334 "Programming Language :: Python :: 3.9", |
|
335 "Programming Language :: Python :: 3.10", |
|
336 "Programming Language :: Python :: 3.11", |
|
337 "Programming Language :: Python :: Implementation :: CPython", |
|
338 "Topic :: Software Development", |
|
339 "Topic :: Text Editors :: Integrated Development Environments (IDE)" |
|
340 ], |
|
341 keywords="Development PyQt6 IDE Python3", |
|
342 python_requires=">=3.7, <3.12", |
|
343 install_requires=[ |
|
344 "pip>=21.1", |
|
345 "wheel", |
|
346 "PyQt6>=6.2.0", |
|
347 "PyQt6-Charts>=6.2.0", |
|
348 "PyQt6-WebEngine>=6.2.0", |
|
349 "PyQt6-QScintilla>=2.13.0", |
|
350 "docutils", |
|
351 "Markdown", |
|
352 "pyyaml", |
|
353 "tomlkit", |
|
354 "chardet", |
|
355 "asttokens", |
|
356 "EditorConfig", |
|
357 "Send2Trash", |
|
358 "Pygments", |
|
359 "parso", |
|
360 "jedi", |
|
361 "packaging", |
|
362 "pipdeptree", |
|
363 "cyclonedx-python-lib", |
|
364 "cyclonedx-bom", |
|
365 "trove-classifiers", |
|
366 "pywin32>=1.0;platform_system=='Windows'", |
|
367 ], |
|
368 data_files=getDataFiles(), |
|
369 packages=find_packages(), |
|
370 zip_safe=False, |
|
371 package_data={ |
|
372 "": getPackageData( |
|
373 "eric7", |
|
374 [".png", ".svg", ".svgz", ".xpm", ".ico", ".gif", ".icns", ".txt", |
|
375 ".md", ".rst", ".tmpl", ".html", ".qch", ".css", ".scss", ".qss", |
|
376 ".ehj", ".ethj", ".api", ".bas", ".dat", ".xbel", ".xml", ".js"] |
|
377 ) + ["i18n/eric7_de.qm", "i18n/eric7_en.qm", "i18n/eric7_es.qm", |
|
378 "i18n/eric7_ru.qm", |
|
379 installInfoName, |
|
380 ] |
|
381 }, |
|
382 entry_points={ |
|
383 "gui_scripts": [ |
|
384 "eric7 = eric7.eric7:main", |
|
385 "eric7_browser = eric7.eric7_browser:main", |
|
386 "eric7_compare = eric7.eric7_compare:main", |
|
387 "eric7_configure = eric7.eric7_configure:main", |
|
388 "eric7_diff = eric7.eric7_diff:main", |
|
389 "eric7_editor = eric7.eric7_editor:main", |
|
390 "eric7_hexeditor = eric7.eric7_hexeditor:main", |
|
391 "eric7_iconeditor = eric7.eric7_iconeditor:main", |
|
392 "eric7_plugininstall = eric7.eric7_plugininstall:main", |
|
393 "eric7_pluginrepository = eric7.eric7_pluginrepository:main", |
|
394 "eric7_pluginuninstall = eric7.eric7_pluginuninstall:main", |
|
395 "eric7_qregularexpression = eric7.eric7_qregularexpression:main", |
|
396 "eric7_re = eric7.eric7_re:main", |
|
397 "eric7_shell = eric7.eric7_shell:main", |
|
398 "eric7_snap = eric7.eric7_snap:main", |
|
399 "eric7_sqlbrowser = eric7.eric7_sqlbrowser:main", |
|
400 "eric7_testing = eric7.eric7_testing:main", |
|
401 "eric7_tray = eric7.eric7_tray:main", |
|
402 "eric7_trpreviewer = eric7.eric7_trpreviewer:main", |
|
403 "eric7_uipreviewer = eric7.eric7_uipreviewer:main", |
|
404 "eric7_virtualenv = eric7.eric7_virtualenv:main", |
|
405 ], |
|
406 "console_scripts": [ |
|
407 "eric7_api = eric7.eric7_api:main", |
|
408 "eric7_doc = eric7.eric7_doc:main", |
|
409 "eric7_post_install = eric7.eric7_post_install:main", |
|
410 ], |
|
411 }, |
|
412 ) |
|
413 |
|
414 # cleanup |
|
415 for fileName in [infoFileName, appdataFileName]: |
|
416 if os.path.exists(fileName + ".orig"): |
|
417 with contextlib.suppress(OSError): |
|
418 os.remove(fileName) |
|
419 os.rename(fileName + ".orig", fileName) |
|