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