mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	initial import of the packaging package in the standard library
This commit is contained in:
		
							parent
							
								
									566f8a646e
								
							
						
					
					
						commit
						1231a4e097
					
				
					 193 changed files with 30376 additions and 149 deletions
				
			
		
							
								
								
									
										17
									
								
								Lib/packaging/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								Lib/packaging/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| """Support for packaging, distribution and installation of Python projects. | ||||
| 
 | ||||
| Third-party tools can use parts of packaging as building blocks | ||||
| without causing the other modules to be imported: | ||||
| 
 | ||||
|     import packaging.version | ||||
|     import packaging.metadata | ||||
|     import packaging.pypi.simple | ||||
|     import packaging.tests.pypi_server | ||||
| """ | ||||
| 
 | ||||
| from logging import getLogger | ||||
| 
 | ||||
| __all__ = ['__version__', 'logger'] | ||||
| 
 | ||||
| __version__ = "1.0a3" | ||||
| logger = getLogger('packaging') | ||||
							
								
								
									
										552
									
								
								Lib/packaging/_trove.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										552
									
								
								Lib/packaging/_trove.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,552 @@ | |||
| """Temporary helper for create.""" | ||||
| 
 | ||||
| # XXX get the list from PyPI and cache it instead of hardcoding | ||||
| 
 | ||||
| # XXX see if it would be more useful to store it as another structure | ||||
| # than a list of strings | ||||
| 
 | ||||
| all_classifiers = [ | ||||
| 'Development Status :: 1 - Planning', | ||||
| 'Development Status :: 2 - Pre-Alpha', | ||||
| 'Development Status :: 3 - Alpha', | ||||
| 'Development Status :: 4 - Beta', | ||||
| 'Development Status :: 5 - Production/Stable', | ||||
| 'Development Status :: 6 - Mature', | ||||
| 'Development Status :: 7 - Inactive', | ||||
| 'Environment :: Console', | ||||
| 'Environment :: Console :: Curses', | ||||
| 'Environment :: Console :: Framebuffer', | ||||
| 'Environment :: Console :: Newt', | ||||
| 'Environment :: Console :: svgalib', | ||||
| "Environment :: Handhelds/PDA's", | ||||
| 'Environment :: MacOS X', | ||||
| 'Environment :: MacOS X :: Aqua', | ||||
| 'Environment :: MacOS X :: Carbon', | ||||
| 'Environment :: MacOS X :: Cocoa', | ||||
| 'Environment :: No Input/Output (Daemon)', | ||||
| 'Environment :: Other Environment', | ||||
| 'Environment :: Plugins', | ||||
| 'Environment :: Web Environment', | ||||
| 'Environment :: Web Environment :: Buffet', | ||||
| 'Environment :: Web Environment :: Mozilla', | ||||
| 'Environment :: Web Environment :: ToscaWidgets', | ||||
| 'Environment :: Win32 (MS Windows)', | ||||
| 'Environment :: X11 Applications', | ||||
| 'Environment :: X11 Applications :: Gnome', | ||||
| 'Environment :: X11 Applications :: GTK', | ||||
| 'Environment :: X11 Applications :: KDE', | ||||
| 'Environment :: X11 Applications :: Qt', | ||||
| 'Framework :: BFG', | ||||
| 'Framework :: Buildout', | ||||
| 'Framework :: Chandler', | ||||
| 'Framework :: CubicWeb', | ||||
| 'Framework :: Django', | ||||
| 'Framework :: IDLE', | ||||
| 'Framework :: Paste', | ||||
| 'Framework :: Plone', | ||||
| 'Framework :: Pylons', | ||||
| 'Framework :: Setuptools Plugin', | ||||
| 'Framework :: Trac', | ||||
| 'Framework :: TurboGears', | ||||
| 'Framework :: TurboGears :: Applications', | ||||
| 'Framework :: TurboGears :: Widgets', | ||||
| 'Framework :: Twisted', | ||||
| 'Framework :: ZODB', | ||||
| 'Framework :: Zope2', | ||||
| 'Framework :: Zope3', | ||||
| 'Intended Audience :: Customer Service', | ||||
| 'Intended Audience :: Developers', | ||||
| 'Intended Audience :: Education', | ||||
| 'Intended Audience :: End Users/Desktop', | ||||
| 'Intended Audience :: Financial and Insurance Industry', | ||||
| 'Intended Audience :: Healthcare Industry', | ||||
| 'Intended Audience :: Information Technology', | ||||
| 'Intended Audience :: Legal Industry', | ||||
| 'Intended Audience :: Manufacturing', | ||||
| 'Intended Audience :: Other Audience', | ||||
| 'Intended Audience :: Religion', | ||||
| 'Intended Audience :: Science/Research', | ||||
| 'Intended Audience :: System Administrators', | ||||
| 'Intended Audience :: Telecommunications Industry', | ||||
| 'License :: Aladdin Free Public License (AFPL)', | ||||
| 'License :: DFSG approved', | ||||
| 'License :: Eiffel Forum License (EFL)', | ||||
| 'License :: Free For Educational Use', | ||||
| 'License :: Free For Home Use', | ||||
| 'License :: Free for non-commercial use', | ||||
| 'License :: Freely Distributable', | ||||
| 'License :: Free To Use But Restricted', | ||||
| 'License :: Freeware', | ||||
| 'License :: Netscape Public License (NPL)', | ||||
| 'License :: Nokia Open Source License (NOKOS)', | ||||
| 'License :: OSI Approved', | ||||
| 'License :: OSI Approved :: Academic Free License (AFL)', | ||||
| 'License :: OSI Approved :: Apache Software License', | ||||
| 'License :: OSI Approved :: Apple Public Source License', | ||||
| 'License :: OSI Approved :: Artistic License', | ||||
| 'License :: OSI Approved :: Attribution Assurance License', | ||||
| 'License :: OSI Approved :: BSD License', | ||||
| 'License :: OSI Approved :: Common Public License', | ||||
| 'License :: OSI Approved :: Eiffel Forum License', | ||||
| 'License :: OSI Approved :: European Union Public Licence 1.0 (EUPL 1.0)', | ||||
| 'License :: OSI Approved :: European Union Public Licence 1.1 (EUPL 1.1)', | ||||
| 'License :: OSI Approved :: GNU Affero General Public License v3', | ||||
| 'License :: OSI Approved :: GNU Free Documentation License (FDL)', | ||||
| 'License :: OSI Approved :: GNU General Public License (GPL)', | ||||
| 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', | ||||
| 'License :: OSI Approved :: IBM Public License', | ||||
| 'License :: OSI Approved :: Intel Open Source License', | ||||
| 'License :: OSI Approved :: ISC License (ISCL)', | ||||
| 'License :: OSI Approved :: Jabber Open Source License', | ||||
| 'License :: OSI Approved :: MIT License', | ||||
| 'License :: OSI Approved :: MITRE Collaborative Virtual Workspace License (CVW)', | ||||
| 'License :: OSI Approved :: Motosoto License', | ||||
| 'License :: OSI Approved :: Mozilla Public License 1.0 (MPL)', | ||||
| 'License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)', | ||||
| 'License :: OSI Approved :: Nethack General Public License', | ||||
| 'License :: OSI Approved :: Nokia Open Source License', | ||||
| 'License :: OSI Approved :: Open Group Test Suite License', | ||||
| 'License :: OSI Approved :: Python License (CNRI Python License)', | ||||
| 'License :: OSI Approved :: Python Software Foundation License', | ||||
| 'License :: OSI Approved :: Qt Public License (QPL)', | ||||
| 'License :: OSI Approved :: Ricoh Source Code Public License', | ||||
| 'License :: OSI Approved :: Sleepycat License', | ||||
| 'License :: OSI Approved :: Sun Industry Standards Source License (SISSL)', | ||||
| 'License :: OSI Approved :: Sun Public License', | ||||
| 'License :: OSI Approved :: University of Illinois/NCSA Open Source License', | ||||
| 'License :: OSI Approved :: Vovida Software License 1.0', | ||||
| 'License :: OSI Approved :: W3C License', | ||||
| 'License :: OSI Approved :: X.Net License', | ||||
| 'License :: OSI Approved :: zlib/libpng License', | ||||
| 'License :: OSI Approved :: Zope Public License', | ||||
| 'License :: Other/Proprietary License', | ||||
| 'License :: Public Domain', | ||||
| 'License :: Repoze Public License', | ||||
| 'Natural Language :: Afrikaans', | ||||
| 'Natural Language :: Arabic', | ||||
| 'Natural Language :: Bengali', | ||||
| 'Natural Language :: Bosnian', | ||||
| 'Natural Language :: Bulgarian', | ||||
| 'Natural Language :: Catalan', | ||||
| 'Natural Language :: Chinese (Simplified)', | ||||
| 'Natural Language :: Chinese (Traditional)', | ||||
| 'Natural Language :: Croatian', | ||||
| 'Natural Language :: Czech', | ||||
| 'Natural Language :: Danish', | ||||
| 'Natural Language :: Dutch', | ||||
| 'Natural Language :: English', | ||||
| 'Natural Language :: Esperanto', | ||||
| 'Natural Language :: Finnish', | ||||
| 'Natural Language :: French', | ||||
| 'Natural Language :: German', | ||||
| 'Natural Language :: Greek', | ||||
| 'Natural Language :: Hebrew', | ||||
| 'Natural Language :: Hindi', | ||||
| 'Natural Language :: Hungarian', | ||||
| 'Natural Language :: Icelandic', | ||||
| 'Natural Language :: Indonesian', | ||||
| 'Natural Language :: Italian', | ||||
| 'Natural Language :: Japanese', | ||||
| 'Natural Language :: Javanese', | ||||
| 'Natural Language :: Korean', | ||||
| 'Natural Language :: Latin', | ||||
| 'Natural Language :: Latvian', | ||||
| 'Natural Language :: Macedonian', | ||||
| 'Natural Language :: Malay', | ||||
| 'Natural Language :: Marathi', | ||||
| 'Natural Language :: Norwegian', | ||||
| 'Natural Language :: Panjabi', | ||||
| 'Natural Language :: Persian', | ||||
| 'Natural Language :: Polish', | ||||
| 'Natural Language :: Portuguese', | ||||
| 'Natural Language :: Portuguese (Brazilian)', | ||||
| 'Natural Language :: Romanian', | ||||
| 'Natural Language :: Russian', | ||||
| 'Natural Language :: Serbian', | ||||
| 'Natural Language :: Slovak', | ||||
| 'Natural Language :: Slovenian', | ||||
| 'Natural Language :: Spanish', | ||||
| 'Natural Language :: Swedish', | ||||
| 'Natural Language :: Tamil', | ||||
| 'Natural Language :: Telugu', | ||||
| 'Natural Language :: Thai', | ||||
| 'Natural Language :: Turkish', | ||||
| 'Natural Language :: Ukranian', | ||||
| 'Natural Language :: Urdu', | ||||
| 'Natural Language :: Vietnamese', | ||||
| 'Operating System :: BeOS', | ||||
| 'Operating System :: MacOS', | ||||
| 'Operating System :: MacOS :: MacOS 9', | ||||
| 'Operating System :: MacOS :: MacOS X', | ||||
| 'Operating System :: Microsoft', | ||||
| 'Operating System :: Microsoft :: MS-DOS', | ||||
| 'Operating System :: Microsoft :: Windows', | ||||
| 'Operating System :: Microsoft :: Windows :: Windows 3.1 or Earlier', | ||||
| 'Operating System :: Microsoft :: Windows :: Windows 95/98/2000', | ||||
| 'Operating System :: Microsoft :: Windows :: Windows CE', | ||||
| 'Operating System :: Microsoft :: Windows :: Windows NT/2000', | ||||
| 'Operating System :: OS/2', | ||||
| 'Operating System :: OS Independent', | ||||
| 'Operating System :: Other OS', | ||||
| 'Operating System :: PalmOS', | ||||
| 'Operating System :: PDA Systems', | ||||
| 'Operating System :: POSIX', | ||||
| 'Operating System :: POSIX :: AIX', | ||||
| 'Operating System :: POSIX :: BSD', | ||||
| 'Operating System :: POSIX :: BSD :: BSD/OS', | ||||
| 'Operating System :: POSIX :: BSD :: FreeBSD', | ||||
| 'Operating System :: POSIX :: BSD :: NetBSD', | ||||
| 'Operating System :: POSIX :: BSD :: OpenBSD', | ||||
| 'Operating System :: POSIX :: GNU Hurd', | ||||
| 'Operating System :: POSIX :: HP-UX', | ||||
| 'Operating System :: POSIX :: IRIX', | ||||
| 'Operating System :: POSIX :: Linux', | ||||
| 'Operating System :: POSIX :: Other', | ||||
| 'Operating System :: POSIX :: SCO', | ||||
| 'Operating System :: POSIX :: SunOS/Solaris', | ||||
| 'Operating System :: Unix', | ||||
| 'Programming Language :: Ada', | ||||
| 'Programming Language :: APL', | ||||
| 'Programming Language :: ASP', | ||||
| 'Programming Language :: Assembly', | ||||
| 'Programming Language :: Awk', | ||||
| 'Programming Language :: Basic', | ||||
| 'Programming Language :: C', | ||||
| 'Programming Language :: C#', | ||||
| 'Programming Language :: C++', | ||||
| 'Programming Language :: Cold Fusion', | ||||
| 'Programming Language :: Cython', | ||||
| 'Programming Language :: Delphi/Kylix', | ||||
| 'Programming Language :: Dylan', | ||||
| 'Programming Language :: Eiffel', | ||||
| 'Programming Language :: Emacs-Lisp', | ||||
| 'Programming Language :: Erlang', | ||||
| 'Programming Language :: Euler', | ||||
| 'Programming Language :: Euphoria', | ||||
| 'Programming Language :: Forth', | ||||
| 'Programming Language :: Fortran', | ||||
| 'Programming Language :: Haskell', | ||||
| 'Programming Language :: Java', | ||||
| 'Programming Language :: JavaScript', | ||||
| 'Programming Language :: Lisp', | ||||
| 'Programming Language :: Logo', | ||||
| 'Programming Language :: ML', | ||||
| 'Programming Language :: Modula', | ||||
| 'Programming Language :: Objective C', | ||||
| 'Programming Language :: Object Pascal', | ||||
| 'Programming Language :: OCaml', | ||||
| 'Programming Language :: Other', | ||||
| 'Programming Language :: Other Scripting Engines', | ||||
| 'Programming Language :: Pascal', | ||||
| 'Programming Language :: Perl', | ||||
| 'Programming Language :: PHP', | ||||
| 'Programming Language :: Pike', | ||||
| 'Programming Language :: Pliant', | ||||
| 'Programming Language :: PL/SQL', | ||||
| 'Programming Language :: PROGRESS', | ||||
| 'Programming Language :: Prolog', | ||||
| 'Programming Language :: Python', | ||||
| 'Programming Language :: Python :: 2', | ||||
| 'Programming Language :: Python :: 2.3', | ||||
| 'Programming Language :: Python :: 2.4', | ||||
| 'Programming Language :: Python :: 2.5', | ||||
| 'Programming Language :: Python :: 2.6', | ||||
| 'Programming Language :: Python :: 2.7', | ||||
| 'Programming Language :: Python :: 3', | ||||
| 'Programming Language :: Python :: 3.0', | ||||
| 'Programming Language :: Python :: 3.1', | ||||
| 'Programming Language :: Python :: 3.2', | ||||
| 'Programming Language :: REBOL', | ||||
| 'Programming Language :: Rexx', | ||||
| 'Programming Language :: Ruby', | ||||
| 'Programming Language :: Scheme', | ||||
| 'Programming Language :: Simula', | ||||
| 'Programming Language :: Smalltalk', | ||||
| 'Programming Language :: SQL', | ||||
| 'Programming Language :: Tcl', | ||||
| 'Programming Language :: Unix Shell', | ||||
| 'Programming Language :: Visual Basic', | ||||
| 'Programming Language :: XBasic', | ||||
| 'Programming Language :: YACC', | ||||
| 'Programming Language :: Zope', | ||||
| 'Topic :: Adaptive Technologies', | ||||
| 'Topic :: Artistic Software', | ||||
| 'Topic :: Communications', | ||||
| 'Topic :: Communications :: BBS', | ||||
| 'Topic :: Communications :: Chat', | ||||
| 'Topic :: Communications :: Chat :: AOL Instant Messenger', | ||||
| 'Topic :: Communications :: Chat :: ICQ', | ||||
| 'Topic :: Communications :: Chat :: Internet Relay Chat', | ||||
| 'Topic :: Communications :: Chat :: Unix Talk', | ||||
| 'Topic :: Communications :: Conferencing', | ||||
| 'Topic :: Communications :: Email', | ||||
| 'Topic :: Communications :: Email :: Address Book', | ||||
| 'Topic :: Communications :: Email :: Email Clients (MUA)', | ||||
| 'Topic :: Communications :: Email :: Filters', | ||||
| 'Topic :: Communications :: Email :: Mailing List Servers', | ||||
| 'Topic :: Communications :: Email :: Mail Transport Agents', | ||||
| 'Topic :: Communications :: Email :: Post-Office', | ||||
| 'Topic :: Communications :: Email :: Post-Office :: IMAP', | ||||
| 'Topic :: Communications :: Email :: Post-Office :: POP3', | ||||
| 'Topic :: Communications :: Fax', | ||||
| 'Topic :: Communications :: FIDO', | ||||
| 'Topic :: Communications :: File Sharing', | ||||
| 'Topic :: Communications :: File Sharing :: Gnutella', | ||||
| 'Topic :: Communications :: File Sharing :: Napster', | ||||
| 'Topic :: Communications :: Ham Radio', | ||||
| 'Topic :: Communications :: Internet Phone', | ||||
| 'Topic :: Communications :: Telephony', | ||||
| 'Topic :: Communications :: Usenet News', | ||||
| 'Topic :: Database', | ||||
| 'Topic :: Database :: Database Engines/Servers', | ||||
| 'Topic :: Database :: Front-Ends', | ||||
| 'Topic :: Desktop Environment', | ||||
| 'Topic :: Desktop Environment :: File Managers', | ||||
| 'Topic :: Desktop Environment :: Gnome', | ||||
| 'Topic :: Desktop Environment :: GNUstep', | ||||
| 'Topic :: Desktop Environment :: K Desktop Environment (KDE)', | ||||
| 'Topic :: Desktop Environment :: K Desktop Environment (KDE) :: Themes', | ||||
| 'Topic :: Desktop Environment :: PicoGUI', | ||||
| 'Topic :: Desktop Environment :: PicoGUI :: Applications', | ||||
| 'Topic :: Desktop Environment :: PicoGUI :: Themes', | ||||
| 'Topic :: Desktop Environment :: Screen Savers', | ||||
| 'Topic :: Desktop Environment :: Window Managers', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Afterstep', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Afterstep :: Themes', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Applets', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Blackbox', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Blackbox :: Themes', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: CTWM', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: CTWM :: Themes', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Enlightenment', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Epplets', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR15', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR16', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR17', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Fluxbox', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Fluxbox :: Themes', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: FVWM', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: FVWM :: Themes', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: IceWM', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: IceWM :: Themes', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: MetaCity', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: MetaCity :: Themes', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Oroborus', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Oroborus :: Themes', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Sawfish', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Sawfish :: Themes 0.30', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Sawfish :: Themes pre-0.30', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Waimea', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Waimea :: Themes', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Window Maker', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Window Maker :: Applets', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: Window Maker :: Themes', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: XFCE', | ||||
| 'Topic :: Desktop Environment :: Window Managers :: XFCE :: Themes', | ||||
| 'Topic :: Documentation', | ||||
| 'Topic :: Education', | ||||
| 'Topic :: Education :: Computer Aided Instruction (CAI)', | ||||
| 'Topic :: Education :: Testing', | ||||
| 'Topic :: Games/Entertainment', | ||||
| 'Topic :: Games/Entertainment :: Arcade', | ||||
| 'Topic :: Games/Entertainment :: Board Games', | ||||
| 'Topic :: Games/Entertainment :: First Person Shooters', | ||||
| 'Topic :: Games/Entertainment :: Fortune Cookies', | ||||
| 'Topic :: Games/Entertainment :: Multi-User Dungeons (MUD)', | ||||
| 'Topic :: Games/Entertainment :: Puzzle Games', | ||||
| 'Topic :: Games/Entertainment :: Real Time Strategy', | ||||
| 'Topic :: Games/Entertainment :: Role-Playing', | ||||
| 'Topic :: Games/Entertainment :: Side-Scrolling/Arcade Games', | ||||
| 'Topic :: Games/Entertainment :: Simulation', | ||||
| 'Topic :: Games/Entertainment :: Turn Based Strategy', | ||||
| 'Topic :: Home Automation', | ||||
| 'Topic :: Internet', | ||||
| 'Topic :: Internet :: File Transfer Protocol (FTP)', | ||||
| 'Topic :: Internet :: Finger', | ||||
| 'Topic :: Internet :: Log Analysis', | ||||
| 'Topic :: Internet :: Name Service (DNS)', | ||||
| 'Topic :: Internet :: Proxy Servers', | ||||
| 'Topic :: Internet :: WAP', | ||||
| 'Topic :: Internet :: WWW/HTTP', | ||||
| 'Topic :: Internet :: WWW/HTTP :: Browsers', | ||||
| 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', | ||||
| 'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries', | ||||
| 'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Message Boards', | ||||
| 'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: News/Diary', | ||||
| 'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Page Counters', | ||||
| 'Topic :: Internet :: WWW/HTTP :: HTTP Servers', | ||||
| 'Topic :: Internet :: WWW/HTTP :: Indexing/Search', | ||||
| 'Topic :: Internet :: WWW/HTTP :: Site Management', | ||||
| 'Topic :: Internet :: WWW/HTTP :: Site Management :: Link Checking', | ||||
| 'Topic :: Internet :: WWW/HTTP :: WSGI', | ||||
| 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', | ||||
| 'Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware', | ||||
| 'Topic :: Internet :: WWW/HTTP :: WSGI :: Server', | ||||
| 'Topic :: Internet :: Z39.50', | ||||
| 'Topic :: Multimedia', | ||||
| 'Topic :: Multimedia :: Graphics', | ||||
| 'Topic :: Multimedia :: Graphics :: 3D Modeling', | ||||
| 'Topic :: Multimedia :: Graphics :: 3D Rendering', | ||||
| 'Topic :: Multimedia :: Graphics :: Capture', | ||||
| 'Topic :: Multimedia :: Graphics :: Capture :: Digital Camera', | ||||
| 'Topic :: Multimedia :: Graphics :: Capture :: Scanners', | ||||
| 'Topic :: Multimedia :: Graphics :: Capture :: Screen Capture', | ||||
| 'Topic :: Multimedia :: Graphics :: Editors', | ||||
| 'Topic :: Multimedia :: Graphics :: Editors :: Raster-Based', | ||||
| 'Topic :: Multimedia :: Graphics :: Editors :: Vector-Based', | ||||
| 'Topic :: Multimedia :: Graphics :: Graphics Conversion', | ||||
| 'Topic :: Multimedia :: Graphics :: Presentation', | ||||
| 'Topic :: Multimedia :: Graphics :: Viewers', | ||||
| 'Topic :: Multimedia :: Sound/Audio', | ||||
| 'Topic :: Multimedia :: Sound/Audio :: Analysis', | ||||
| 'Topic :: Multimedia :: Sound/Audio :: Capture/Recording', | ||||
| 'Topic :: Multimedia :: Sound/Audio :: CD Audio', | ||||
| 'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Playing', | ||||
| 'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Ripping', | ||||
| 'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Writing', | ||||
| 'Topic :: Multimedia :: Sound/Audio :: Conversion', | ||||
| 'Topic :: Multimedia :: Sound/Audio :: Editors', | ||||
| 'Topic :: Multimedia :: Sound/Audio :: MIDI', | ||||
| 'Topic :: Multimedia :: Sound/Audio :: Mixers', | ||||
| 'Topic :: Multimedia :: Sound/Audio :: Players', | ||||
| 'Topic :: Multimedia :: Sound/Audio :: Players :: MP3', | ||||
| 'Topic :: Multimedia :: Sound/Audio :: Sound Synthesis', | ||||
| 'Topic :: Multimedia :: Sound/Audio :: Speech', | ||||
| 'Topic :: Multimedia :: Video', | ||||
| 'Topic :: Multimedia :: Video :: Capture', | ||||
| 'Topic :: Multimedia :: Video :: Conversion', | ||||
| 'Topic :: Multimedia :: Video :: Display', | ||||
| 'Topic :: Multimedia :: Video :: Non-Linear Editor', | ||||
| 'Topic :: Office/Business', | ||||
| 'Topic :: Office/Business :: Financial', | ||||
| 'Topic :: Office/Business :: Financial :: Accounting', | ||||
| 'Topic :: Office/Business :: Financial :: Investment', | ||||
| 'Topic :: Office/Business :: Financial :: Point-Of-Sale', | ||||
| 'Topic :: Office/Business :: Financial :: Spreadsheet', | ||||
| 'Topic :: Office/Business :: Groupware', | ||||
| 'Topic :: Office/Business :: News/Diary', | ||||
| 'Topic :: Office/Business :: Office Suites', | ||||
| 'Topic :: Office/Business :: Scheduling', | ||||
| 'Topic :: Other/Nonlisted Topic', | ||||
| 'Topic :: Printing', | ||||
| 'Topic :: Religion', | ||||
| 'Topic :: Scientific/Engineering', | ||||
| 'Topic :: Scientific/Engineering :: Artificial Intelligence', | ||||
| 'Topic :: Scientific/Engineering :: Astronomy', | ||||
| 'Topic :: Scientific/Engineering :: Atmospheric Science', | ||||
| 'Topic :: Scientific/Engineering :: Bio-Informatics', | ||||
| 'Topic :: Scientific/Engineering :: Chemistry', | ||||
| 'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)', | ||||
| 'Topic :: Scientific/Engineering :: GIS', | ||||
| 'Topic :: Scientific/Engineering :: Human Machine Interfaces', | ||||
| 'Topic :: Scientific/Engineering :: Image Recognition', | ||||
| 'Topic :: Scientific/Engineering :: Information Analysis', | ||||
| 'Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator', | ||||
| 'Topic :: Scientific/Engineering :: Mathematics', | ||||
| 'Topic :: Scientific/Engineering :: Medical Science Apps.', | ||||
| 'Topic :: Scientific/Engineering :: Physics', | ||||
| 'Topic :: Scientific/Engineering :: Visualization', | ||||
| 'Topic :: Security', | ||||
| 'Topic :: Security :: Cryptography', | ||||
| 'Topic :: Sociology', | ||||
| 'Topic :: Sociology :: Genealogy', | ||||
| 'Topic :: Sociology :: History', | ||||
| 'Topic :: Software Development', | ||||
| 'Topic :: Software Development :: Assemblers', | ||||
| 'Topic :: Software Development :: Bug Tracking', | ||||
| 'Topic :: Software Development :: Build Tools', | ||||
| 'Topic :: Software Development :: Code Generators', | ||||
| 'Topic :: Software Development :: Compilers', | ||||
| 'Topic :: Software Development :: Debuggers', | ||||
| 'Topic :: Software Development :: Disassemblers', | ||||
| 'Topic :: Software Development :: Documentation', | ||||
| 'Topic :: Software Development :: Embedded Systems', | ||||
| 'Topic :: Software Development :: Internationalization', | ||||
| 'Topic :: Software Development :: Interpreters', | ||||
| 'Topic :: Software Development :: Libraries', | ||||
| 'Topic :: Software Development :: Libraries :: Application Frameworks', | ||||
| 'Topic :: Software Development :: Libraries :: Java Libraries', | ||||
| 'Topic :: Software Development :: Libraries :: Perl Modules', | ||||
| 'Topic :: Software Development :: Libraries :: PHP Classes', | ||||
| 'Topic :: Software Development :: Libraries :: Pike Modules', | ||||
| 'Topic :: Software Development :: Libraries :: pygame', | ||||
| 'Topic :: Software Development :: Libraries :: Python Modules', | ||||
| 'Topic :: Software Development :: Libraries :: Ruby Modules', | ||||
| 'Topic :: Software Development :: Libraries :: Tcl Extensions', | ||||
| 'Topic :: Software Development :: Localization', | ||||
| 'Topic :: Software Development :: Object Brokering', | ||||
| 'Topic :: Software Development :: Object Brokering :: CORBA', | ||||
| 'Topic :: Software Development :: Pre-processors', | ||||
| 'Topic :: Software Development :: Quality Assurance', | ||||
| 'Topic :: Software Development :: Testing', | ||||
| 'Topic :: Software Development :: Testing :: Traffic Generation', | ||||
| 'Topic :: Software Development :: User Interfaces', | ||||
| 'Topic :: Software Development :: Version Control', | ||||
| 'Topic :: Software Development :: Version Control :: CVS', | ||||
| 'Topic :: Software Development :: Version Control :: RCS', | ||||
| 'Topic :: Software Development :: Version Control :: SCCS', | ||||
| 'Topic :: Software Development :: Widget Sets', | ||||
| 'Topic :: System', | ||||
| 'Topic :: System :: Archiving', | ||||
| 'Topic :: System :: Archiving :: Backup', | ||||
| 'Topic :: System :: Archiving :: Compression', | ||||
| 'Topic :: System :: Archiving :: Mirroring', | ||||
| 'Topic :: System :: Archiving :: Packaging', | ||||
| 'Topic :: System :: Benchmark', | ||||
| 'Topic :: System :: Boot', | ||||
| 'Topic :: System :: Boot :: Init', | ||||
| 'Topic :: System :: Clustering', | ||||
| 'Topic :: System :: Console Fonts', | ||||
| 'Topic :: System :: Distributed Computing', | ||||
| 'Topic :: System :: Emulators', | ||||
| 'Topic :: System :: Filesystems', | ||||
| 'Topic :: System :: Hardware', | ||||
| 'Topic :: System :: Hardware :: Hardware Drivers', | ||||
| 'Topic :: System :: Hardware :: Mainframes', | ||||
| 'Topic :: System :: Hardware :: Symmetric Multi-processing', | ||||
| 'Topic :: System :: Installation/Setup', | ||||
| 'Topic :: System :: Logging', | ||||
| 'Topic :: System :: Monitoring', | ||||
| 'Topic :: System :: Networking', | ||||
| 'Topic :: System :: Networking :: Firewalls', | ||||
| 'Topic :: System :: Networking :: Monitoring', | ||||
| 'Topic :: System :: Networking :: Monitoring :: Hardware Watchdog', | ||||
| 'Topic :: System :: Networking :: Time Synchronization', | ||||
| 'Topic :: System :: Operating System', | ||||
| 'Topic :: System :: Operating System Kernels', | ||||
| 'Topic :: System :: Operating System Kernels :: BSD', | ||||
| 'Topic :: System :: Operating System Kernels :: GNU Hurd', | ||||
| 'Topic :: System :: Operating System Kernels :: Linux', | ||||
| 'Topic :: System :: Power (UPS)', | ||||
| 'Topic :: System :: Recovery Tools', | ||||
| 'Topic :: System :: Shells', | ||||
| 'Topic :: System :: Software Distribution', | ||||
| 'Topic :: System :: Systems Administration', | ||||
| 'Topic :: System :: Systems Administration :: Authentication/Directory', | ||||
| 'Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP', | ||||
| 'Topic :: System :: Systems Administration :: Authentication/Directory :: NIS', | ||||
| 'Topic :: System :: System Shells', | ||||
| 'Topic :: Terminals', | ||||
| 'Topic :: Terminals :: Serial', | ||||
| 'Topic :: Terminals :: Telnet', | ||||
| 'Topic :: Terminals :: Terminal Emulators/X Terminals', | ||||
| 'Topic :: Text Editors', | ||||
| 'Topic :: Text Editors :: Documentation', | ||||
| 'Topic :: Text Editors :: Emacs', | ||||
| 'Topic :: Text Editors :: Integrated Development Environments (IDE)', | ||||
| 'Topic :: Text Editors :: Text Processing', | ||||
| 'Topic :: Text Editors :: Word Processors', | ||||
| 'Topic :: Text Processing', | ||||
| 'Topic :: Text Processing :: Filters', | ||||
| 'Topic :: Text Processing :: Fonts', | ||||
| 'Topic :: Text Processing :: General', | ||||
| 'Topic :: Text Processing :: Indexing', | ||||
| 'Topic :: Text Processing :: Linguistic', | ||||
| 'Topic :: Text Processing :: Markup', | ||||
| 'Topic :: Text Processing :: Markup :: HTML', | ||||
| 'Topic :: Text Processing :: Markup :: LaTeX', | ||||
| 'Topic :: Text Processing :: Markup :: SGML', | ||||
| 'Topic :: Text Processing :: Markup :: VRML', | ||||
| 'Topic :: Text Processing :: Markup :: XML', | ||||
| 'Topic :: Utilities', | ||||
| ] | ||||
							
								
								
									
										56
									
								
								Lib/packaging/command/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								Lib/packaging/command/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| """Subpackage containing all standard commands.""" | ||||
| 
 | ||||
| from packaging.errors import PackagingModuleError | ||||
| from packaging.util import resolve_name | ||||
| 
 | ||||
| __all__ = ['get_command_names', 'set_command', 'get_command_class', | ||||
|            'STANDARD_COMMANDS'] | ||||
| 
 | ||||
| _COMMANDS = { | ||||
|     'check': 'packaging.command.check.check', | ||||
|     'test': 'packaging.command.test.test', | ||||
|     'build': 'packaging.command.build.build', | ||||
|     'build_py': 'packaging.command.build_py.build_py', | ||||
|     'build_ext': 'packaging.command.build_ext.build_ext', | ||||
|     'build_clib': 'packaging.command.build_clib.build_clib', | ||||
|     'build_scripts': 'packaging.command.build_scripts.build_scripts', | ||||
|     'clean': 'packaging.command.clean.clean', | ||||
|     'install_dist': 'packaging.command.install_dist.install_dist', | ||||
|     'install_lib': 'packaging.command.install_lib.install_lib', | ||||
|     'install_headers': 'packaging.command.install_headers.install_headers', | ||||
|     'install_scripts': 'packaging.command.install_scripts.install_scripts', | ||||
|     'install_data': 'packaging.command.install_data.install_data', | ||||
|     'install_distinfo': | ||||
|         'packaging.command.install_distinfo.install_distinfo', | ||||
|     'sdist': 'packaging.command.sdist.sdist', | ||||
|     'bdist': 'packaging.command.bdist.bdist', | ||||
|     'bdist_dumb': 'packaging.command.bdist_dumb.bdist_dumb', | ||||
|     'bdist_wininst': 'packaging.command.bdist_wininst.bdist_wininst', | ||||
|     'register': 'packaging.command.register.register', | ||||
|     'upload': 'packaging.command.upload.upload', | ||||
|     'upload_docs': 'packaging.command.upload_docs.upload_docs'} | ||||
| 
 | ||||
| STANDARD_COMMANDS = set(_COMMANDS) | ||||
| 
 | ||||
| 
 | ||||
| def get_command_names(): | ||||
|     """Return registered commands""" | ||||
|     return sorted(_COMMANDS) | ||||
| 
 | ||||
| 
 | ||||
| def set_command(location): | ||||
|     cls = resolve_name(location) | ||||
|     # XXX we want to do the duck-type checking here | ||||
|     _COMMANDS[cls.get_command_name()] = cls | ||||
| 
 | ||||
| 
 | ||||
| def get_command_class(name): | ||||
|     """Return the registered command""" | ||||
|     try: | ||||
|         cls = _COMMANDS[name] | ||||
|         if isinstance(cls, str): | ||||
|             cls = resolve_name(cls) | ||||
|             _COMMANDS[name] = cls | ||||
|         return cls | ||||
|     except KeyError: | ||||
|         raise PackagingModuleError("Invalid command %s" % name) | ||||
							
								
								
									
										141
									
								
								Lib/packaging/command/bdist.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								Lib/packaging/command/bdist.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,141 @@ | |||
| """Create a built (binary) distribution. | ||||
| 
 | ||||
| If a --formats option was given on the command line, this command will | ||||
| call the corresponding bdist_* commands; if the option was absent, a | ||||
| bdist_* command depending on the current platform will be called. | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| 
 | ||||
| from packaging import util | ||||
| from packaging.command.cmd import Command | ||||
| from packaging.errors import PackagingPlatformError, PackagingOptionError | ||||
| 
 | ||||
| 
 | ||||
| def show_formats(): | ||||
|     """Print list of available formats (arguments to "--format" option). | ||||
|     """ | ||||
|     from packaging.fancy_getopt import FancyGetopt | ||||
|     formats = [] | ||||
|     for format in bdist.format_commands: | ||||
|         formats.append(("formats=" + format, None, | ||||
|                         bdist.format_command[format][1])) | ||||
|     pretty_printer = FancyGetopt(formats) | ||||
|     pretty_printer.print_help("List of available distribution formats:") | ||||
| 
 | ||||
| 
 | ||||
| class bdist(Command): | ||||
| 
 | ||||
|     description = "create a built (binary) distribution" | ||||
| 
 | ||||
|     user_options = [('bdist-base=', 'b', | ||||
|                      "temporary directory for creating built distributions"), | ||||
|                     ('plat-name=', 'p', | ||||
|                      "platform name to embed in generated filenames " | ||||
|                      "(default: %s)" % util.get_platform()), | ||||
|                     ('formats=', None, | ||||
|                      "formats for distribution (comma-separated list)"), | ||||
|                     ('dist-dir=', 'd', | ||||
|                      "directory to put final built distributions in " | ||||
|                      "[default: dist]"), | ||||
|                     ('skip-build', None, | ||||
|                      "skip rebuilding everything (for testing/debugging)"), | ||||
|                     ('owner=', 'u', | ||||
|                      "Owner name used when creating a tar file" | ||||
|                      " [default: current user]"), | ||||
|                     ('group=', 'g', | ||||
|                      "Group name used when creating a tar file" | ||||
|                      " [default: current group]"), | ||||
|                    ] | ||||
| 
 | ||||
|     boolean_options = ['skip-build'] | ||||
| 
 | ||||
|     help_options = [ | ||||
|         ('help-formats', None, | ||||
|          "lists available distribution formats", show_formats), | ||||
|         ] | ||||
| 
 | ||||
|     # This is of course very simplistic.  The various UNIX family operating | ||||
|     # systems have their specific formats, but they are out of scope for us; | ||||
|     # bdist_dumb is, well, dumb; it's more a building block for other | ||||
|     # packaging tools than a real end-user binary format. | ||||
|     default_format = {'posix': 'gztar', | ||||
|                       'nt': 'zip', | ||||
|                       'os2': 'zip'} | ||||
| 
 | ||||
|     # Establish the preferred order (for the --help-formats option). | ||||
|     format_commands = ['gztar', 'bztar', 'ztar', 'tar', | ||||
|                        'wininst', 'zip', 'msi'] | ||||
| 
 | ||||
|     # And the real information. | ||||
|     format_command = {'gztar': ('bdist_dumb', "gzip'ed tar file"), | ||||
|                       'bztar': ('bdist_dumb', "bzip2'ed tar file"), | ||||
|                       'ztar':  ('bdist_dumb', "compressed tar file"), | ||||
|                       'tar':   ('bdist_dumb', "tar file"), | ||||
|                       'wininst': ('bdist_wininst', | ||||
|                                   "Windows executable installer"), | ||||
|                       'zip':   ('bdist_dumb', "ZIP file"), | ||||
|                       'msi':   ('bdist_msi',  "Microsoft Installer") | ||||
|                       } | ||||
| 
 | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.bdist_base = None | ||||
|         self.plat_name = None | ||||
|         self.formats = None | ||||
|         self.dist_dir = None | ||||
|         self.skip_build = False | ||||
|         self.group = None | ||||
|         self.owner = None | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         # have to finalize 'plat_name' before 'bdist_base' | ||||
|         if self.plat_name is None: | ||||
|             if self.skip_build: | ||||
|                 self.plat_name = util.get_platform() | ||||
|             else: | ||||
|                 self.plat_name = self.get_finalized_command('build').plat_name | ||||
| 
 | ||||
|         # 'bdist_base' -- parent of per-built-distribution-format | ||||
|         # temporary directories (eg. we'll probably have | ||||
|         # "build/bdist.<plat>/dumb", etc.) | ||||
|         if self.bdist_base is None: | ||||
|             build_base = self.get_finalized_command('build').build_base | ||||
|             self.bdist_base = os.path.join(build_base, | ||||
|                                            'bdist.' + self.plat_name) | ||||
| 
 | ||||
|         self.ensure_string_list('formats') | ||||
|         if self.formats is None: | ||||
|             try: | ||||
|                 self.formats = [self.default_format[os.name]] | ||||
|             except KeyError: | ||||
|                 raise PackagingPlatformError("don't know how to create built distributions " + \ | ||||
|                       "on platform %s" % os.name) | ||||
| 
 | ||||
|         if self.dist_dir is None: | ||||
|             self.dist_dir = "dist" | ||||
| 
 | ||||
|     def run(self): | ||||
|         # Figure out which sub-commands we need to run. | ||||
|         commands = [] | ||||
|         for format in self.formats: | ||||
|             try: | ||||
|                 commands.append(self.format_command[format][0]) | ||||
|             except KeyError: | ||||
|                 raise PackagingOptionError("invalid format '%s'" % format) | ||||
| 
 | ||||
|         # Reinitialize and run each command. | ||||
|         for i in range(len(self.formats)): | ||||
|             cmd_name = commands[i] | ||||
|             sub_cmd = self.get_reinitialized_command(cmd_name) | ||||
| 
 | ||||
|             # passing the owner and group names for tar archiving | ||||
|             if cmd_name == 'bdist_dumb': | ||||
|                 sub_cmd.owner = self.owner | ||||
|                 sub_cmd.group = self.group | ||||
| 
 | ||||
|             # If we're going to need to run this command again, tell it to | ||||
|             # keep its temporary files around so subsequent runs go faster. | ||||
|             if cmd_name in commands[i+1:]: | ||||
|                 sub_cmd.keep_temp = True | ||||
|             self.run_command(cmd_name) | ||||
							
								
								
									
										137
									
								
								Lib/packaging/command/bdist_dumb.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								Lib/packaging/command/bdist_dumb.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,137 @@ | |||
| """Create a "dumb" built distribution. | ||||
| 
 | ||||
| A dumb distribution is just an archive meant to be unpacked under | ||||
| sys.prefix or sys.exec_prefix. | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| 
 | ||||
| from shutil import rmtree | ||||
| from sysconfig import get_python_version | ||||
| from packaging.util import get_platform | ||||
| from packaging.command.cmd import Command | ||||
| from packaging.errors import PackagingPlatformError | ||||
| from packaging import logger | ||||
| 
 | ||||
| class bdist_dumb(Command): | ||||
| 
 | ||||
|     description = 'create a "dumb" built distribution' | ||||
| 
 | ||||
|     user_options = [('bdist-dir=', 'd', | ||||
|                      "temporary directory for creating the distribution"), | ||||
|                     ('plat-name=', 'p', | ||||
|                      "platform name to embed in generated filenames " | ||||
|                      "(default: %s)" % get_platform()), | ||||
|                     ('format=', 'f', | ||||
|                      "archive format to create (tar, ztar, gztar, zip)"), | ||||
|                     ('keep-temp', 'k', | ||||
|                      "keep the pseudo-installation tree around after " + | ||||
|                      "creating the distribution archive"), | ||||
|                     ('dist-dir=', 'd', | ||||
|                      "directory to put final built distributions in"), | ||||
|                     ('skip-build', None, | ||||
|                      "skip rebuilding everything (for testing/debugging)"), | ||||
|                     ('relative', None, | ||||
|                      "build the archive using relative paths" | ||||
|                      "(default: false)"), | ||||
|                     ('owner=', 'u', | ||||
|                      "Owner name used when creating a tar file" | ||||
|                      " [default: current user]"), | ||||
|                     ('group=', 'g', | ||||
|                      "Group name used when creating a tar file" | ||||
|                      " [default: current group]"), | ||||
|                    ] | ||||
| 
 | ||||
|     boolean_options = ['keep-temp', 'skip-build', 'relative'] | ||||
| 
 | ||||
|     default_format = { 'posix': 'gztar', | ||||
|                        'nt': 'zip', | ||||
|                        'os2': 'zip' } | ||||
| 
 | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.bdist_dir = None | ||||
|         self.plat_name = None | ||||
|         self.format = None | ||||
|         self.keep_temp = False | ||||
|         self.dist_dir = None | ||||
|         self.skip_build = False | ||||
|         self.relative = False | ||||
|         self.owner = None | ||||
|         self.group = None | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         if self.bdist_dir is None: | ||||
|             bdist_base = self.get_finalized_command('bdist').bdist_base | ||||
|             self.bdist_dir = os.path.join(bdist_base, 'dumb') | ||||
| 
 | ||||
|         if self.format is None: | ||||
|             try: | ||||
|                 self.format = self.default_format[os.name] | ||||
|             except KeyError: | ||||
|                 raise PackagingPlatformError(("don't know how to create dumb built distributions " + | ||||
|                        "on platform %s") % os.name) | ||||
| 
 | ||||
|         self.set_undefined_options('bdist', 'dist_dir', 'plat_name') | ||||
| 
 | ||||
|     def run(self): | ||||
|         if not self.skip_build: | ||||
|             self.run_command('build') | ||||
| 
 | ||||
|         install = self.get_reinitialized_command('install_dist', | ||||
|                                                  reinit_subcommands=True) | ||||
|         install.root = self.bdist_dir | ||||
|         install.skip_build = self.skip_build | ||||
|         install.warn_dir = False | ||||
| 
 | ||||
|         logger.info("installing to %s", self.bdist_dir) | ||||
|         self.run_command('install_dist') | ||||
| 
 | ||||
|         # And make an archive relative to the root of the | ||||
|         # pseudo-installation tree. | ||||
|         archive_basename = "%s.%s" % (self.distribution.get_fullname(), | ||||
|                                       self.plat_name) | ||||
| 
 | ||||
|         # OS/2 objects to any ":" characters in a filename (such as when | ||||
|         # a timestamp is used in a version) so change them to hyphens. | ||||
|         if os.name == "os2": | ||||
|             archive_basename = archive_basename.replace(":", "-") | ||||
| 
 | ||||
|         pseudoinstall_root = os.path.join(self.dist_dir, archive_basename) | ||||
|         if not self.relative: | ||||
|             archive_root = self.bdist_dir | ||||
|         else: | ||||
|             if (self.distribution.has_ext_modules() and | ||||
|                 (install.install_base != install.install_platbase)): | ||||
|                 raise PackagingPlatformError( | ||||
|                     "can't make a dumb built distribution where base and " | ||||
|                     "platbase are different (%r, %r)" % | ||||
|                     (install.install_base, install.install_platbase)) | ||||
|             else: | ||||
|                 archive_root = os.path.join( | ||||
|                     self.bdist_dir, | ||||
|                     self._ensure_relative(install.install_base)) | ||||
| 
 | ||||
|         # Make the archive | ||||
|         filename = self.make_archive(pseudoinstall_root, | ||||
|                                      self.format, root_dir=archive_root, | ||||
|                                      owner=self.owner, group=self.group) | ||||
|         if self.distribution.has_ext_modules(): | ||||
|             pyversion = get_python_version() | ||||
|         else: | ||||
|             pyversion = 'any' | ||||
|         self.distribution.dist_files.append(('bdist_dumb', pyversion, | ||||
|                                              filename)) | ||||
| 
 | ||||
|         if not self.keep_temp: | ||||
|             if self.dry_run: | ||||
|                 logger.info('removing %s', self.bdist_dir) | ||||
|             else: | ||||
|                 rmtree(self.bdist_dir) | ||||
| 
 | ||||
|     def _ensure_relative(self, path): | ||||
|         # copied from dir_util, deleted | ||||
|         drive, path = os.path.splitdrive(path) | ||||
|         if path[0:1] == os.sep: | ||||
|             path = drive + path[1:] | ||||
|         return path | ||||
							
								
								
									
										740
									
								
								Lib/packaging/command/bdist_msi.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										740
									
								
								Lib/packaging/command/bdist_msi.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,740 @@ | |||
| """Create a Microsoft Installer (.msi) binary distribution.""" | ||||
| 
 | ||||
| # Copyright (C) 2005, 2006 Martin von Löwis | ||||
| # Licensed to PSF under a Contributor Agreement. | ||||
| 
 | ||||
| import sys | ||||
| import os | ||||
| import msilib | ||||
| 
 | ||||
| 
 | ||||
| from sysconfig import get_python_version | ||||
| from shutil import rmtree | ||||
| from packaging.command.cmd import Command | ||||
| from packaging.version import NormalizedVersion | ||||
| from packaging.errors import PackagingOptionError | ||||
| from packaging import logger as log | ||||
| from packaging.util import get_platform | ||||
| from msilib import schema, sequence, text | ||||
| from msilib import Directory, Feature, Dialog, add_data | ||||
| 
 | ||||
| class MSIVersion(NormalizedVersion): | ||||
|     """ | ||||
|     MSI ProductVersion must be strictly numeric. | ||||
|     MSIVersion disallows prerelease and postrelease versions. | ||||
|     """ | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(MSIVersion, self).__init__(*args, **kwargs) | ||||
|         if not self.is_final: | ||||
|             raise ValueError("ProductVersion must be strictly numeric") | ||||
| 
 | ||||
| class PyDialog(Dialog): | ||||
|     """Dialog class with a fixed layout: controls at the top, then a ruler, | ||||
|     then a list of buttons: back, next, cancel. Optionally a bitmap at the | ||||
|     left.""" | ||||
|     def __init__(self, *args, **kw): | ||||
|         """Dialog(database, name, x, y, w, h, attributes, title, first, | ||||
|         default, cancel, bitmap=true)""" | ||||
|         Dialog.__init__(self, *args) | ||||
|         ruler = self.h - 36 | ||||
|         #if kw.get("bitmap", True): | ||||
|         #    self.bitmap("Bitmap", 0, 0, bmwidth, ruler, "PythonWin") | ||||
|         self.line("BottomLine", 0, ruler, self.w, 0) | ||||
| 
 | ||||
|     def title(self, title): | ||||
|         "Set the title text of the dialog at the top." | ||||
|         # name, x, y, w, h, flags=Visible|Enabled|Transparent|NoPrefix, | ||||
|         # text, in VerdanaBold10 | ||||
|         self.text("Title", 15, 10, 320, 60, 0x30003, | ||||
|                   r"{\VerdanaBold10}%s" % title) | ||||
| 
 | ||||
|     def back(self, title, next, name = "Back", active = 1): | ||||
|         """Add a back button with a given title, the tab-next button, | ||||
|         its name in the Control table, possibly initially disabled. | ||||
| 
 | ||||
|         Return the button, so that events can be associated""" | ||||
|         if active: | ||||
|             flags = 3 # Visible|Enabled | ||||
|         else: | ||||
|             flags = 1 # Visible | ||||
|         return self.pushbutton(name, 180, self.h-27 , 56, 17, flags, title, next) | ||||
| 
 | ||||
|     def cancel(self, title, next, name = "Cancel", active = 1): | ||||
|         """Add a cancel button with a given title, the tab-next button, | ||||
|         its name in the Control table, possibly initially disabled. | ||||
| 
 | ||||
|         Return the button, so that events can be associated""" | ||||
|         if active: | ||||
|             flags = 3 # Visible|Enabled | ||||
|         else: | ||||
|             flags = 1 # Visible | ||||
|         return self.pushbutton(name, 304, self.h-27, 56, 17, flags, title, next) | ||||
| 
 | ||||
|     def next(self, title, next, name = "Next", active = 1): | ||||
|         """Add a Next button with a given title, the tab-next button, | ||||
|         its name in the Control table, possibly initially disabled. | ||||
| 
 | ||||
|         Return the button, so that events can be associated""" | ||||
|         if active: | ||||
|             flags = 3 # Visible|Enabled | ||||
|         else: | ||||
|             flags = 1 # Visible | ||||
|         return self.pushbutton(name, 236, self.h-27, 56, 17, flags, title, next) | ||||
| 
 | ||||
|     def xbutton(self, name, title, next, xpos): | ||||
|         """Add a button with a given title, the tab-next button, | ||||
|         its name in the Control table, giving its x position; the | ||||
|         y-position is aligned with the other buttons. | ||||
| 
 | ||||
|         Return the button, so that events can be associated""" | ||||
|         return self.pushbutton(name, int(self.w*xpos - 28), self.h-27, 56, 17, 3, title, next) | ||||
| 
 | ||||
| class bdist_msi(Command): | ||||
| 
 | ||||
|     description = "create a Microsoft Installer (.msi) binary distribution" | ||||
| 
 | ||||
|     user_options = [('bdist-dir=', None, | ||||
|                      "temporary directory for creating the distribution"), | ||||
|                     ('plat-name=', 'p', | ||||
|                      "platform name to embed in generated filenames " | ||||
|                      "(default: %s)" % get_platform()), | ||||
|                     ('keep-temp', 'k', | ||||
|                      "keep the pseudo-installation tree around after " + | ||||
|                      "creating the distribution archive"), | ||||
|                     ('target-version=', None, | ||||
|                      "require a specific python version" + | ||||
|                      " on the target system"), | ||||
|                     ('no-target-compile', 'c', | ||||
|                      "do not compile .py to .pyc on the target system"), | ||||
|                     ('no-target-optimize', 'o', | ||||
|                      "do not compile .py to .pyo (optimized)" | ||||
|                      "on the target system"), | ||||
|                     ('dist-dir=', 'd', | ||||
|                      "directory to put final built distributions in"), | ||||
|                     ('skip-build', None, | ||||
|                      "skip rebuilding everything (for testing/debugging)"), | ||||
|                     ('install-script=', None, | ||||
|                      "basename of installation script to be run after" | ||||
|                      "installation or before deinstallation"), | ||||
|                     ('pre-install-script=', None, | ||||
|                      "Fully qualified filename of a script to be run before " | ||||
|                      "any files are installed.  This script need not be in the " | ||||
|                      "distribution"), | ||||
|                    ] | ||||
| 
 | ||||
|     boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize', | ||||
|                        'skip-build'] | ||||
| 
 | ||||
|     all_versions = ['2.0', '2.1', '2.2', '2.3', '2.4', | ||||
|                     '2.5', '2.6', '2.7', '2.8', '2.9', | ||||
|                     '3.0', '3.1', '3.2', '3.3', '3.4', | ||||
|                     '3.5', '3.6', '3.7', '3.8', '3.9'] | ||||
|     other_version = 'X' | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.bdist_dir = None | ||||
|         self.plat_name = None | ||||
|         self.keep_temp = False | ||||
|         self.no_target_compile = False | ||||
|         self.no_target_optimize = False | ||||
|         self.target_version = None | ||||
|         self.dist_dir = None | ||||
|         self.skip_build = False | ||||
|         self.install_script = None | ||||
|         self.pre_install_script = None | ||||
|         self.versions = None | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         if self.bdist_dir is None: | ||||
|             bdist_base = self.get_finalized_command('bdist').bdist_base | ||||
|             self.bdist_dir = os.path.join(bdist_base, 'msi') | ||||
|         short_version = get_python_version() | ||||
|         if (not self.target_version) and self.distribution.has_ext_modules(): | ||||
|             self.target_version = short_version | ||||
|         if self.target_version: | ||||
|             self.versions = [self.target_version] | ||||
|             if not self.skip_build and self.distribution.has_ext_modules()\ | ||||
|                and self.target_version != short_version: | ||||
|                 raise PackagingOptionError("target version can only be %s, or the '--skip-build'" \ | ||||
|                       " option must be specified" % (short_version,)) | ||||
|         else: | ||||
|             self.versions = list(self.all_versions) | ||||
| 
 | ||||
|         self.set_undefined_options('bdist', 'dist_dir', 'plat_name') | ||||
| 
 | ||||
|         if self.pre_install_script: | ||||
|             raise PackagingOptionError("the pre-install-script feature is not yet implemented") | ||||
| 
 | ||||
|         if self.install_script: | ||||
|             for script in self.distribution.scripts: | ||||
|                 if self.install_script == os.path.basename(script): | ||||
|                     break | ||||
|             else: | ||||
|                 raise PackagingOptionError("install_script '%s' not found in scripts" % \ | ||||
|                       self.install_script) | ||||
|         self.install_script_key = None | ||||
| 
 | ||||
| 
 | ||||
|     def run(self): | ||||
|         if not self.skip_build: | ||||
|             self.run_command('build') | ||||
| 
 | ||||
|         install = self.get_reinitialized_command('install_dist', | ||||
|                                                  reinit_subcommands=True) | ||||
|         install.prefix = self.bdist_dir | ||||
|         install.skip_build = self.skip_build | ||||
|         install.warn_dir = False | ||||
| 
 | ||||
|         install_lib = self.get_reinitialized_command('install_lib') | ||||
|         # we do not want to include pyc or pyo files | ||||
|         install_lib.compile = False | ||||
|         install_lib.optimize = 0 | ||||
| 
 | ||||
|         if self.distribution.has_ext_modules(): | ||||
|             # If we are building an installer for a Python version other | ||||
|             # than the one we are currently running, then we need to ensure | ||||
|             # our build_lib reflects the other Python version rather than ours. | ||||
|             # Note that for target_version!=sys.version, we must have skipped the | ||||
|             # build step, so there is no issue with enforcing the build of this | ||||
|             # version. | ||||
|             target_version = self.target_version | ||||
|             if not target_version: | ||||
|                 assert self.skip_build, "Should have already checked this" | ||||
|                 target_version = sys.version[0:3] | ||||
|             plat_specifier = ".%s-%s" % (self.plat_name, target_version) | ||||
|             build = self.get_finalized_command('build') | ||||
|             build.build_lib = os.path.join(build.build_base, | ||||
|                                            'lib' + plat_specifier) | ||||
| 
 | ||||
|         log.info("installing to %s", self.bdist_dir) | ||||
|         install.ensure_finalized() | ||||
| 
 | ||||
|         # avoid warning of 'install_lib' about installing | ||||
|         # into a directory not in sys.path | ||||
|         sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB')) | ||||
| 
 | ||||
|         install.run() | ||||
| 
 | ||||
|         del sys.path[0] | ||||
| 
 | ||||
|         self.mkpath(self.dist_dir) | ||||
|         fullname = self.distribution.get_fullname() | ||||
|         installer_name = self.get_installer_filename(fullname) | ||||
|         installer_name = os.path.abspath(installer_name) | ||||
|         if os.path.exists(installer_name): os.unlink(installer_name) | ||||
| 
 | ||||
|         metadata = self.distribution.metadata | ||||
|         author = metadata.author | ||||
|         if not author: | ||||
|             author = metadata.maintainer | ||||
|         if not author: | ||||
|             author = "UNKNOWN" | ||||
|         version = MSIVersion(metadata.get_version()) | ||||
|         # Prefix ProductName with Python x.y, so that | ||||
|         # it sorts together with the other Python packages | ||||
|         # in Add-Remove-Programs (APR) | ||||
|         fullname = self.distribution.get_fullname() | ||||
|         if self.target_version: | ||||
|             product_name = "Python %s %s" % (self.target_version, fullname) | ||||
|         else: | ||||
|             product_name = "Python %s" % (fullname) | ||||
|         self.db = msilib.init_database(installer_name, schema, | ||||
|                 product_name, msilib.gen_uuid(), | ||||
|                 str(version), author) | ||||
|         msilib.add_tables(self.db, sequence) | ||||
|         props = [('DistVersion', version)] | ||||
|         email = metadata.author_email or metadata.maintainer_email | ||||
|         if email: | ||||
|             props.append(("ARPCONTACT", email)) | ||||
|         if metadata.url: | ||||
|             props.append(("ARPURLINFOABOUT", metadata.url)) | ||||
|         if props: | ||||
|             add_data(self.db, 'Property', props) | ||||
| 
 | ||||
|         self.add_find_python() | ||||
|         self.add_files() | ||||
|         self.add_scripts() | ||||
|         self.add_ui() | ||||
|         self.db.Commit() | ||||
| 
 | ||||
|         if hasattr(self.distribution, 'dist_files'): | ||||
|             tup = 'bdist_msi', self.target_version or 'any', fullname | ||||
|             self.distribution.dist_files.append(tup) | ||||
| 
 | ||||
|         if not self.keep_temp: | ||||
|             log.info("removing temporary build directory %s", self.bdist_dir) | ||||
|             if not self.dry_run: | ||||
|                 rmtree(self.bdist_dir) | ||||
| 
 | ||||
|     def add_files(self): | ||||
|         db = self.db | ||||
|         cab = msilib.CAB("distfiles") | ||||
|         rootdir = os.path.abspath(self.bdist_dir) | ||||
| 
 | ||||
|         root = Directory(db, cab, None, rootdir, "TARGETDIR", "SourceDir") | ||||
|         f = Feature(db, "Python", "Python", "Everything", | ||||
|                     0, 1, directory="TARGETDIR") | ||||
| 
 | ||||
|         items = [(f, root, '')] | ||||
|         for version in self.versions + [self.other_version]: | ||||
|             target = "TARGETDIR" + version | ||||
|             name = default = "Python" + version | ||||
|             desc = "Everything" | ||||
|             if version is self.other_version: | ||||
|                 title = "Python from another location" | ||||
|                 level = 2 | ||||
|             else: | ||||
|                 title = "Python %s from registry" % version | ||||
|                 level = 1 | ||||
|             f = Feature(db, name, title, desc, 1, level, directory=target) | ||||
|             dir = Directory(db, cab, root, rootdir, target, default) | ||||
|             items.append((f, dir, version)) | ||||
|         db.Commit() | ||||
| 
 | ||||
|         seen = {} | ||||
|         for feature, dir, version in items: | ||||
|             todo = [dir] | ||||
|             while todo: | ||||
|                 dir = todo.pop() | ||||
|                 for file in os.listdir(dir.absolute): | ||||
|                     afile = os.path.join(dir.absolute, file) | ||||
|                     if os.path.isdir(afile): | ||||
|                         short = "%s|%s" % (dir.make_short(file), file) | ||||
|                         default = file + version | ||||
|                         newdir = Directory(db, cab, dir, file, default, short) | ||||
|                         todo.append(newdir) | ||||
|                     else: | ||||
|                         if not dir.component: | ||||
|                             dir.start_component(dir.logical, feature, 0) | ||||
|                         if afile not in seen: | ||||
|                             key = seen[afile] = dir.add_file(file) | ||||
|                             if file==self.install_script: | ||||
|                                 if self.install_script_key: | ||||
|                                     raise PackagingOptionError( | ||||
|                                           "Multiple files with name %s" % file) | ||||
|                                 self.install_script_key = '[#%s]' % key | ||||
|                         else: | ||||
|                             key = seen[afile] | ||||
|                             add_data(self.db, "DuplicateFile", | ||||
|                                 [(key + version, dir.component, key, None, dir.logical)]) | ||||
|             db.Commit() | ||||
|         cab.commit(db) | ||||
| 
 | ||||
|     def add_find_python(self): | ||||
|         """Adds code to the installer to compute the location of Python. | ||||
| 
 | ||||
|         Properties PYTHON.MACHINE.X.Y and PYTHON.USER.X.Y will be set from the | ||||
|         registry for each version of Python. | ||||
| 
 | ||||
|         Properties TARGETDIRX.Y will be set from PYTHON.USER.X.Y if defined, | ||||
|         else from PYTHON.MACHINE.X.Y. | ||||
| 
 | ||||
|         Properties PYTHONX.Y will be set to TARGETDIRX.Y\\python.exe""" | ||||
| 
 | ||||
|         start = 402 | ||||
|         for ver in self.versions: | ||||
|             install_path = r"SOFTWARE\Python\PythonCore\%s\InstallPath" % ver | ||||
|             machine_reg = "python.machine." + ver | ||||
|             user_reg = "python.user." + ver | ||||
|             machine_prop = "PYTHON.MACHINE." + ver | ||||
|             user_prop = "PYTHON.USER." + ver | ||||
|             machine_action = "PythonFromMachine" + ver | ||||
|             user_action = "PythonFromUser" + ver | ||||
|             exe_action = "PythonExe" + ver | ||||
|             target_dir_prop = "TARGETDIR" + ver | ||||
|             exe_prop = "PYTHON" + ver | ||||
|             if msilib.Win64: | ||||
|                 # type: msidbLocatorTypeRawValue + msidbLocatorType64bit | ||||
|                 Type = 2+16 | ||||
|             else: | ||||
|                 Type = 2 | ||||
|             add_data(self.db, "RegLocator", | ||||
|                     [(machine_reg, 2, install_path, None, Type), | ||||
|                      (user_reg, 1, install_path, None, Type)]) | ||||
|             add_data(self.db, "AppSearch", | ||||
|                     [(machine_prop, machine_reg), | ||||
|                      (user_prop, user_reg)]) | ||||
|             add_data(self.db, "CustomAction", | ||||
|                     [(machine_action, 51+256, target_dir_prop, "[" + machine_prop + "]"), | ||||
|                      (user_action, 51+256, target_dir_prop, "[" + user_prop + "]"), | ||||
|                      (exe_action, 51+256, exe_prop, "[" + target_dir_prop + "]\\python.exe"), | ||||
|                     ]) | ||||
|             add_data(self.db, "InstallExecuteSequence", | ||||
|                     [(machine_action, machine_prop, start), | ||||
|                      (user_action, user_prop, start + 1), | ||||
|                      (exe_action, None, start + 2), | ||||
|                     ]) | ||||
|             add_data(self.db, "InstallUISequence", | ||||
|                     [(machine_action, machine_prop, start), | ||||
|                      (user_action, user_prop, start + 1), | ||||
|                      (exe_action, None, start + 2), | ||||
|                     ]) | ||||
|             add_data(self.db, "Condition", | ||||
|                     [("Python" + ver, 0, "NOT TARGETDIR" + ver)]) | ||||
|             start += 4 | ||||
|             assert start < 500 | ||||
| 
 | ||||
|     def add_scripts(self): | ||||
|         if self.install_script: | ||||
|             start = 6800 | ||||
|             for ver in self.versions + [self.other_version]: | ||||
|                 install_action = "install_script." + ver | ||||
|                 exe_prop = "PYTHON" + ver | ||||
|                 add_data(self.db, "CustomAction", | ||||
|                         [(install_action, 50, exe_prop, self.install_script_key)]) | ||||
|                 add_data(self.db, "InstallExecuteSequence", | ||||
|                         [(install_action, "&Python%s=3" % ver, start)]) | ||||
|                 start += 1 | ||||
|         # XXX pre-install scripts are currently refused in finalize_options() | ||||
|         #     but if this feature is completed, it will also need to add | ||||
|         #     entries for each version as the above code does | ||||
|         if self.pre_install_script: | ||||
|             scriptfn = os.path.join(self.bdist_dir, "preinstall.bat") | ||||
|             with open(scriptfn, "w") as f: | ||||
|                 # The batch file will be executed with [PYTHON], so that %1 | ||||
|                 # is the path to the Python interpreter; %0 will be the path | ||||
|                 # of the batch file. | ||||
|                 # rem =""" | ||||
|                 # %1 %0 | ||||
|                 # exit | ||||
|                 # """ | ||||
|                 # <actual script> | ||||
|                 f.write('rem ="""\n%1 %0\nexit\n"""\n') | ||||
|                 with open(self.pre_install_script) as fp: | ||||
|                     f.write(fp.read()) | ||||
|             add_data(self.db, "Binary", | ||||
|                      [("PreInstall", msilib.Binary(scriptfn)), | ||||
|                      ]) | ||||
|             add_data(self.db, "CustomAction", | ||||
|                      [("PreInstall", 2, "PreInstall", None), | ||||
|                      ]) | ||||
|             add_data(self.db, "InstallExecuteSequence", | ||||
|                      [("PreInstall", "NOT Installed", 450), | ||||
|                      ]) | ||||
| 
 | ||||
|     def add_ui(self): | ||||
|         db = self.db | ||||
|         x = y = 50 | ||||
|         w = 370 | ||||
|         h = 300 | ||||
|         title = "[ProductName] Setup" | ||||
| 
 | ||||
|         # see "Dialog Style Bits" | ||||
|         modal = 3      # visible | modal | ||||
|         modeless = 1   # visible | ||||
| 
 | ||||
|         # UI customization properties | ||||
|         add_data(db, "Property", | ||||
|                  # See "DefaultUIFont Property" | ||||
|                  [("DefaultUIFont", "DlgFont8"), | ||||
|                   # See "ErrorDialog Style Bit" | ||||
|                   ("ErrorDialog", "ErrorDlg"), | ||||
|                   ("Progress1", "Install"),   # modified in maintenance type dlg | ||||
|                   ("Progress2", "installs"), | ||||
|                   ("MaintenanceForm_Action", "Repair"), | ||||
|                   # possible values: ALL, JUSTME | ||||
|                   ("WhichUsers", "ALL") | ||||
|                  ]) | ||||
| 
 | ||||
|         # Fonts, see "TextStyle Table" | ||||
|         add_data(db, "TextStyle", | ||||
|                  [("DlgFont8", "Tahoma", 9, None, 0), | ||||
|                   ("DlgFontBold8", "Tahoma", 8, None, 1), #bold | ||||
|                   ("VerdanaBold10", "Verdana", 10, None, 1), | ||||
|                   ("VerdanaRed9", "Verdana", 9, 255, 0), | ||||
|                  ]) | ||||
| 
 | ||||
|         # UI Sequences, see "InstallUISequence Table", "Using a Sequence Table" | ||||
|         # Numbers indicate sequence; see sequence.py for how these action integrate | ||||
|         add_data(db, "InstallUISequence", | ||||
|                  [("PrepareDlg", "Not Privileged or Windows9x or Installed", 140), | ||||
|                   ("WhichUsersDlg", "Privileged and not Windows9x and not Installed", 141), | ||||
|                   # In the user interface, assume all-users installation if privileged. | ||||
|                   ("SelectFeaturesDlg", "Not Installed", 1230), | ||||
|                   # XXX no support for resume installations yet | ||||
|                   #("ResumeDlg", "Installed AND (RESUME OR Preselected)", 1240), | ||||
|                   ("MaintenanceTypeDlg", "Installed AND NOT RESUME AND NOT Preselected", 1250), | ||||
|                   ("ProgressDlg", None, 1280)]) | ||||
| 
 | ||||
|         add_data(db, 'ActionText', text.ActionText) | ||||
|         add_data(db, 'UIText', text.UIText) | ||||
|         ##################################################################### | ||||
|         # Standard dialogs: FatalError, UserExit, ExitDialog | ||||
|         fatal=PyDialog(db, "FatalError", x, y, w, h, modal, title, | ||||
|                      "Finish", "Finish", "Finish") | ||||
|         fatal.title("[ProductName] Installer ended prematurely") | ||||
|         fatal.back("< Back", "Finish", active = 0) | ||||
|         fatal.cancel("Cancel", "Back", active = 0) | ||||
|         fatal.text("Description1", 15, 70, 320, 80, 0x30003, | ||||
|                    "[ProductName] setup ended prematurely because of an error.  Your system has not been modified.  To install this program at a later time, please run the installation again.") | ||||
|         fatal.text("Description2", 15, 155, 320, 20, 0x30003, | ||||
|                    "Click the Finish button to exit the Installer.") | ||||
|         c=fatal.next("Finish", "Cancel", name="Finish") | ||||
|         c.event("EndDialog", "Exit") | ||||
| 
 | ||||
|         user_exit=PyDialog(db, "UserExit", x, y, w, h, modal, title, | ||||
|                      "Finish", "Finish", "Finish") | ||||
|         user_exit.title("[ProductName] Installer was interrupted") | ||||
|         user_exit.back("< Back", "Finish", active = 0) | ||||
|         user_exit.cancel("Cancel", "Back", active = 0) | ||||
|         user_exit.text("Description1", 15, 70, 320, 80, 0x30003, | ||||
|                    "[ProductName] setup was interrupted.  Your system has not been modified.  " | ||||
|                    "To install this program at a later time, please run the installation again.") | ||||
|         user_exit.text("Description2", 15, 155, 320, 20, 0x30003, | ||||
|                    "Click the Finish button to exit the Installer.") | ||||
|         c = user_exit.next("Finish", "Cancel", name="Finish") | ||||
|         c.event("EndDialog", "Exit") | ||||
| 
 | ||||
|         exit_dialog = PyDialog(db, "ExitDialog", x, y, w, h, modal, title, | ||||
|                              "Finish", "Finish", "Finish") | ||||
|         exit_dialog.title("Completing the [ProductName] Installer") | ||||
|         exit_dialog.back("< Back", "Finish", active = 0) | ||||
|         exit_dialog.cancel("Cancel", "Back", active = 0) | ||||
|         exit_dialog.text("Description", 15, 235, 320, 20, 0x30003, | ||||
|                    "Click the Finish button to exit the Installer.") | ||||
|         c = exit_dialog.next("Finish", "Cancel", name="Finish") | ||||
|         c.event("EndDialog", "Return") | ||||
| 
 | ||||
|         ##################################################################### | ||||
|         # Required dialog: FilesInUse, ErrorDlg | ||||
|         inuse = PyDialog(db, "FilesInUse", | ||||
|                          x, y, w, h, | ||||
|                          19,                # KeepModeless|Modal|Visible | ||||
|                          title, | ||||
|                          "Retry", "Retry", "Retry", bitmap=False) | ||||
|         inuse.text("Title", 15, 6, 200, 15, 0x30003, | ||||
|                    r"{\DlgFontBold8}Files in Use") | ||||
|         inuse.text("Description", 20, 23, 280, 20, 0x30003, | ||||
|                "Some files that need to be updated are currently in use.") | ||||
|         inuse.text("Text", 20, 55, 330, 50, 3, | ||||
|                    "The following applications are using files that need to be updated by this setup. Close these applications and then click Retry to continue the installation or Cancel to exit it.") | ||||
|         inuse.control("List", "ListBox", 20, 107, 330, 130, 7, "FileInUseProcess", | ||||
|                       None, None, None) | ||||
|         c=inuse.back("Exit", "Ignore", name="Exit") | ||||
|         c.event("EndDialog", "Exit") | ||||
|         c=inuse.next("Ignore", "Retry", name="Ignore") | ||||
|         c.event("EndDialog", "Ignore") | ||||
|         c=inuse.cancel("Retry", "Exit", name="Retry") | ||||
|         c.event("EndDialog","Retry") | ||||
| 
 | ||||
|         # See "Error Dialog". See "ICE20" for the required names of the controls. | ||||
|         error = Dialog(db, "ErrorDlg", | ||||
|                        50, 10, 330, 101, | ||||
|                        65543,       # Error|Minimize|Modal|Visible | ||||
|                        title, | ||||
|                        "ErrorText", None, None) | ||||
|         error.text("ErrorText", 50,9,280,48,3, "") | ||||
|         #error.control("ErrorIcon", "Icon", 15, 9, 24, 24, 5242881, None, "py.ico", None, None) | ||||
|         error.pushbutton("N",120,72,81,21,3,"No",None).event("EndDialog","ErrorNo") | ||||
|         error.pushbutton("Y",240,72,81,21,3,"Yes",None).event("EndDialog","ErrorYes") | ||||
|         error.pushbutton("A",0,72,81,21,3,"Abort",None).event("EndDialog","ErrorAbort") | ||||
|         error.pushbutton("C",42,72,81,21,3,"Cancel",None).event("EndDialog","ErrorCancel") | ||||
|         error.pushbutton("I",81,72,81,21,3,"Ignore",None).event("EndDialog","ErrorIgnore") | ||||
|         error.pushbutton("O",159,72,81,21,3,"Ok",None).event("EndDialog","ErrorOk") | ||||
|         error.pushbutton("R",198,72,81,21,3,"Retry",None).event("EndDialog","ErrorRetry") | ||||
| 
 | ||||
|         ##################################################################### | ||||
|         # Global "Query Cancel" dialog | ||||
|         cancel = Dialog(db, "CancelDlg", 50, 10, 260, 85, 3, title, | ||||
|                         "No", "No", "No") | ||||
|         cancel.text("Text", 48, 15, 194, 30, 3, | ||||
|                     "Are you sure you want to cancel [ProductName] installation?") | ||||
|         #cancel.control("Icon", "Icon", 15, 15, 24, 24, 5242881, None, | ||||
|         #               "py.ico", None, None) | ||||
|         c=cancel.pushbutton("Yes", 72, 57, 56, 17, 3, "Yes", "No") | ||||
|         c.event("EndDialog", "Exit") | ||||
| 
 | ||||
|         c=cancel.pushbutton("No", 132, 57, 56, 17, 3, "No", "Yes") | ||||
|         c.event("EndDialog", "Return") | ||||
| 
 | ||||
|         ##################################################################### | ||||
|         # Global "Wait for costing" dialog | ||||
|         costing = Dialog(db, "WaitForCostingDlg", 50, 10, 260, 85, modal, title, | ||||
|                          "Return", "Return", "Return") | ||||
|         costing.text("Text", 48, 15, 194, 30, 3, | ||||
|                      "Please wait while the installer finishes determining your disk space requirements.") | ||||
|         c = costing.pushbutton("Return", 102, 57, 56, 17, 3, "Return", None) | ||||
|         c.event("EndDialog", "Exit") | ||||
| 
 | ||||
|         ##################################################################### | ||||
|         # Preparation dialog: no user input except cancellation | ||||
|         prep = PyDialog(db, "PrepareDlg", x, y, w, h, modeless, title, | ||||
|                         "Cancel", "Cancel", "Cancel") | ||||
|         prep.text("Description", 15, 70, 320, 40, 0x30003, | ||||
|                   "Please wait while the Installer prepares to guide you through the installation.") | ||||
|         prep.title("Welcome to the [ProductName] Installer") | ||||
|         c=prep.text("ActionText", 15, 110, 320, 20, 0x30003, "Pondering...") | ||||
|         c.mapping("ActionText", "Text") | ||||
|         c=prep.text("ActionData", 15, 135, 320, 30, 0x30003, None) | ||||
|         c.mapping("ActionData", "Text") | ||||
|         prep.back("Back", None, active=0) | ||||
|         prep.next("Next", None, active=0) | ||||
|         c=prep.cancel("Cancel", None) | ||||
|         c.event("SpawnDialog", "CancelDlg") | ||||
| 
 | ||||
|         ##################################################################### | ||||
|         # Feature (Python directory) selection | ||||
|         seldlg = PyDialog(db, "SelectFeaturesDlg", x, y, w, h, modal, title, | ||||
|                         "Next", "Next", "Cancel") | ||||
|         seldlg.title("Select Python Installations") | ||||
| 
 | ||||
|         seldlg.text("Hint", 15, 30, 300, 20, 3, | ||||
|                     "Select the Python locations where %s should be installed." | ||||
|                     % self.distribution.get_fullname()) | ||||
| 
 | ||||
|         seldlg.back("< Back", None, active=0) | ||||
|         c = seldlg.next("Next >", "Cancel") | ||||
|         order = 1 | ||||
|         c.event("[TARGETDIR]", "[SourceDir]", ordering=order) | ||||
|         for version in self.versions + [self.other_version]: | ||||
|             order += 1 | ||||
|             c.event("[TARGETDIR]", "[TARGETDIR%s]" % version, | ||||
|                     "FEATURE_SELECTED AND &Python%s=3" % version, | ||||
|                     ordering=order) | ||||
|         c.event("SpawnWaitDialog", "WaitForCostingDlg", ordering=order + 1) | ||||
|         c.event("EndDialog", "Return", ordering=order + 2) | ||||
|         c = seldlg.cancel("Cancel", "Features") | ||||
|         c.event("SpawnDialog", "CancelDlg") | ||||
| 
 | ||||
|         c = seldlg.control("Features", "SelectionTree", 15, 60, 300, 120, 3, | ||||
|                            "FEATURE", None, "PathEdit", None) | ||||
|         c.event("[FEATURE_SELECTED]", "1") | ||||
|         ver = self.other_version | ||||
|         install_other_cond = "FEATURE_SELECTED AND &Python%s=3" % ver | ||||
|         dont_install_other_cond = "FEATURE_SELECTED AND &Python%s<>3" % ver | ||||
| 
 | ||||
|         c = seldlg.text("Other", 15, 200, 300, 15, 3, | ||||
|                         "Provide an alternate Python location") | ||||
|         c.condition("Enable", install_other_cond) | ||||
|         c.condition("Show", install_other_cond) | ||||
|         c.condition("Disable", dont_install_other_cond) | ||||
|         c.condition("Hide", dont_install_other_cond) | ||||
| 
 | ||||
|         c = seldlg.control("PathEdit", "PathEdit", 15, 215, 300, 16, 1, | ||||
|                            "TARGETDIR" + ver, None, "Next", None) | ||||
|         c.condition("Enable", install_other_cond) | ||||
|         c.condition("Show", install_other_cond) | ||||
|         c.condition("Disable", dont_install_other_cond) | ||||
|         c.condition("Hide", dont_install_other_cond) | ||||
| 
 | ||||
|         ##################################################################### | ||||
|         # Disk cost | ||||
|         cost = PyDialog(db, "DiskCostDlg", x, y, w, h, modal, title, | ||||
|                         "OK", "OK", "OK", bitmap=False) | ||||
|         cost.text("Title", 15, 6, 200, 15, 0x30003, | ||||
|                   "{\DlgFontBold8}Disk Space Requirements") | ||||
|         cost.text("Description", 20, 20, 280, 20, 0x30003, | ||||
|                   "The disk space required for the installation of the selected features.") | ||||
|         cost.text("Text", 20, 53, 330, 60, 3, | ||||
|                   "The highlighted volumes (if any) do not have enough disk space " | ||||
|               "available for the currently selected features.  You can either " | ||||
|               "remove some files from the highlighted volumes, or choose to " | ||||
|               "install less features onto local drive(s), or select different " | ||||
|               "destination drive(s).") | ||||
|         cost.control("VolumeList", "VolumeCostList", 20, 100, 330, 150, 393223, | ||||
|                      None, "{120}{70}{70}{70}{70}", None, None) | ||||
|         cost.xbutton("OK", "Ok", None, 0.5).event("EndDialog", "Return") | ||||
| 
 | ||||
|         ##################################################################### | ||||
|         # WhichUsers Dialog. Only available on NT, and for privileged users. | ||||
|         # This must be run before FindRelatedProducts, because that will | ||||
|         # take into account whether the previous installation was per-user | ||||
|         # or per-machine. We currently don't support going back to this | ||||
|         # dialog after "Next" was selected; to support this, we would need to | ||||
|         # find how to reset the ALLUSERS property, and how to re-run | ||||
|         # FindRelatedProducts. | ||||
|         # On Windows9x, the ALLUSERS property is ignored on the command line | ||||
|         # and in the Property table, but installer fails according to the documentation | ||||
|         # if a dialog attempts to set ALLUSERS. | ||||
|         whichusers = PyDialog(db, "WhichUsersDlg", x, y, w, h, modal, title, | ||||
|                             "AdminInstall", "Next", "Cancel") | ||||
|         whichusers.title("Select whether to install [ProductName] for all users of this computer.") | ||||
|         # A radio group with two options: allusers, justme | ||||
|         g = whichusers.radiogroup("AdminInstall", 15, 60, 260, 50, 3, | ||||
|                                   "WhichUsers", "", "Next") | ||||
|         g.add("ALL", 0, 5, 150, 20, "Install for all users") | ||||
|         g.add("JUSTME", 0, 25, 150, 20, "Install just for me") | ||||
| 
 | ||||
|         whichusers.back("Back", None, active=0) | ||||
| 
 | ||||
|         c = whichusers.next("Next >", "Cancel") | ||||
|         c.event("[ALLUSERS]", "1", 'WhichUsers="ALL"', 1) | ||||
|         c.event("EndDialog", "Return", ordering = 2) | ||||
| 
 | ||||
|         c = whichusers.cancel("Cancel", "AdminInstall") | ||||
|         c.event("SpawnDialog", "CancelDlg") | ||||
| 
 | ||||
|         ##################################################################### | ||||
|         # Installation Progress dialog (modeless) | ||||
|         progress = PyDialog(db, "ProgressDlg", x, y, w, h, modeless, title, | ||||
|                             "Cancel", "Cancel", "Cancel", bitmap=False) | ||||
|         progress.text("Title", 20, 15, 200, 15, 0x30003, | ||||
|                       "{\DlgFontBold8}[Progress1] [ProductName]") | ||||
|         progress.text("Text", 35, 65, 300, 30, 3, | ||||
|                       "Please wait while the Installer [Progress2] [ProductName]. " | ||||
|                       "This may take several minutes.") | ||||
|         progress.text("StatusLabel", 35, 100, 35, 20, 3, "Status:") | ||||
| 
 | ||||
|         c=progress.text("ActionText", 70, 100, w-70, 20, 3, "Pondering...") | ||||
|         c.mapping("ActionText", "Text") | ||||
| 
 | ||||
|         #c=progress.text("ActionData", 35, 140, 300, 20, 3, None) | ||||
|         #c.mapping("ActionData", "Text") | ||||
| 
 | ||||
|         c=progress.control("ProgressBar", "ProgressBar", 35, 120, 300, 10, 65537, | ||||
|                            None, "Progress done", None, None) | ||||
|         c.mapping("SetProgress", "Progress") | ||||
| 
 | ||||
|         progress.back("< Back", "Next", active=False) | ||||
|         progress.next("Next >", "Cancel", active=False) | ||||
|         progress.cancel("Cancel", "Back").event("SpawnDialog", "CancelDlg") | ||||
| 
 | ||||
|         ################################################################### | ||||
|         # Maintenance type: repair/uninstall | ||||
|         maint = PyDialog(db, "MaintenanceTypeDlg", x, y, w, h, modal, title, | ||||
|                          "Next", "Next", "Cancel") | ||||
|         maint.title("Welcome to the [ProductName] Setup Wizard") | ||||
|         maint.text("BodyText", 15, 63, 330, 42, 3, | ||||
|                    "Select whether you want to repair or remove [ProductName].") | ||||
|         g=maint.radiogroup("RepairRadioGroup", 15, 108, 330, 60, 3, | ||||
|                             "MaintenanceForm_Action", "", "Next") | ||||
|         #g.add("Change", 0, 0, 200, 17, "&Change [ProductName]") | ||||
|         g.add("Repair", 0, 18, 200, 17, "&Repair [ProductName]") | ||||
|         g.add("Remove", 0, 36, 200, 17, "Re&move [ProductName]") | ||||
| 
 | ||||
|         maint.back("< Back", None, active=False) | ||||
|         c=maint.next("Finish", "Cancel") | ||||
|         # Change installation: Change progress dialog to "Change", then ask | ||||
|         # for feature selection | ||||
|         #c.event("[Progress1]", "Change", 'MaintenanceForm_Action="Change"', 1) | ||||
|         #c.event("[Progress2]", "changes", 'MaintenanceForm_Action="Change"', 2) | ||||
| 
 | ||||
|         # Reinstall: Change progress dialog to "Repair", then invoke reinstall | ||||
|         # Also set list of reinstalled features to "ALL" | ||||
|         c.event("[REINSTALL]", "ALL", 'MaintenanceForm_Action="Repair"', 5) | ||||
|         c.event("[Progress1]", "Repairing", 'MaintenanceForm_Action="Repair"', 6) | ||||
|         c.event("[Progress2]", "repairs", 'MaintenanceForm_Action="Repair"', 7) | ||||
|         c.event("Reinstall", "ALL", 'MaintenanceForm_Action="Repair"', 8) | ||||
| 
 | ||||
|         # Uninstall: Change progress to "Remove", then invoke uninstall | ||||
|         # Also set list of removed features to "ALL" | ||||
|         c.event("[REMOVE]", "ALL", 'MaintenanceForm_Action="Remove"', 11) | ||||
|         c.event("[Progress1]", "Removing", 'MaintenanceForm_Action="Remove"', 12) | ||||
|         c.event("[Progress2]", "removes", 'MaintenanceForm_Action="Remove"', 13) | ||||
|         c.event("Remove", "ALL", 'MaintenanceForm_Action="Remove"', 14) | ||||
| 
 | ||||
|         # Close dialog when maintenance action scheduled | ||||
|         c.event("EndDialog", "Return", 'MaintenanceForm_Action<>"Change"', 20) | ||||
|         #c.event("NewDialog", "SelectFeaturesDlg", 'MaintenanceForm_Action="Change"', 21) | ||||
| 
 | ||||
|         maint.cancel("Cancel", "RepairRadioGroup").event("SpawnDialog", "CancelDlg") | ||||
| 
 | ||||
|     def get_installer_filename(self, fullname): | ||||
|         # Factored out to allow overriding in subclasses | ||||
|         if self.target_version: | ||||
|             base_name = "%s.%s-py%s.msi" % (fullname, self.plat_name, | ||||
|                                             self.target_version) | ||||
|         else: | ||||
|             base_name = "%s.%s.msi" % (fullname, self.plat_name) | ||||
|         installer_name = os.path.join(self.dist_dir, base_name) | ||||
|         return installer_name | ||||
							
								
								
									
										342
									
								
								Lib/packaging/command/bdist_wininst.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										342
									
								
								Lib/packaging/command/bdist_wininst.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,342 @@ | |||
| """Create an executable installer for Windows.""" | ||||
| 
 | ||||
| # FIXME synchronize bytes/str use with same file in distutils | ||||
| 
 | ||||
| import sys | ||||
| import os | ||||
| 
 | ||||
| from shutil import rmtree | ||||
| from sysconfig import get_python_version | ||||
| from packaging.command.cmd import Command | ||||
| from packaging.errors import PackagingOptionError, PackagingPlatformError | ||||
| from packaging import logger | ||||
| from packaging.util import get_platform | ||||
| 
 | ||||
| 
 | ||||
| class bdist_wininst(Command): | ||||
| 
 | ||||
|     description = "create an executable installer for Windows" | ||||
| 
 | ||||
|     user_options = [('bdist-dir=', None, | ||||
|                      "temporary directory for creating the distribution"), | ||||
|                     ('plat-name=', 'p', | ||||
|                      "platform name to embed in generated filenames " | ||||
|                      "(default: %s)" % get_platform()), | ||||
|                     ('keep-temp', 'k', | ||||
|                      "keep the pseudo-installation tree around after " + | ||||
|                      "creating the distribution archive"), | ||||
|                     ('target-version=', None, | ||||
|                      "require a specific python version" + | ||||
|                      " on the target system"), | ||||
|                     ('no-target-compile', 'c', | ||||
|                      "do not compile .py to .pyc on the target system"), | ||||
|                     ('no-target-optimize', 'o', | ||||
|                      "do not compile .py to .pyo (optimized)" | ||||
|                      "on the target system"), | ||||
|                     ('dist-dir=', 'd', | ||||
|                      "directory to put final built distributions in"), | ||||
|                     ('bitmap=', 'b', | ||||
|                      "bitmap to use for the installer instead of python-powered logo"), | ||||
|                     ('title=', 't', | ||||
|                      "title to display on the installer background instead of default"), | ||||
|                     ('skip-build', None, | ||||
|                      "skip rebuilding everything (for testing/debugging)"), | ||||
|                     ('install-script=', None, | ||||
|                      "basename of installation script to be run after" | ||||
|                      "installation or before deinstallation"), | ||||
|                     ('pre-install-script=', None, | ||||
|                      "Fully qualified filename of a script to be run before " | ||||
|                      "any files are installed.  This script need not be in the " | ||||
|                      "distribution"), | ||||
|                     ('user-access-control=', None, | ||||
|                      "specify Vista's UAC handling - 'none'/default=no " | ||||
|                      "handling, 'auto'=use UAC if target Python installed for " | ||||
|                      "all users, 'force'=always use UAC"), | ||||
|                    ] | ||||
| 
 | ||||
|     boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize', | ||||
|                        'skip-build'] | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.bdist_dir = None | ||||
|         self.plat_name = None | ||||
|         self.keep_temp = False | ||||
|         self.no_target_compile = False | ||||
|         self.no_target_optimize = False | ||||
|         self.target_version = None | ||||
|         self.dist_dir = None | ||||
|         self.bitmap = None | ||||
|         self.title = None | ||||
|         self.skip_build = False | ||||
|         self.install_script = None | ||||
|         self.pre_install_script = None | ||||
|         self.user_access_control = None | ||||
| 
 | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         if self.bdist_dir is None: | ||||
|             if self.skip_build and self.plat_name: | ||||
|                 # If build is skipped and plat_name is overridden, bdist will | ||||
|                 # not see the correct 'plat_name' - so set that up manually. | ||||
|                 bdist = self.distribution.get_command_obj('bdist') | ||||
|                 bdist.plat_name = self.plat_name | ||||
|                 # next the command will be initialized using that name | ||||
|             bdist_base = self.get_finalized_command('bdist').bdist_base | ||||
|             self.bdist_dir = os.path.join(bdist_base, 'wininst') | ||||
|         if not self.target_version: | ||||
|             self.target_version = "" | ||||
|         if not self.skip_build and self.distribution.has_ext_modules(): | ||||
|             short_version = get_python_version() | ||||
|             if self.target_version and self.target_version != short_version: | ||||
|                 raise PackagingOptionError("target version can only be %s, or the '--skip-build'" \ | ||||
|                       " option must be specified" % (short_version,)) | ||||
|             self.target_version = short_version | ||||
| 
 | ||||
|         self.set_undefined_options('bdist', 'dist_dir', 'plat_name') | ||||
| 
 | ||||
|         if self.install_script: | ||||
|             for script in self.distribution.scripts: | ||||
|                 if self.install_script == os.path.basename(script): | ||||
|                     break | ||||
|             else: | ||||
|                 raise PackagingOptionError("install_script '%s' not found in scripts" % \ | ||||
|                       self.install_script) | ||||
| 
 | ||||
|     def run(self): | ||||
|         if (sys.platform != "win32" and | ||||
|             (self.distribution.has_ext_modules() or | ||||
|              self.distribution.has_c_libraries())): | ||||
|             raise PackagingPlatformError \ | ||||
|                   ("distribution contains extensions and/or C libraries; " | ||||
|                    "must be compiled on a Windows 32 platform") | ||||
| 
 | ||||
|         if not self.skip_build: | ||||
|             self.run_command('build') | ||||
| 
 | ||||
|         install = self.get_reinitialized_command('install', | ||||
|                                                  reinit_subcommands=True) | ||||
|         install.root = self.bdist_dir | ||||
|         install.skip_build = self.skip_build | ||||
|         install.warn_dir = False | ||||
|         install.plat_name = self.plat_name | ||||
| 
 | ||||
|         install_lib = self.get_reinitialized_command('install_lib') | ||||
|         # we do not want to include pyc or pyo files | ||||
|         install_lib.compile = False | ||||
|         install_lib.optimize = 0 | ||||
| 
 | ||||
|         if self.distribution.has_ext_modules(): | ||||
|             # If we are building an installer for a Python version other | ||||
|             # than the one we are currently running, then we need to ensure | ||||
|             # our build_lib reflects the other Python version rather than ours. | ||||
|             # Note that for target_version!=sys.version, we must have skipped the | ||||
|             # build step, so there is no issue with enforcing the build of this | ||||
|             # version. | ||||
|             target_version = self.target_version | ||||
|             if not target_version: | ||||
|                 assert self.skip_build, "Should have already checked this" | ||||
|                 target_version = sys.version[0:3] | ||||
|             plat_specifier = ".%s-%s" % (self.plat_name, target_version) | ||||
|             build = self.get_finalized_command('build') | ||||
|             build.build_lib = os.path.join(build.build_base, | ||||
|                                            'lib' + plat_specifier) | ||||
| 
 | ||||
|         # Use a custom scheme for the zip-file, because we have to decide | ||||
|         # at installation time which scheme to use. | ||||
|         for key in ('purelib', 'platlib', 'headers', 'scripts', 'data'): | ||||
|             value = key.upper() | ||||
|             if key == 'headers': | ||||
|                 value = value + '/Include/$dist_name' | ||||
|             setattr(install, | ||||
|                     'install_' + key, | ||||
|                     value) | ||||
| 
 | ||||
|         logger.info("installing to %s", self.bdist_dir) | ||||
|         install.ensure_finalized() | ||||
| 
 | ||||
|         # avoid warning of 'install_lib' about installing | ||||
|         # into a directory not in sys.path | ||||
|         sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB')) | ||||
| 
 | ||||
|         install.run() | ||||
| 
 | ||||
|         del sys.path[0] | ||||
| 
 | ||||
|         # And make an archive relative to the root of the | ||||
|         # pseudo-installation tree. | ||||
|         from tempfile import NamedTemporaryFile | ||||
|         archive_basename = NamedTemporaryFile().name | ||||
|         fullname = self.distribution.get_fullname() | ||||
|         arcname = self.make_archive(archive_basename, "zip", | ||||
|                                     root_dir=self.bdist_dir) | ||||
|         # create an exe containing the zip-file | ||||
|         self.create_exe(arcname, fullname, self.bitmap) | ||||
|         if self.distribution.has_ext_modules(): | ||||
|             pyversion = get_python_version() | ||||
|         else: | ||||
|             pyversion = 'any' | ||||
|         self.distribution.dist_files.append(('bdist_wininst', pyversion, | ||||
|                                              self.get_installer_filename(fullname))) | ||||
|         # remove the zip-file again | ||||
|         logger.debug("removing temporary file '%s'", arcname) | ||||
|         os.remove(arcname) | ||||
| 
 | ||||
|         if not self.keep_temp: | ||||
|             if self.dry_run: | ||||
|                 logger.info('removing %s', self.bdist_dir) | ||||
|             else: | ||||
|                 rmtree(self.bdist_dir) | ||||
| 
 | ||||
|     def get_inidata(self): | ||||
|         # Return data describing the installation. | ||||
| 
 | ||||
|         lines = [] | ||||
|         metadata = self.distribution.metadata | ||||
| 
 | ||||
|         # Write the [metadata] section. | ||||
|         lines.append("[metadata]") | ||||
| 
 | ||||
|         # 'info' will be displayed in the installer's dialog box, | ||||
|         # describing the items to be installed. | ||||
|         info = (metadata.long_description or '') + '\n' | ||||
| 
 | ||||
|         # Escape newline characters | ||||
|         def escape(s): | ||||
|             return s.replace("\n", "\\n") | ||||
| 
 | ||||
|         for name in ["author", "author_email", "description", "maintainer", | ||||
|                      "maintainer_email", "name", "url", "version"]: | ||||
|             data = getattr(metadata, name, "") | ||||
|             if data: | ||||
|                 info = info + ("\n    %s: %s" % \ | ||||
|                                (name.capitalize(), escape(data))) | ||||
|                 lines.append("%s=%s" % (name, escape(data))) | ||||
| 
 | ||||
|         # The [setup] section contains entries controlling | ||||
|         # the installer runtime. | ||||
|         lines.append("\n[Setup]") | ||||
|         if self.install_script: | ||||
|             lines.append("install_script=%s" % self.install_script) | ||||
|         lines.append("info=%s" % escape(info)) | ||||
|         lines.append("target_compile=%d" % (not self.no_target_compile)) | ||||
|         lines.append("target_optimize=%d" % (not self.no_target_optimize)) | ||||
|         if self.target_version: | ||||
|             lines.append("target_version=%s" % self.target_version) | ||||
|         if self.user_access_control: | ||||
|             lines.append("user_access_control=%s" % self.user_access_control) | ||||
| 
 | ||||
|         title = self.title or self.distribution.get_fullname() | ||||
|         lines.append("title=%s" % escape(title)) | ||||
|         import time | ||||
|         import packaging | ||||
|         build_info = "Built %s with packaging-%s" % \ | ||||
|                      (time.ctime(time.time()), packaging.__version__) | ||||
|         lines.append("build_info=%s" % build_info) | ||||
|         return "\n".join(lines) | ||||
| 
 | ||||
|     def create_exe(self, arcname, fullname, bitmap=None): | ||||
|         import struct | ||||
| 
 | ||||
|         self.mkpath(self.dist_dir) | ||||
| 
 | ||||
|         cfgdata = self.get_inidata() | ||||
| 
 | ||||
|         installer_name = self.get_installer_filename(fullname) | ||||
|         logger.info("creating %s", installer_name) | ||||
| 
 | ||||
|         if bitmap: | ||||
|             with open(bitmap, "rb") as fp: | ||||
|                 bitmapdata = fp.read() | ||||
|             bitmaplen = len(bitmapdata) | ||||
|         else: | ||||
|             bitmaplen = 0 | ||||
| 
 | ||||
|         with open(installer_name, "wb") as file: | ||||
|             file.write(self.get_exe_bytes()) | ||||
|             if bitmap: | ||||
|                 file.write(bitmapdata) | ||||
| 
 | ||||
|             # Convert cfgdata from unicode to ascii, mbcs encoded | ||||
|             if isinstance(cfgdata, str): | ||||
|                 cfgdata = cfgdata.encode("mbcs") | ||||
| 
 | ||||
|             # Append the pre-install script | ||||
|             cfgdata = cfgdata + "\0" | ||||
|             if self.pre_install_script: | ||||
|                 with open(self.pre_install_script) as fp: | ||||
|                     script_data = fp.read() | ||||
|                 cfgdata = cfgdata + script_data + "\n\0" | ||||
|             else: | ||||
|                 # empty pre-install script | ||||
|                 cfgdata = cfgdata + "\0" | ||||
|             file.write(cfgdata) | ||||
| 
 | ||||
|             # The 'magic number' 0x1234567B is used to make sure that the | ||||
|             # binary layout of 'cfgdata' is what the wininst.exe binary | ||||
|             # expects.  If the layout changes, increment that number, make | ||||
|             # the corresponding changes to the wininst.exe sources, and | ||||
|             # recompile them. | ||||
|             header = struct.pack("<iii", | ||||
|                                  0x1234567B,       # tag | ||||
|                                  len(cfgdata),     # length | ||||
|                                  bitmaplen,        # number of bytes in bitmap | ||||
|                                  ) | ||||
|             file.write(header) | ||||
|             with open(arcname, "rb") as fp: | ||||
|                 file.write(fp.read()) | ||||
| 
 | ||||
|     def get_installer_filename(self, fullname): | ||||
|         # Factored out to allow overriding in subclasses | ||||
|         if self.target_version: | ||||
|             # if we create an installer for a specific python version, | ||||
|             # it's better to include this in the name | ||||
|             installer_name = os.path.join(self.dist_dir, | ||||
|                                           "%s.%s-py%s.exe" % | ||||
|                                            (fullname, self.plat_name, self.target_version)) | ||||
|         else: | ||||
|             installer_name = os.path.join(self.dist_dir, | ||||
|                                           "%s.%s.exe" % (fullname, self.plat_name)) | ||||
|         return installer_name | ||||
| 
 | ||||
|     def get_exe_bytes(self): | ||||
|         from packaging.compiler.msvccompiler import get_build_version | ||||
|         # If a target-version other than the current version has been | ||||
|         # specified, then using the MSVC version from *this* build is no good. | ||||
|         # Without actually finding and executing the target version and parsing | ||||
|         # its sys.version, we just hard-code our knowledge of old versions. | ||||
|         # NOTE: Possible alternative is to allow "--target-version" to | ||||
|         # specify a Python executable rather than a simple version string. | ||||
|         # We can then execute this program to obtain any info we need, such | ||||
|         # as the real sys.version string for the build. | ||||
|         cur_version = get_python_version() | ||||
|         if self.target_version and self.target_version != cur_version: | ||||
|             # If the target version is *later* than us, then we assume they | ||||
|             # use what we use | ||||
|             # string compares seem wrong, but are what sysconfig.py itself uses | ||||
|             if self.target_version > cur_version: | ||||
|                 bv = get_build_version() | ||||
|             else: | ||||
|                 if self.target_version < "2.4": | ||||
|                     bv = 6.0 | ||||
|                 else: | ||||
|                     bv = 7.1 | ||||
|         else: | ||||
|             # for current version - use authoritative check. | ||||
|             bv = get_build_version() | ||||
| 
 | ||||
|         # wininst-x.y.exe is in the same directory as this file | ||||
|         directory = os.path.dirname(__file__) | ||||
|         # we must use a wininst-x.y.exe built with the same C compiler | ||||
|         # used for python.  XXX What about mingw, borland, and so on? | ||||
| 
 | ||||
|         # if plat_name starts with "win" but is not "win32" | ||||
|         # we want to strip "win" and leave the rest (e.g. -amd64) | ||||
|         # for all other cases, we don't want any suffix | ||||
|         if self.plat_name != 'win32' and self.plat_name[:3] == 'win': | ||||
|             sfix = self.plat_name[3:] | ||||
|         else: | ||||
|             sfix = '' | ||||
| 
 | ||||
|         filename = os.path.join(directory, "wininst-%.1f%s.exe" % (bv, sfix)) | ||||
|         with open(filename, "rb") as fp: | ||||
|             return fp.read() | ||||
							
								
								
									
										151
									
								
								Lib/packaging/command/build.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								Lib/packaging/command/build.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,151 @@ | |||
| """Main build command, which calls the other build_* commands.""" | ||||
| 
 | ||||
| import sys | ||||
| import os | ||||
| 
 | ||||
| from packaging.util import get_platform | ||||
| from packaging.command.cmd import Command | ||||
| from packaging.errors import PackagingOptionError | ||||
| from packaging.compiler import show_compilers | ||||
| 
 | ||||
| 
 | ||||
| class build(Command): | ||||
| 
 | ||||
|     description = "build everything needed to install" | ||||
| 
 | ||||
|     user_options = [ | ||||
|         ('build-base=', 'b', | ||||
|          "base directory for build library"), | ||||
|         ('build-purelib=', None, | ||||
|          "build directory for platform-neutral distributions"), | ||||
|         ('build-platlib=', None, | ||||
|          "build directory for platform-specific distributions"), | ||||
|         ('build-lib=', None, | ||||
|          "build directory for all distribution (defaults to either " + | ||||
|          "build-purelib or build-platlib"), | ||||
|         ('build-scripts=', None, | ||||
|          "build directory for scripts"), | ||||
|         ('build-temp=', 't', | ||||
|          "temporary build directory"), | ||||
|         ('plat-name=', 'p', | ||||
|          "platform name to build for, if supported " | ||||
|          "(default: %s)" % get_platform()), | ||||
|         ('compiler=', 'c', | ||||
|          "specify the compiler type"), | ||||
|         ('debug', 'g', | ||||
|          "compile extensions and libraries with debugging information"), | ||||
|         ('force', 'f', | ||||
|          "forcibly build everything (ignore file timestamps)"), | ||||
|         ('executable=', 'e', | ||||
|          "specify final destination interpreter path (build.py)"), | ||||
|         ('use-2to3', None, | ||||
|          "use 2to3 to make source python 3.x compatible"), | ||||
|         ('convert-2to3-doctests', None, | ||||
|          "use 2to3 to convert doctests in seperate text files"), | ||||
|         ('use-2to3-fixers', None, | ||||
|          "list additional fixers opted for during 2to3 conversion"), | ||||
|         ] | ||||
| 
 | ||||
|     boolean_options = ['debug', 'force'] | ||||
| 
 | ||||
|     help_options = [ | ||||
|         ('help-compiler', None, | ||||
|          "list available compilers", show_compilers), | ||||
|         ] | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.build_base = 'build' | ||||
|         # these are decided only after 'build_base' has its final value | ||||
|         # (unless overridden by the user or client) | ||||
|         self.build_purelib = None | ||||
|         self.build_platlib = None | ||||
|         self.build_lib = None | ||||
|         self.build_temp = None | ||||
|         self.build_scripts = None | ||||
|         self.compiler = None | ||||
|         self.plat_name = None | ||||
|         self.debug = None | ||||
|         self.force = False | ||||
|         self.executable = None | ||||
|         self.use_2to3 = False | ||||
|         self.convert_2to3_doctests = None | ||||
|         self.use_2to3_fixers = None | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         if self.plat_name is None: | ||||
|             self.plat_name = get_platform() | ||||
|         else: | ||||
|             # plat-name only supported for windows (other platforms are | ||||
|             # supported via ./configure flags, if at all).  Avoid misleading | ||||
|             # other platforms. | ||||
|             if os.name != 'nt': | ||||
|                 raise PackagingOptionError( | ||||
|                             "--plat-name only supported on Windows (try " | ||||
|                             "using './configure --help' on your platform)") | ||||
| 
 | ||||
|         plat_specifier = ".%s-%s" % (self.plat_name, sys.version[0:3]) | ||||
| 
 | ||||
|         # Make it so Python 2.x and Python 2.x with --with-pydebug don't | ||||
|         # share the same build directories. Doing so confuses the build | ||||
|         # process for C modules | ||||
|         if hasattr(sys, 'gettotalrefcount'): | ||||
|             plat_specifier += '-pydebug' | ||||
| 
 | ||||
|         # 'build_purelib' and 'build_platlib' just default to 'lib' and | ||||
|         # 'lib.<plat>' under the base build directory.  We only use one of | ||||
|         # them for a given distribution, though -- | ||||
|         if self.build_purelib is None: | ||||
|             self.build_purelib = os.path.join(self.build_base, 'lib') | ||||
|         if self.build_platlib is None: | ||||
|             self.build_platlib = os.path.join(self.build_base, | ||||
|                                               'lib' + plat_specifier) | ||||
| 
 | ||||
|         # 'build_lib' is the actual directory that we will use for this | ||||
|         # particular module distribution -- if user didn't supply it, pick | ||||
|         # one of 'build_purelib' or 'build_platlib'. | ||||
|         if self.build_lib is None: | ||||
|             if self.distribution.ext_modules: | ||||
|                 self.build_lib = self.build_platlib | ||||
|             else: | ||||
|                 self.build_lib = self.build_purelib | ||||
| 
 | ||||
|         # 'build_temp' -- temporary directory for compiler turds, | ||||
|         # "build/temp.<plat>" | ||||
|         if self.build_temp is None: | ||||
|             self.build_temp = os.path.join(self.build_base, | ||||
|                                            'temp' + plat_specifier) | ||||
|         if self.build_scripts is None: | ||||
|             self.build_scripts = os.path.join(self.build_base, | ||||
|                                               'scripts-' + sys.version[0:3]) | ||||
| 
 | ||||
|         if self.executable is None: | ||||
|             self.executable = os.path.normpath(sys.executable) | ||||
| 
 | ||||
|     def run(self): | ||||
|         # Run all relevant sub-commands.  This will be some subset of: | ||||
|         #  - build_py      - pure Python modules | ||||
|         #  - build_clib    - standalone C libraries | ||||
|         #  - build_ext     - Python extension modules | ||||
|         #  - build_scripts - Python scripts | ||||
|         for cmd_name in self.get_sub_commands(): | ||||
|             self.run_command(cmd_name) | ||||
| 
 | ||||
|     # -- Predicates for the sub-command list --------------------------- | ||||
| 
 | ||||
|     def has_pure_modules(self): | ||||
|         return self.distribution.has_pure_modules() | ||||
| 
 | ||||
|     def has_c_libraries(self): | ||||
|         return self.distribution.has_c_libraries() | ||||
| 
 | ||||
|     def has_ext_modules(self): | ||||
|         return self.distribution.has_ext_modules() | ||||
| 
 | ||||
|     def has_scripts(self): | ||||
|         return self.distribution.has_scripts() | ||||
| 
 | ||||
|     sub_commands = [('build_py', has_pure_modules), | ||||
|                     ('build_clib', has_c_libraries), | ||||
|                     ('build_ext', has_ext_modules), | ||||
|                     ('build_scripts', has_scripts), | ||||
|                    ] | ||||
							
								
								
									
										198
									
								
								Lib/packaging/command/build_clib.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								Lib/packaging/command/build_clib.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,198 @@ | |||
| """Build C/C++ libraries. | ||||
| 
 | ||||
| This command is useful to build libraries that are included in the | ||||
| distribution and needed by extension modules. | ||||
| """ | ||||
| 
 | ||||
| # XXX this module has *lots* of code ripped-off quite transparently from | ||||
| # build_ext.py -- not surprisingly really, as the work required to build | ||||
| # a static library from a collection of C source files is not really all | ||||
| # that different from what's required to build a shared object file from | ||||
| # a collection of C source files.  Nevertheless, I haven't done the | ||||
| # necessary refactoring to account for the overlap in code between the | ||||
| # two modules, mainly because a number of subtle details changed in the | ||||
| # cut 'n paste.  Sigh. | ||||
| 
 | ||||
| import os | ||||
| from packaging.command.cmd import Command | ||||
| from packaging.errors import PackagingSetupError | ||||
| from packaging.compiler import customize_compiler | ||||
| from packaging import logger | ||||
| 
 | ||||
| 
 | ||||
| def show_compilers(): | ||||
|     from packaging.compiler import show_compilers | ||||
|     show_compilers() | ||||
| 
 | ||||
| 
 | ||||
| class build_clib(Command): | ||||
| 
 | ||||
|     description = "build C/C++ libraries used by extension modules" | ||||
| 
 | ||||
|     user_options = [ | ||||
|         ('build-clib=', 'b', | ||||
|          "directory to build C/C++ libraries to"), | ||||
|         ('build-temp=', 't', | ||||
|          "directory to put temporary build by-products"), | ||||
|         ('debug', 'g', | ||||
|          "compile with debugging information"), | ||||
|         ('force', 'f', | ||||
|          "forcibly build everything (ignore file timestamps)"), | ||||
|         ('compiler=', 'c', | ||||
|          "specify the compiler type"), | ||||
|         ] | ||||
| 
 | ||||
|     boolean_options = ['debug', 'force'] | ||||
| 
 | ||||
|     help_options = [ | ||||
|         ('help-compiler', None, | ||||
|          "list available compilers", show_compilers), | ||||
|         ] | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.build_clib = None | ||||
|         self.build_temp = None | ||||
| 
 | ||||
|         # List of libraries to build | ||||
|         self.libraries = None | ||||
| 
 | ||||
|         # Compilation options for all libraries | ||||
|         self.include_dirs = None | ||||
|         self.define = None | ||||
|         self.undef = None | ||||
|         self.debug = None | ||||
|         self.force = False | ||||
|         self.compiler = None | ||||
| 
 | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         # This might be confusing: both build-clib and build-temp default | ||||
|         # to build-temp as defined by the "build" command.  This is because | ||||
|         # I think that C libraries are really just temporary build | ||||
|         # by-products, at least from the point of view of building Python | ||||
|         # extensions -- but I want to keep my options open. | ||||
|         self.set_undefined_options('build', | ||||
|                                    ('build_temp', 'build_clib'), | ||||
|                                    ('build_temp', 'build_temp'), | ||||
|                                    'compiler', 'debug', 'force') | ||||
| 
 | ||||
|         self.libraries = self.distribution.libraries | ||||
|         if self.libraries: | ||||
|             self.check_library_list(self.libraries) | ||||
| 
 | ||||
|         if self.include_dirs is None: | ||||
|             self.include_dirs = self.distribution.include_dirs or [] | ||||
|         if isinstance(self.include_dirs, str): | ||||
|             self.include_dirs = self.include_dirs.split(os.pathsep) | ||||
| 
 | ||||
|         # XXX same as for build_ext -- what about 'self.define' and | ||||
|         # 'self.undef' ? | ||||
| 
 | ||||
|     def run(self): | ||||
|         if not self.libraries: | ||||
|             return | ||||
| 
 | ||||
|         # Yech -- this is cut 'n pasted from build_ext.py! | ||||
|         from packaging.compiler import new_compiler | ||||
|         self.compiler = new_compiler(compiler=self.compiler, | ||||
|                                      dry_run=self.dry_run, | ||||
|                                      force=self.force) | ||||
|         customize_compiler(self.compiler) | ||||
| 
 | ||||
|         if self.include_dirs is not None: | ||||
|             self.compiler.set_include_dirs(self.include_dirs) | ||||
|         if self.define is not None: | ||||
|             # 'define' option is a list of (name,value) tuples | ||||
|             for name, value in self.define: | ||||
|                 self.compiler.define_macro(name, value) | ||||
|         if self.undef is not None: | ||||
|             for macro in self.undef: | ||||
|                 self.compiler.undefine_macro(macro) | ||||
| 
 | ||||
|         self.build_libraries(self.libraries) | ||||
| 
 | ||||
| 
 | ||||
|     def check_library_list(self, libraries): | ||||
|         """Ensure that the list of libraries is valid. | ||||
| 
 | ||||
|         `library` is presumably provided as a command option 'libraries'. | ||||
|         This method checks that it is a list of 2-tuples, where the tuples | ||||
|         are (library_name, build_info_dict). | ||||
| 
 | ||||
|         Raise PackagingSetupError if the structure is invalid anywhere; | ||||
|         just returns otherwise. | ||||
|         """ | ||||
|         if not isinstance(libraries, list): | ||||
|             raise PackagingSetupError("'libraries' option must be a list of tuples") | ||||
| 
 | ||||
|         for lib in libraries: | ||||
|             if not isinstance(lib, tuple) and len(lib) != 2: | ||||
|                 raise PackagingSetupError("each element of 'libraries' must a 2-tuple") | ||||
| 
 | ||||
|             name, build_info = lib | ||||
| 
 | ||||
|             if not isinstance(name, str): | ||||
|                 raise PackagingSetupError("first element of each tuple in 'libraries' " + \ | ||||
|                       "must be a string (the library name)") | ||||
|             if '/' in name or (os.sep != '/' and os.sep in name): | ||||
|                 raise PackagingSetupError(("bad library name '%s': " + | ||||
|                        "may not contain directory separators") % \ | ||||
|                       lib[0]) | ||||
| 
 | ||||
|             if not isinstance(build_info, dict): | ||||
|                 raise PackagingSetupError("second element of each tuple in 'libraries' " + \ | ||||
|                       "must be a dictionary (build info)") | ||||
| 
 | ||||
|     def get_library_names(self): | ||||
|         # Assume the library list is valid -- 'check_library_list()' is | ||||
|         # called from 'finalize_options()', so it should be! | ||||
|         if not self.libraries: | ||||
|             return None | ||||
| 
 | ||||
|         lib_names = [] | ||||
|         for lib_name, build_info in self.libraries: | ||||
|             lib_names.append(lib_name) | ||||
|         return lib_names | ||||
| 
 | ||||
| 
 | ||||
|     def get_source_files(self): | ||||
|         self.check_library_list(self.libraries) | ||||
|         filenames = [] | ||||
|         for lib_name, build_info in self.libraries: | ||||
|             sources = build_info.get('sources') | ||||
|             if sources is None or not isinstance(sources, (list, tuple)): | ||||
|                 raise PackagingSetupError(("in 'libraries' option (library '%s'), " | ||||
|                        "'sources' must be present and must be " | ||||
|                        "a list of source filenames") % lib_name) | ||||
| 
 | ||||
|             filenames.extend(sources) | ||||
|         return filenames | ||||
| 
 | ||||
|     def build_libraries(self, libraries): | ||||
|         for lib_name, build_info in libraries: | ||||
|             sources = build_info.get('sources') | ||||
|             if sources is None or not isinstance(sources, (list, tuple)): | ||||
|                 raise PackagingSetupError(("in 'libraries' option (library '%s'), " + | ||||
|                        "'sources' must be present and must be " + | ||||
|                        "a list of source filenames") % lib_name) | ||||
|             sources = list(sources) | ||||
| 
 | ||||
|             logger.info("building '%s' library", lib_name) | ||||
| 
 | ||||
|             # First, compile the source code to object files in the library | ||||
|             # directory.  (This should probably change to putting object | ||||
|             # files in a temporary build directory.) | ||||
|             macros = build_info.get('macros') | ||||
|             include_dirs = build_info.get('include_dirs') | ||||
|             objects = self.compiler.compile(sources, | ||||
|                                             output_dir=self.build_temp, | ||||
|                                             macros=macros, | ||||
|                                             include_dirs=include_dirs, | ||||
|                                             debug=self.debug) | ||||
| 
 | ||||
|             # Now "link" the object files together into a static library. | ||||
|             # (On Unix at least, this isn't really linking -- it just | ||||
|             # builds an archive.  Whatever.) | ||||
|             self.compiler.create_static_lib(objects, lib_name, | ||||
|                                             output_dir=self.build_clib, | ||||
|                                             debug=self.debug) | ||||
							
								
								
									
										666
									
								
								Lib/packaging/command/build_ext.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										666
									
								
								Lib/packaging/command/build_ext.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,666 @@ | |||
| """Build extension modules.""" | ||||
| 
 | ||||
| # FIXME Is this module limited to C extensions or do C++ extensions work too? | ||||
| # The docstring of this module said that C++ was not supported, but other | ||||
| # comments contradict that. | ||||
| 
 | ||||
| import os | ||||
| import re | ||||
| import sys | ||||
| import logging | ||||
| import sysconfig | ||||
| 
 | ||||
| from packaging.util import get_platform | ||||
| from packaging.command.cmd import Command | ||||
| from packaging.errors import (CCompilerError, CompileError, PackagingError, | ||||
|                                PackagingPlatformError, PackagingSetupError) | ||||
| from packaging.compiler import customize_compiler, show_compilers | ||||
| from packaging.util import newer_group | ||||
| from packaging.compiler.extension import Extension | ||||
| from packaging import logger | ||||
| 
 | ||||
| import site | ||||
| HAS_USER_SITE = True | ||||
| 
 | ||||
| if os.name == 'nt': | ||||
|     from packaging.compiler.msvccompiler import get_build_version | ||||
|     MSVC_VERSION = int(get_build_version()) | ||||
| 
 | ||||
| # An extension name is just a dot-separated list of Python NAMEs (ie. | ||||
| # the same as a fully-qualified module name). | ||||
| extension_name_re = re.compile \ | ||||
|     (r'^[a-zA-Z_][a-zA-Z_0-9]*(\.[a-zA-Z_][a-zA-Z_0-9]*)*$') | ||||
| 
 | ||||
| 
 | ||||
| class build_ext(Command): | ||||
| 
 | ||||
|     description = "build C/C++ extension modules (compile/link to build directory)" | ||||
| 
 | ||||
|     # XXX thoughts on how to deal with complex command-line options like | ||||
|     # these, i.e. how to make it so fancy_getopt can suck them off the | ||||
|     # command line and make it look like setup.py defined the appropriate | ||||
|     # lists of tuples of what-have-you. | ||||
|     #   - each command needs a callback to process its command-line options | ||||
|     #   - Command.__init__() needs access to its share of the whole | ||||
|     #     command line (must ultimately come from | ||||
|     #     Distribution.parse_command_line()) | ||||
|     #   - it then calls the current command class' option-parsing | ||||
|     #     callback to deal with weird options like -D, which have to | ||||
|     #     parse the option text and churn out some custom data | ||||
|     #     structure | ||||
|     #   - that data structure (in this case, a list of 2-tuples) | ||||
|     #     will then be present in the command object by the time | ||||
|     #     we get to finalize_options() (i.e. the constructor | ||||
|     #     takes care of both command-line and client options | ||||
|     #     in between initialize_options() and finalize_options()) | ||||
| 
 | ||||
|     sep_by = " (separated by '%s')" % os.pathsep | ||||
|     user_options = [ | ||||
|         ('build-lib=', 'b', | ||||
|          "directory for compiled extension modules"), | ||||
|         ('build-temp=', 't', | ||||
|          "directory for temporary files (build by-products)"), | ||||
|         ('plat-name=', 'p', | ||||
|          "platform name to cross-compile for, if supported " | ||||
|          "(default: %s)" % get_platform()), | ||||
|         ('inplace', 'i', | ||||
|          "ignore build-lib and put compiled extensions into the source " + | ||||
|          "directory alongside your pure Python modules"), | ||||
|         ('include-dirs=', 'I', | ||||
|          "list of directories to search for header files" + sep_by), | ||||
|         ('define=', 'D', | ||||
|          "C preprocessor macros to define"), | ||||
|         ('undef=', 'U', | ||||
|          "C preprocessor macros to undefine"), | ||||
|         ('libraries=', 'l', | ||||
|          "external C libraries to link with"), | ||||
|         ('library-dirs=', 'L', | ||||
|          "directories to search for external C libraries" + sep_by), | ||||
|         ('rpath=', 'R', | ||||
|          "directories to search for shared C libraries at runtime"), | ||||
|         ('link-objects=', 'O', | ||||
|          "extra explicit link objects to include in the link"), | ||||
|         ('debug', 'g', | ||||
|          "compile/link with debugging information"), | ||||
|         ('force', 'f', | ||||
|          "forcibly build everything (ignore file timestamps)"), | ||||
|         ('compiler=', 'c', | ||||
|          "specify the compiler type"), | ||||
|         ('swig-opts=', None, | ||||
|          "list of SWIG command-line options"), | ||||
|         ('swig=', None, | ||||
|          "path to the SWIG executable"), | ||||
|         ] | ||||
| 
 | ||||
|     boolean_options = ['inplace', 'debug', 'force'] | ||||
| 
 | ||||
|     if HAS_USER_SITE: | ||||
|         user_options.append(('user', None, | ||||
|                              "add user include, library and rpath")) | ||||
|         boolean_options.append('user') | ||||
| 
 | ||||
|     help_options = [ | ||||
|         ('help-compiler', None, | ||||
|          "list available compilers", show_compilers), | ||||
|         ] | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.extensions = None | ||||
|         self.build_lib = None | ||||
|         self.plat_name = None | ||||
|         self.build_temp = None | ||||
|         self.inplace = False | ||||
|         self.package = None | ||||
| 
 | ||||
|         self.include_dirs = None | ||||
|         self.define = None | ||||
|         self.undef = None | ||||
|         self.libraries = None | ||||
|         self.library_dirs = None | ||||
|         self.rpath = None | ||||
|         self.link_objects = None | ||||
|         self.debug = None | ||||
|         self.force = None | ||||
|         self.compiler = None | ||||
|         self.swig = None | ||||
|         self.swig_opts = None | ||||
|         if HAS_USER_SITE: | ||||
|             self.user = None | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         self.set_undefined_options('build', | ||||
|                                    'build_lib', 'build_temp', 'compiler', | ||||
|                                    'debug', 'force', 'plat_name') | ||||
| 
 | ||||
|         if self.package is None: | ||||
|             self.package = self.distribution.ext_package | ||||
| 
 | ||||
|         # Ensure that the list of extensions is valid, i.e. it is a list of | ||||
|         # Extension objects. | ||||
|         self.extensions = self.distribution.ext_modules | ||||
|         if self.extensions: | ||||
|             if not isinstance(self.extensions, (list, tuple)): | ||||
|                 type_name = (self.extensions is None and 'None' | ||||
|                             or type(self.extensions).__name__) | ||||
|                 raise PackagingSetupError( | ||||
|                     "'ext_modules' must be a sequence of Extension instances," | ||||
|                     " not %s" % (type_name,)) | ||||
|             for i, ext in enumerate(self.extensions): | ||||
|                 if isinstance(ext, Extension): | ||||
|                     continue                # OK! (assume type-checking done | ||||
|                                             # by Extension constructor) | ||||
|                 type_name = (ext is None and 'None' or type(ext).__name__) | ||||
|                 raise PackagingSetupError( | ||||
|                     "'ext_modules' item %d must be an Extension instance," | ||||
|                     " not %s" % (i, type_name)) | ||||
| 
 | ||||
|         # Make sure Python's include directories (for Python.h, pyconfig.h, | ||||
|         # etc.) are in the include search path. | ||||
|         py_include = sysconfig.get_path('include') | ||||
|         plat_py_include = sysconfig.get_path('platinclude') | ||||
|         if self.include_dirs is None: | ||||
|             self.include_dirs = self.distribution.include_dirs or [] | ||||
|         if isinstance(self.include_dirs, str): | ||||
|             self.include_dirs = self.include_dirs.split(os.pathsep) | ||||
| 
 | ||||
|         # Put the Python "system" include dir at the end, so that | ||||
|         # any local include dirs take precedence. | ||||
|         self.include_dirs.append(py_include) | ||||
|         if plat_py_include != py_include: | ||||
|             self.include_dirs.append(plat_py_include) | ||||
| 
 | ||||
|         if isinstance(self.libraries, str): | ||||
|             self.libraries = [self.libraries] | ||||
| 
 | ||||
|         # Life is easier if we're not forever checking for None, so | ||||
|         # simplify these options to empty lists if unset | ||||
|         if self.libraries is None: | ||||
|             self.libraries = [] | ||||
|         if self.library_dirs is None: | ||||
|             self.library_dirs = [] | ||||
|         elif isinstance(self.library_dirs, str): | ||||
|             self.library_dirs = self.library_dirs.split(os.pathsep) | ||||
| 
 | ||||
|         if self.rpath is None: | ||||
|             self.rpath = [] | ||||
|         elif isinstance(self.rpath, str): | ||||
|             self.rpath = self.rpath.split(os.pathsep) | ||||
| 
 | ||||
|         # for extensions under windows use different directories | ||||
|         # for Release and Debug builds. | ||||
|         # also Python's library directory must be appended to library_dirs | ||||
|         if os.name == 'nt': | ||||
|             # the 'libs' directory is for binary installs - we assume that | ||||
|             # must be the *native* platform.  But we don't really support | ||||
|             # cross-compiling via a binary install anyway, so we let it go. | ||||
|             self.library_dirs.append(os.path.join(sys.exec_prefix, 'libs')) | ||||
|             if self.debug: | ||||
|                 self.build_temp = os.path.join(self.build_temp, "Debug") | ||||
|             else: | ||||
|                 self.build_temp = os.path.join(self.build_temp, "Release") | ||||
| 
 | ||||
|             # Append the source distribution include and library directories, | ||||
|             # this allows distutils on windows to work in the source tree | ||||
|             self.include_dirs.append(os.path.join(sys.exec_prefix, 'PC')) | ||||
|             if MSVC_VERSION == 9: | ||||
|                 # Use the .lib files for the correct architecture | ||||
|                 if self.plat_name == 'win32': | ||||
|                     suffix = '' | ||||
|                 else: | ||||
|                     # win-amd64 or win-ia64 | ||||
|                     suffix = self.plat_name[4:] | ||||
|                 new_lib = os.path.join(sys.exec_prefix, 'PCbuild') | ||||
|                 if suffix: | ||||
|                     new_lib = os.path.join(new_lib, suffix) | ||||
|                 self.library_dirs.append(new_lib) | ||||
| 
 | ||||
|             elif MSVC_VERSION == 8: | ||||
|                 self.library_dirs.append(os.path.join(sys.exec_prefix, | ||||
|                                          'PC', 'VS8.0')) | ||||
|             elif MSVC_VERSION == 7: | ||||
|                 self.library_dirs.append(os.path.join(sys.exec_prefix, | ||||
|                                          'PC', 'VS7.1')) | ||||
|             else: | ||||
|                 self.library_dirs.append(os.path.join(sys.exec_prefix, | ||||
|                                          'PC', 'VC6')) | ||||
| 
 | ||||
|         # OS/2 (EMX) doesn't support Debug vs Release builds, but has the | ||||
|         # import libraries in its "Config" subdirectory | ||||
|         if os.name == 'os2': | ||||
|             self.library_dirs.append(os.path.join(sys.exec_prefix, 'Config')) | ||||
| 
 | ||||
|         # for extensions under Cygwin and AtheOS Python's library directory must be | ||||
|         # appended to library_dirs | ||||
|         if sys.platform[:6] == 'cygwin' or sys.platform[:6] == 'atheos': | ||||
|             if sys.executable.startswith(os.path.join(sys.exec_prefix, "bin")): | ||||
|                 # building third party extensions | ||||
|                 self.library_dirs.append(os.path.join(sys.prefix, "lib", | ||||
|                                   "python" + sysconfig.get_python_version(), | ||||
|                                                       "config")) | ||||
|             else: | ||||
|                 # building python standard extensions | ||||
|                 self.library_dirs.append(os.curdir) | ||||
| 
 | ||||
|         # for extensions under Linux or Solaris with a shared Python library, | ||||
|         # Python's library directory must be appended to library_dirs | ||||
|         sysconfig.get_config_var('Py_ENABLE_SHARED') | ||||
|         if ((sys.platform.startswith('linux') or sys.platform.startswith('gnu') | ||||
|              or sys.platform.startswith('sunos')) | ||||
|             and sysconfig.get_config_var('Py_ENABLE_SHARED')): | ||||
|             if sys.executable.startswith(os.path.join(sys.exec_prefix, "bin")): | ||||
|                 # building third party extensions | ||||
|                 self.library_dirs.append(sysconfig.get_config_var('LIBDIR')) | ||||
|             else: | ||||
|                 # building python standard extensions | ||||
|                 self.library_dirs.append(os.curdir) | ||||
| 
 | ||||
|         # The argument parsing will result in self.define being a string, but | ||||
|         # it has to be a list of 2-tuples.  All the preprocessor symbols | ||||
|         # specified by the 'define' option will be set to '1'.  Multiple | ||||
|         # symbols can be separated with commas. | ||||
| 
 | ||||
|         if self.define: | ||||
|             defines = self.define.split(',') | ||||
|             self.define = [(symbol, '1') for symbol in defines] | ||||
| 
 | ||||
|         # The option for macros to undefine is also a string from the | ||||
|         # option parsing, but has to be a list.  Multiple symbols can also | ||||
|         # be separated with commas here. | ||||
|         if self.undef: | ||||
|             self.undef = self.undef.split(',') | ||||
| 
 | ||||
|         if self.swig_opts is None: | ||||
|             self.swig_opts = [] | ||||
|         else: | ||||
|             self.swig_opts = self.swig_opts.split(' ') | ||||
| 
 | ||||
|         # Finally add the user include and library directories if requested | ||||
|         if HAS_USER_SITE and self.user: | ||||
|             user_include = os.path.join(site.USER_BASE, "include") | ||||
|             user_lib = os.path.join(site.USER_BASE, "lib") | ||||
|             if os.path.isdir(user_include): | ||||
|                 self.include_dirs.append(user_include) | ||||
|             if os.path.isdir(user_lib): | ||||
|                 self.library_dirs.append(user_lib) | ||||
|                 self.rpath.append(user_lib) | ||||
| 
 | ||||
|     def run(self): | ||||
|         from packaging.compiler import new_compiler | ||||
| 
 | ||||
|         # 'self.extensions', as supplied by setup.py, is a list of | ||||
|         # Extension instances.  See the documentation for Extension (in | ||||
|         # distutils.extension) for details. | ||||
|         if not self.extensions: | ||||
|             return | ||||
| 
 | ||||
|         # If we were asked to build any C/C++ libraries, make sure that the | ||||
|         # directory where we put them is in the library search path for | ||||
|         # linking extensions. | ||||
|         if self.distribution.has_c_libraries(): | ||||
|             build_clib = self.get_finalized_command('build_clib') | ||||
|             self.libraries.extend(build_clib.get_library_names() or []) | ||||
|             self.library_dirs.append(build_clib.build_clib) | ||||
| 
 | ||||
|         # Temporary kludge until we remove the verbose arguments and use | ||||
|         # logging everywhere | ||||
|         verbose = logger.getEffectiveLevel() >= logging.DEBUG | ||||
| 
 | ||||
|         # Setup the CCompiler object that we'll use to do all the | ||||
|         # compiling and linking | ||||
|         self.compiler_obj = new_compiler(compiler=self.compiler, | ||||
|                                          verbose=verbose, | ||||
|                                          dry_run=self.dry_run, | ||||
|                                          force=self.force) | ||||
| 
 | ||||
|         customize_compiler(self.compiler_obj) | ||||
|         # If we are cross-compiling, init the compiler now (if we are not | ||||
|         # cross-compiling, init would not hurt, but people may rely on | ||||
|         # late initialization of compiler even if they shouldn't...) | ||||
|         if os.name == 'nt' and self.plat_name != get_platform(): | ||||
|             self.compiler_obj.initialize(self.plat_name) | ||||
| 
 | ||||
|         # And make sure that any compile/link-related options (which might | ||||
|         # come from the command line or from the setup script) are set in | ||||
|         # that CCompiler object -- that way, they automatically apply to | ||||
|         # all compiling and linking done here. | ||||
|         if self.include_dirs is not None: | ||||
|             self.compiler_obj.set_include_dirs(self.include_dirs) | ||||
|         if self.define is not None: | ||||
|             # 'define' option is a list of (name,value) tuples | ||||
|             for name, value in self.define: | ||||
|                 self.compiler_obj.define_macro(name, value) | ||||
|         if self.undef is not None: | ||||
|             for macro in self.undef: | ||||
|                 self.compiler_obj.undefine_macro(macro) | ||||
|         if self.libraries is not None: | ||||
|             self.compiler_obj.set_libraries(self.libraries) | ||||
|         if self.library_dirs is not None: | ||||
|             self.compiler_obj.set_library_dirs(self.library_dirs) | ||||
|         if self.rpath is not None: | ||||
|             self.compiler_obj.set_runtime_library_dirs(self.rpath) | ||||
|         if self.link_objects is not None: | ||||
|             self.compiler_obj.set_link_objects(self.link_objects) | ||||
| 
 | ||||
|         # Now actually compile and link everything. | ||||
|         self.build_extensions() | ||||
| 
 | ||||
|     def get_source_files(self): | ||||
|         filenames = [] | ||||
| 
 | ||||
|         # Wouldn't it be neat if we knew the names of header files too... | ||||
|         for ext in self.extensions: | ||||
|             filenames.extend(ext.sources) | ||||
| 
 | ||||
|         return filenames | ||||
| 
 | ||||
|     def get_outputs(self): | ||||
|         # And build the list of output (built) filenames.  Note that this | ||||
|         # ignores the 'inplace' flag, and assumes everything goes in the | ||||
|         # "build" tree. | ||||
|         outputs = [] | ||||
|         for ext in self.extensions: | ||||
|             outputs.append(self.get_ext_fullpath(ext.name)) | ||||
|         return outputs | ||||
| 
 | ||||
|     def build_extensions(self): | ||||
|         for ext in self.extensions: | ||||
|             try: | ||||
|                 self.build_extension(ext) | ||||
|             except (CCompilerError, PackagingError, CompileError) as e: | ||||
|                 if not ext.optional: | ||||
|                     raise | ||||
|                 logger.warning('%s: building extension %r failed: %s', | ||||
|                                self.get_command_name(), ext.name, e) | ||||
| 
 | ||||
|     def build_extension(self, ext): | ||||
|         sources = ext.sources | ||||
|         if sources is None or not isinstance(sources, (list, tuple)): | ||||
|             raise PackagingSetupError(("in 'ext_modules' option (extension '%s'), " + | ||||
|                    "'sources' must be present and must be " + | ||||
|                    "a list of source filenames") % ext.name) | ||||
|         sources = list(sources) | ||||
| 
 | ||||
|         ext_path = self.get_ext_fullpath(ext.name) | ||||
|         depends = sources + ext.depends | ||||
|         if not (self.force or newer_group(depends, ext_path, 'newer')): | ||||
|             logger.debug("skipping '%s' extension (up-to-date)", ext.name) | ||||
|             return | ||||
|         else: | ||||
|             logger.info("building '%s' extension", ext.name) | ||||
| 
 | ||||
|         # First, scan the sources for SWIG definition files (.i), run | ||||
|         # SWIG on 'em to create .c files, and modify the sources list | ||||
|         # accordingly. | ||||
|         sources = self.swig_sources(sources, ext) | ||||
| 
 | ||||
|         # Next, compile the source code to object files. | ||||
| 
 | ||||
|         # XXX not honouring 'define_macros' or 'undef_macros' -- the | ||||
|         # CCompiler API needs to change to accommodate this, and I | ||||
|         # want to do one thing at a time! | ||||
| 
 | ||||
|         # Two possible sources for extra compiler arguments: | ||||
|         #   - 'extra_compile_args' in Extension object | ||||
|         #   - CFLAGS environment variable (not particularly | ||||
|         #     elegant, but people seem to expect it and I | ||||
|         #     guess it's useful) | ||||
|         # The environment variable should take precedence, and | ||||
|         # any sensible compiler will give precedence to later | ||||
|         # command-line args.  Hence we combine them in order: | ||||
|         extra_args = ext.extra_compile_args or [] | ||||
| 
 | ||||
|         macros = ext.define_macros[:] | ||||
|         for undef in ext.undef_macros: | ||||
|             macros.append((undef,)) | ||||
| 
 | ||||
|         objects = self.compiler_obj.compile(sources, | ||||
|                                             output_dir=self.build_temp, | ||||
|                                             macros=macros, | ||||
|                                             include_dirs=ext.include_dirs, | ||||
|                                             debug=self.debug, | ||||
|                                             extra_postargs=extra_args, | ||||
|                                             depends=ext.depends) | ||||
| 
 | ||||
|         # XXX -- this is a Vile HACK! | ||||
|         # | ||||
|         # The setup.py script for Python on Unix needs to be able to | ||||
|         # get this list so it can perform all the clean up needed to | ||||
|         # avoid keeping object files around when cleaning out a failed | ||||
|         # build of an extension module.  Since Packaging does not | ||||
|         # track dependencies, we have to get rid of intermediates to | ||||
|         # ensure all the intermediates will be properly re-built. | ||||
|         # | ||||
|         self._built_objects = objects[:] | ||||
| 
 | ||||
|         # Now link the object files together into a "shared object" -- | ||||
|         # of course, first we have to figure out all the other things | ||||
|         # that go into the mix. | ||||
|         if ext.extra_objects: | ||||
|             objects.extend(ext.extra_objects) | ||||
|         extra_args = ext.extra_link_args or [] | ||||
| 
 | ||||
|         # Detect target language, if not provided | ||||
|         language = ext.language or self.compiler_obj.detect_language(sources) | ||||
| 
 | ||||
|         self.compiler_obj.link_shared_object( | ||||
|             objects, ext_path, | ||||
|             libraries=self.get_libraries(ext), | ||||
|             library_dirs=ext.library_dirs, | ||||
|             runtime_library_dirs=ext.runtime_library_dirs, | ||||
|             extra_postargs=extra_args, | ||||
|             export_symbols=self.get_export_symbols(ext), | ||||
|             debug=self.debug, | ||||
|             build_temp=self.build_temp, | ||||
|             target_lang=language) | ||||
| 
 | ||||
| 
 | ||||
|     def swig_sources(self, sources, extension): | ||||
|         """Walk the list of source files in 'sources', looking for SWIG | ||||
|         interface (.i) files.  Run SWIG on all that are found, and | ||||
|         return a modified 'sources' list with SWIG source files replaced | ||||
|         by the generated C (or C++) files. | ||||
|         """ | ||||
|         new_sources = [] | ||||
|         swig_sources = [] | ||||
|         swig_targets = {} | ||||
| 
 | ||||
|         # XXX this drops generated C/C++ files into the source tree, which | ||||
|         # is fine for developers who want to distribute the generated | ||||
|         # source -- but there should be an option to put SWIG output in | ||||
|         # the temp dir. | ||||
| 
 | ||||
|         if ('-c++' in self.swig_opts or '-c++' in extension.swig_opts): | ||||
|             target_ext = '.cpp' | ||||
|         else: | ||||
|             target_ext = '.c' | ||||
| 
 | ||||
|         for source in sources: | ||||
|             base, ext = os.path.splitext(source) | ||||
|             if ext == ".i":             # SWIG interface file | ||||
|                 new_sources.append(base + '_wrap' + target_ext) | ||||
|                 swig_sources.append(source) | ||||
|                 swig_targets[source] = new_sources[-1] | ||||
|             else: | ||||
|                 new_sources.append(source) | ||||
| 
 | ||||
|         if not swig_sources: | ||||
|             return new_sources | ||||
| 
 | ||||
|         swig = self.swig or self.find_swig() | ||||
|         swig_cmd = [swig, "-python"] | ||||
|         swig_cmd.extend(self.swig_opts) | ||||
| 
 | ||||
|         # Do not override commandline arguments | ||||
|         if not self.swig_opts: | ||||
|             for o in extension.swig_opts: | ||||
|                 swig_cmd.append(o) | ||||
| 
 | ||||
|         for source in swig_sources: | ||||
|             target = swig_targets[source] | ||||
|             logger.info("swigging %s to %s", source, target) | ||||
|             self.spawn(swig_cmd + ["-o", target, source]) | ||||
| 
 | ||||
|         return new_sources | ||||
| 
 | ||||
|     def find_swig(self): | ||||
|         """Return the name of the SWIG executable.  On Unix, this is | ||||
|         just "swig" -- it should be in the PATH.  Tries a bit harder on | ||||
|         Windows. | ||||
|         """ | ||||
| 
 | ||||
|         if os.name == "posix": | ||||
|             return "swig" | ||||
|         elif os.name == "nt": | ||||
| 
 | ||||
|             # Look for SWIG in its standard installation directory on | ||||
|             # Windows (or so I presume!).  If we find it there, great; | ||||
|             # if not, act like Unix and assume it's in the PATH. | ||||
|             for vers in ("1.3", "1.2", "1.1"): | ||||
|                 fn = os.path.join("c:\\swig%s" % vers, "swig.exe") | ||||
|                 if os.path.isfile(fn): | ||||
|                     return fn | ||||
|             else: | ||||
|                 return "swig.exe" | ||||
| 
 | ||||
|         elif os.name == "os2": | ||||
|             # assume swig available in the PATH. | ||||
|             return "swig.exe" | ||||
| 
 | ||||
|         else: | ||||
|             raise PackagingPlatformError(("I don't know how to find (much less run) SWIG " | ||||
|                    "on platform '%s'") % os.name) | ||||
| 
 | ||||
|     # -- Name generators ----------------------------------------------- | ||||
|     # (extension names, filenames, whatever) | ||||
|     def get_ext_fullpath(self, ext_name): | ||||
|         """Returns the path of the filename for a given extension. | ||||
| 
 | ||||
|         The file is located in `build_lib` or directly in the package | ||||
|         (inplace option). | ||||
|         """ | ||||
|         fullname = self.get_ext_fullname(ext_name) | ||||
|         modpath = fullname.split('.') | ||||
|         filename = self.get_ext_filename(modpath[-1]) | ||||
| 
 | ||||
|         if not self.inplace: | ||||
|             # no further work needed | ||||
|             # returning : | ||||
|             #   build_dir/package/path/filename | ||||
|             filename = os.path.join(*modpath[:-1]+[filename]) | ||||
|             return os.path.join(self.build_lib, filename) | ||||
| 
 | ||||
|         # the inplace option requires to find the package directory | ||||
|         # using the build_py command for that | ||||
|         package = '.'.join(modpath[0:-1]) | ||||
|         build_py = self.get_finalized_command('build_py') | ||||
|         package_dir = os.path.abspath(build_py.get_package_dir(package)) | ||||
| 
 | ||||
|         # returning | ||||
|         #   package_dir/filename | ||||
|         return os.path.join(package_dir, filename) | ||||
| 
 | ||||
|     def get_ext_fullname(self, ext_name): | ||||
|         """Returns the fullname of a given extension name. | ||||
| 
 | ||||
|         Adds the `package.` prefix""" | ||||
|         if self.package is None: | ||||
|             return ext_name | ||||
|         else: | ||||
|             return self.package + '.' + ext_name | ||||
| 
 | ||||
|     def get_ext_filename(self, ext_name): | ||||
|         r"""Convert the name of an extension (eg. "foo.bar") into the name | ||||
|         of the file from which it will be loaded (eg. "foo/bar.so", or | ||||
|         "foo\bar.pyd"). | ||||
|         """ | ||||
|         ext_path = ext_name.split('.') | ||||
|         # OS/2 has an 8 character module (extension) limit :-( | ||||
|         if os.name == "os2": | ||||
|             ext_path[len(ext_path) - 1] = ext_path[len(ext_path) - 1][:8] | ||||
|         # extensions in debug_mode are named 'module_d.pyd' under windows | ||||
|         so_ext = sysconfig.get_config_var('SO') | ||||
|         if os.name == 'nt' and self.debug: | ||||
|             return os.path.join(*ext_path) + '_d' + so_ext | ||||
|         return os.path.join(*ext_path) + so_ext | ||||
| 
 | ||||
|     def get_export_symbols(self, ext): | ||||
|         """Return the list of symbols that a shared extension has to | ||||
|         export.  This either uses 'ext.export_symbols' or, if it's not | ||||
|         provided, "init" + module_name.  Only relevant on Windows, where | ||||
|         the .pyd file (DLL) must export the module "init" function. | ||||
|         """ | ||||
|         initfunc_name = "init" + ext.name.split('.')[-1] | ||||
|         if initfunc_name not in ext.export_symbols: | ||||
|             ext.export_symbols.append(initfunc_name) | ||||
|         return ext.export_symbols | ||||
| 
 | ||||
|     def get_libraries(self, ext): | ||||
|         """Return the list of libraries to link against when building a | ||||
|         shared extension.  On most platforms, this is just 'ext.libraries'; | ||||
|         on Windows and OS/2, we add the Python library (eg. python20.dll). | ||||
|         """ | ||||
|         # The python library is always needed on Windows.  For MSVC, this | ||||
|         # is redundant, since the library is mentioned in a pragma in | ||||
|         # pyconfig.h that MSVC groks.  The other Windows compilers all seem | ||||
|         # to need it mentioned explicitly, though, so that's what we do. | ||||
|         # Append '_d' to the python import library on debug builds. | ||||
|         if sys.platform == "win32": | ||||
|             from packaging.compiler.msvccompiler import MSVCCompiler | ||||
|             if not isinstance(self.compiler_obj, MSVCCompiler): | ||||
|                 template = "python%d%d" | ||||
|                 if self.debug: | ||||
|                     template = template + '_d' | ||||
|                 pythonlib = (template % | ||||
|                        (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) | ||||
|                 # don't extend ext.libraries, it may be shared with other | ||||
|                 # extensions, it is a reference to the original list | ||||
|                 return ext.libraries + [pythonlib] | ||||
|             else: | ||||
|                 return ext.libraries | ||||
|         elif sys.platform == "os2emx": | ||||
|             # EMX/GCC requires the python library explicitly, and I | ||||
|             # believe VACPP does as well (though not confirmed) - AIM Apr01 | ||||
|             template = "python%d%d" | ||||
|             # debug versions of the main DLL aren't supported, at least | ||||
|             # not at this time - AIM Apr01 | ||||
|             #if self.debug: | ||||
|             #    template = template + '_d' | ||||
|             pythonlib = (template % | ||||
|                    (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) | ||||
|             # don't extend ext.libraries, it may be shared with other | ||||
|             # extensions, it is a reference to the original list | ||||
|             return ext.libraries + [pythonlib] | ||||
|         elif sys.platform[:6] == "cygwin": | ||||
|             template = "python%d.%d" | ||||
|             pythonlib = (template % | ||||
|                    (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) | ||||
|             # don't extend ext.libraries, it may be shared with other | ||||
|             # extensions, it is a reference to the original list | ||||
|             return ext.libraries + [pythonlib] | ||||
|         elif sys.platform[:6] == "atheos": | ||||
|             template = "python%d.%d" | ||||
|             pythonlib = (template % | ||||
|                    (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) | ||||
|             # Get SHLIBS from Makefile | ||||
|             extra = [] | ||||
|             for lib in sysconfig.get_config_var('SHLIBS').split(): | ||||
|                 if lib.startswith('-l'): | ||||
|                     extra.append(lib[2:]) | ||||
|                 else: | ||||
|                     extra.append(lib) | ||||
|             # don't extend ext.libraries, it may be shared with other | ||||
|             # extensions, it is a reference to the original list | ||||
|             return ext.libraries + [pythonlib, "m"] + extra | ||||
| 
 | ||||
|         elif sys.platform == 'darwin': | ||||
|             # Don't use the default code below | ||||
|             return ext.libraries | ||||
| 
 | ||||
|         else: | ||||
|             if sysconfig.get_config_var('Py_ENABLE_SHARED'): | ||||
|                 template = "python%d.%d" | ||||
|                 pythonlib = (template % | ||||
|                              (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) | ||||
|                 return ext.libraries + [pythonlib] | ||||
|             else: | ||||
|                 return ext.libraries | ||||
							
								
								
									
										410
									
								
								Lib/packaging/command/build_py.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										410
									
								
								Lib/packaging/command/build_py.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,410 @@ | |||
| """Build pure Python modules (just copy to build directory).""" | ||||
| 
 | ||||
| import os | ||||
| import sys | ||||
| from glob import glob | ||||
| 
 | ||||
| from packaging import logger | ||||
| from packaging.command.cmd import Command | ||||
| from packaging.errors import PackagingOptionError, PackagingFileError | ||||
| from packaging.util import convert_path | ||||
| from packaging.compat import Mixin2to3 | ||||
| 
 | ||||
| # marking public APIs | ||||
| __all__ = ['build_py'] | ||||
| 
 | ||||
| class build_py(Command, Mixin2to3): | ||||
| 
 | ||||
|     description = "build pure Python modules (copy to build directory)" | ||||
| 
 | ||||
|     user_options = [ | ||||
|         ('build-lib=', 'd', "directory to build (copy) to"), | ||||
|         ('compile', 'c', "compile .py to .pyc"), | ||||
|         ('no-compile', None, "don't compile .py files [default]"), | ||||
|         ('optimize=', 'O', | ||||
|          "also compile with optimization: -O1 for \"python -O\", " | ||||
|          "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), | ||||
|         ('force', 'f', "forcibly build everything (ignore file timestamps)"), | ||||
|         ('use-2to3', None, | ||||
|          "use 2to3 to make source python 3.x compatible"), | ||||
|         ('convert-2to3-doctests', None, | ||||
|          "use 2to3 to convert doctests in seperate text files"), | ||||
|         ('use-2to3-fixers', None, | ||||
|          "list additional fixers opted for during 2to3 conversion"), | ||||
|         ] | ||||
| 
 | ||||
|     boolean_options = ['compile', 'force'] | ||||
|     negative_opt = {'no-compile' : 'compile'} | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.build_lib = None | ||||
|         self.py_modules = None | ||||
|         self.package = None | ||||
|         self.package_data = None | ||||
|         self.package_dir = None | ||||
|         self.compile = False | ||||
|         self.optimize = 0 | ||||
|         self.force = None | ||||
|         self._updated_files = [] | ||||
|         self._doctests_2to3 = [] | ||||
|         self.use_2to3 = False | ||||
|         self.convert_2to3_doctests = None | ||||
|         self.use_2to3_fixers = None | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         self.set_undefined_options('build', | ||||
|                                    'use_2to3', 'use_2to3_fixers', | ||||
|                                    'convert_2to3_doctests', 'build_lib', | ||||
|                                    'force') | ||||
| 
 | ||||
|         # Get the distribution options that are aliases for build_py | ||||
|         # options -- list of packages and list of modules. | ||||
|         self.packages = self.distribution.packages | ||||
|         self.py_modules = self.distribution.py_modules | ||||
|         self.package_data = self.distribution.package_data | ||||
|         self.package_dir = None | ||||
|         if self.distribution.package_dir is not None: | ||||
|             self.package_dir = convert_path(self.distribution.package_dir) | ||||
|         self.data_files = self.get_data_files() | ||||
| 
 | ||||
|         # Ick, copied straight from install_lib.py (fancy_getopt needs a | ||||
|         # type system!  Hell, *everything* needs a type system!!!) | ||||
|         if not isinstance(self.optimize, int): | ||||
|             try: | ||||
|                 self.optimize = int(self.optimize) | ||||
|                 assert 0 <= self.optimize <= 2 | ||||
|             except (ValueError, AssertionError): | ||||
|                 raise PackagingOptionError("optimize must be 0, 1, or 2") | ||||
| 
 | ||||
|     def run(self): | ||||
|         # XXX copy_file by default preserves atime and mtime.  IMHO this is | ||||
|         # the right thing to do, but perhaps it should be an option -- in | ||||
|         # particular, a site administrator might want installed files to | ||||
|         # reflect the time of installation rather than the last | ||||
|         # modification time before the installed release. | ||||
| 
 | ||||
|         # XXX copy_file by default preserves mode, which appears to be the | ||||
|         # wrong thing to do: if a file is read-only in the working | ||||
|         # directory, we want it to be installed read/write so that the next | ||||
|         # installation of the same module distribution can overwrite it | ||||
|         # without problems.  (This might be a Unix-specific issue.)  Thus | ||||
|         # we turn off 'preserve_mode' when copying to the build directory, | ||||
|         # since the build directory is supposed to be exactly what the | ||||
|         # installation will look like (ie. we preserve mode when | ||||
|         # installing). | ||||
| 
 | ||||
|         # Two options control which modules will be installed: 'packages' | ||||
|         # and 'py_modules'.  The former lets us work with whole packages, not | ||||
|         # specifying individual modules at all; the latter is for | ||||
|         # specifying modules one-at-a-time. | ||||
| 
 | ||||
|         if self.py_modules: | ||||
|             self.build_modules() | ||||
|         if self.packages: | ||||
|             self.build_packages() | ||||
|             self.build_package_data() | ||||
| 
 | ||||
|         if self.use_2to3 and self._updated_files: | ||||
|             self.run_2to3(self._updated_files, self._doctests_2to3, | ||||
|                                             self.use_2to3_fixers) | ||||
| 
 | ||||
|         self.byte_compile(self.get_outputs(include_bytecode=False)) | ||||
| 
 | ||||
|     # -- Top-level worker functions ------------------------------------ | ||||
| 
 | ||||
|     def get_data_files(self): | ||||
|         """Generate list of '(package,src_dir,build_dir,filenames)' tuples. | ||||
| 
 | ||||
|         Helper function for `finalize_options()`. | ||||
|         """ | ||||
|         data = [] | ||||
|         if not self.packages: | ||||
|             return data | ||||
|         for package in self.packages: | ||||
|             # Locate package source directory | ||||
|             src_dir = self.get_package_dir(package) | ||||
| 
 | ||||
|             # Compute package build directory | ||||
|             build_dir = os.path.join(*([self.build_lib] + package.split('.'))) | ||||
| 
 | ||||
|             # Length of path to strip from found files | ||||
|             plen = 0 | ||||
|             if src_dir: | ||||
|                 plen = len(src_dir)+1 | ||||
| 
 | ||||
|             # Strip directory from globbed filenames | ||||
|             filenames = [ | ||||
|                 file[plen:] for file in self.find_data_files(package, src_dir) | ||||
|                 ] | ||||
|             data.append((package, src_dir, build_dir, filenames)) | ||||
|         return data | ||||
| 
 | ||||
|     def find_data_files(self, package, src_dir): | ||||
|         """Return filenames for package's data files in 'src_dir'. | ||||
| 
 | ||||
|         Helper function for `get_data_files()`. | ||||
|         """ | ||||
|         globs = (self.package_data.get('', []) | ||||
|                  + self.package_data.get(package, [])) | ||||
|         files = [] | ||||
|         for pattern in globs: | ||||
|             # Each pattern has to be converted to a platform-specific path | ||||
|             filelist = glob(os.path.join(src_dir, convert_path(pattern))) | ||||
|             # Files that match more than one pattern are only added once | ||||
|             files.extend(fn for fn in filelist if fn not in files) | ||||
|         return files | ||||
| 
 | ||||
|     def build_package_data(self): | ||||
|         """Copy data files into build directory. | ||||
| 
 | ||||
|         Helper function for `run()`. | ||||
|         """ | ||||
|         # FIXME add tests for this method | ||||
|         for package, src_dir, build_dir, filenames in self.data_files: | ||||
|             for filename in filenames: | ||||
|                 target = os.path.join(build_dir, filename) | ||||
|                 srcfile = os.path.join(src_dir, filename) | ||||
|                 self.mkpath(os.path.dirname(target)) | ||||
|                 outf, copied = self.copy_file(srcfile, | ||||
|                                target, preserve_mode=False) | ||||
|                 if copied and srcfile in self.distribution.convert_2to3.doctests: | ||||
|                     self._doctests_2to3.append(outf) | ||||
| 
 | ||||
|     # XXX - this should be moved to the Distribution class as it is not | ||||
|     # only needed for build_py. It also has no dependencies on this class. | ||||
|     def get_package_dir(self, package): | ||||
|         """Return the directory, relative to the top of the source | ||||
|            distribution, where package 'package' should be found | ||||
|            (at least according to the 'package_dir' option, if any).""" | ||||
| 
 | ||||
|         path = package.split('.') | ||||
|         if self.package_dir is not None: | ||||
|             path.insert(0, self.package_dir) | ||||
| 
 | ||||
|         if len(path) > 0: | ||||
|             return os.path.join(*path) | ||||
| 
 | ||||
|         return '' | ||||
| 
 | ||||
|     def check_package(self, package, package_dir): | ||||
|         """Helper function for `find_package_modules()` and `find_modules()'. | ||||
|         """ | ||||
|         # Empty dir name means current directory, which we can probably | ||||
|         # assume exists.  Also, os.path.exists and isdir don't know about | ||||
|         # my "empty string means current dir" convention, so we have to | ||||
|         # circumvent them. | ||||
|         if package_dir != "": | ||||
|             if not os.path.exists(package_dir): | ||||
|                 raise PackagingFileError( | ||||
|                       "package directory '%s' does not exist" % package_dir) | ||||
|             if not os.path.isdir(package_dir): | ||||
|                 raise PackagingFileError( | ||||
|                        "supposed package directory '%s' exists, " | ||||
|                        "but is not a directory" % package_dir) | ||||
| 
 | ||||
|         # Require __init__.py for all but the "root package" | ||||
|         if package: | ||||
|             init_py = os.path.join(package_dir, "__init__.py") | ||||
|             if os.path.isfile(init_py): | ||||
|                 return init_py | ||||
|             else: | ||||
|                 logger.warning(("package init file '%s' not found " + | ||||
|                                 "(or not a regular file)"), init_py) | ||||
| 
 | ||||
|         # Either not in a package at all (__init__.py not expected), or | ||||
|         # __init__.py doesn't exist -- so don't return the filename. | ||||
|         return None | ||||
| 
 | ||||
|     def check_module(self, module, module_file): | ||||
|         if not os.path.isfile(module_file): | ||||
|             logger.warning("file %s (for module %s) not found", | ||||
|                            module_file, module) | ||||
|             return False | ||||
|         else: | ||||
|             return True | ||||
| 
 | ||||
|     def find_package_modules(self, package, package_dir): | ||||
|         self.check_package(package, package_dir) | ||||
|         module_files = glob(os.path.join(package_dir, "*.py")) | ||||
|         modules = [] | ||||
|         if self.distribution.script_name is not None: | ||||
|             setup_script = os.path.abspath(self.distribution.script_name) | ||||
|         else: | ||||
|             setup_script = None | ||||
| 
 | ||||
|         for f in module_files: | ||||
|             abs_f = os.path.abspath(f) | ||||
|             if abs_f != setup_script: | ||||
|                 module = os.path.splitext(os.path.basename(f))[0] | ||||
|                 modules.append((package, module, f)) | ||||
|             else: | ||||
|                 logger.debug("excluding %s", setup_script) | ||||
|         return modules | ||||
| 
 | ||||
|     def find_modules(self): | ||||
|         """Finds individually-specified Python modules, ie. those listed by | ||||
|         module name in 'self.py_modules'.  Returns a list of tuples (package, | ||||
|         module_base, filename): 'package' is a tuple of the path through | ||||
|         package-space to the module; 'module_base' is the bare (no | ||||
|         packages, no dots) module name, and 'filename' is the path to the | ||||
|         ".py" file (relative to the distribution root) that implements the | ||||
|         module. | ||||
|         """ | ||||
|         # Map package names to tuples of useful info about the package: | ||||
|         #    (package_dir, checked) | ||||
|         # package_dir - the directory where we'll find source files for | ||||
|         #   this package | ||||
|         # checked - true if we have checked that the package directory | ||||
|         #   is valid (exists, contains __init__.py, ... ?) | ||||
|         packages = {} | ||||
| 
 | ||||
|         # List of (package, module, filename) tuples to return | ||||
|         modules = [] | ||||
| 
 | ||||
|         # We treat modules-in-packages almost the same as toplevel modules, | ||||
|         # just the "package" for a toplevel is empty (either an empty | ||||
|         # string or empty list, depending on context).  Differences: | ||||
|         #   - don't check for __init__.py in directory for empty package | ||||
|         for module in self.py_modules: | ||||
|             path = module.split('.') | ||||
|             package = '.'.join(path[0:-1]) | ||||
|             module_base = path[-1] | ||||
| 
 | ||||
|             try: | ||||
|                 package_dir, checked = packages[package] | ||||
|             except KeyError: | ||||
|                 package_dir = self.get_package_dir(package) | ||||
|                 checked = False | ||||
| 
 | ||||
|             if not checked: | ||||
|                 init_py = self.check_package(package, package_dir) | ||||
|                 packages[package] = (package_dir, 1) | ||||
|                 if init_py: | ||||
|                     modules.append((package, "__init__", init_py)) | ||||
| 
 | ||||
|             # XXX perhaps we should also check for just .pyc files | ||||
|             # (so greedy closed-source bastards can distribute Python | ||||
|             # modules too) | ||||
|             module_file = os.path.join(package_dir, module_base + ".py") | ||||
|             if not self.check_module(module, module_file): | ||||
|                 continue | ||||
| 
 | ||||
|             modules.append((package, module_base, module_file)) | ||||
| 
 | ||||
|         return modules | ||||
| 
 | ||||
|     def find_all_modules(self): | ||||
|         """Compute the list of all modules that will be built, whether | ||||
|         they are specified one-module-at-a-time ('self.py_modules') or | ||||
|         by whole packages ('self.packages').  Return a list of tuples | ||||
|         (package, module, module_file), just like 'find_modules()' and | ||||
|         'find_package_modules()' do.""" | ||||
|         modules = [] | ||||
|         if self.py_modules: | ||||
|             modules.extend(self.find_modules()) | ||||
|         if self.packages: | ||||
|             for package in self.packages: | ||||
|                 package_dir = self.get_package_dir(package) | ||||
|                 m = self.find_package_modules(package, package_dir) | ||||
|                 modules.extend(m) | ||||
|         return modules | ||||
| 
 | ||||
|     def get_source_files(self): | ||||
|         sources = [module[-1] for module in self.find_all_modules()] | ||||
|         sources += [ | ||||
|             os.path.join(src_dir, filename) | ||||
|             for package, src_dir, build_dir, filenames in self.data_files | ||||
|             for filename in filenames] | ||||
|         return sources | ||||
| 
 | ||||
|     def get_module_outfile(self, build_dir, package, module): | ||||
|         outfile_path = [build_dir] + list(package) + [module + ".py"] | ||||
|         return os.path.join(*outfile_path) | ||||
| 
 | ||||
|     def get_outputs(self, include_bytecode=True): | ||||
|         modules = self.find_all_modules() | ||||
|         outputs = [] | ||||
|         for package, module, module_file in modules: | ||||
|             package = package.split('.') | ||||
|             filename = self.get_module_outfile(self.build_lib, package, module) | ||||
|             outputs.append(filename) | ||||
|             if include_bytecode: | ||||
|                 if self.compile: | ||||
|                     outputs.append(filename + "c") | ||||
|                 if self.optimize > 0: | ||||
|                     outputs.append(filename + "o") | ||||
| 
 | ||||
|         outputs += [ | ||||
|             os.path.join(build_dir, filename) | ||||
|             for package, src_dir, build_dir, filenames in self.data_files | ||||
|             for filename in filenames] | ||||
| 
 | ||||
|         return outputs | ||||
| 
 | ||||
|     def build_module(self, module, module_file, package): | ||||
|         if isinstance(package, str): | ||||
|             package = package.split('.') | ||||
|         elif not isinstance(package, (list, tuple)): | ||||
|             raise TypeError( | ||||
|                   "'package' must be a string (dot-separated), list, or tuple") | ||||
| 
 | ||||
|         # Now put the module source file into the "build" area -- this is | ||||
|         # easy, we just copy it somewhere under self.build_lib (the build | ||||
|         # directory for Python source). | ||||
|         outfile = self.get_module_outfile(self.build_lib, package, module) | ||||
|         dir = os.path.dirname(outfile) | ||||
|         self.mkpath(dir) | ||||
|         return self.copy_file(module_file, outfile, preserve_mode=False) | ||||
| 
 | ||||
|     def build_modules(self): | ||||
|         modules = self.find_modules() | ||||
|         for package, module, module_file in modules: | ||||
| 
 | ||||
|             # Now "build" the module -- ie. copy the source file to | ||||
|             # self.build_lib (the build directory for Python source). | ||||
|             # (Actually, it gets copied to the directory for this package | ||||
|             # under self.build_lib.) | ||||
|             self.build_module(module, module_file, package) | ||||
| 
 | ||||
|     def build_packages(self): | ||||
|         for package in self.packages: | ||||
| 
 | ||||
|             # Get list of (package, module, module_file) tuples based on | ||||
|             # scanning the package directory.  'package' is only included | ||||
|             # in the tuple so that 'find_modules()' and | ||||
|             # 'find_package_tuples()' have a consistent interface; it's | ||||
|             # ignored here (apart from a sanity check).  Also, 'module' is | ||||
|             # the *unqualified* module name (ie. no dots, no package -- we | ||||
|             # already know its package!), and 'module_file' is the path to | ||||
|             # the .py file, relative to the current directory | ||||
|             # (ie. including 'package_dir'). | ||||
|             package_dir = self.get_package_dir(package) | ||||
|             modules = self.find_package_modules(package, package_dir) | ||||
| 
 | ||||
|             # Now loop over the modules we found, "building" each one (just | ||||
|             # copy it to self.build_lib). | ||||
|             for package_, module, module_file in modules: | ||||
|                 assert package == package_ | ||||
|                 self.build_module(module, module_file, package) | ||||
| 
 | ||||
|     def byte_compile(self, files): | ||||
|         if hasattr(sys, 'dont_write_bytecode') and sys.dont_write_bytecode: | ||||
|             logger.warning('%s: byte-compiling is disabled, skipping.', | ||||
|                            self.get_command_name()) | ||||
|             return | ||||
| 
 | ||||
|         from packaging.util import byte_compile | ||||
|         prefix = self.build_lib | ||||
|         if prefix[-1] != os.sep: | ||||
|             prefix = prefix + os.sep | ||||
| 
 | ||||
|         # XXX this code is essentially the same as the 'byte_compile() | ||||
|         # method of the "install_lib" command, except for the determination | ||||
|         # of the 'prefix' string.  Hmmm. | ||||
| 
 | ||||
|         if self.compile: | ||||
|             byte_compile(files, optimize=0, | ||||
|                          force=self.force, prefix=prefix, dry_run=self.dry_run) | ||||
|         if self.optimize > 0: | ||||
|             byte_compile(files, optimize=self.optimize, | ||||
|                          force=self.force, prefix=prefix, dry_run=self.dry_run) | ||||
							
								
								
									
										132
									
								
								Lib/packaging/command/build_scripts.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								Lib/packaging/command/build_scripts.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,132 @@ | |||
| """Build scripts (copy to build dir and fix up shebang line).""" | ||||
| 
 | ||||
| import os | ||||
| import re | ||||
| import sysconfig | ||||
| 
 | ||||
| from packaging.command.cmd import Command | ||||
| from packaging.util import convert_path, newer | ||||
| from packaging import logger | ||||
| from packaging.compat import Mixin2to3 | ||||
| 
 | ||||
| 
 | ||||
| # check if Python is called on the first line with this expression | ||||
| first_line_re = re.compile('^#!.*python[0-9.]*([ \t].*)?$') | ||||
| 
 | ||||
| class build_scripts(Command, Mixin2to3): | ||||
| 
 | ||||
|     description = "build scripts (copy and fix up shebang line)" | ||||
| 
 | ||||
|     user_options = [ | ||||
|         ('build-dir=', 'd', "directory to build (copy) to"), | ||||
|         ('force', 'f', "forcibly build everything (ignore file timestamps"), | ||||
|         ('executable=', 'e', "specify final destination interpreter path"), | ||||
|         ] | ||||
| 
 | ||||
|     boolean_options = ['force'] | ||||
| 
 | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.build_dir = None | ||||
|         self.scripts = None | ||||
|         self.force = None | ||||
|         self.executable = None | ||||
|         self.outfiles = None | ||||
|         self.use_2to3 = False | ||||
|         self.convert_2to3_doctests = None | ||||
|         self.use_2to3_fixers = None | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         self.set_undefined_options('build', | ||||
|                                    ('build_scripts', 'build_dir'), | ||||
|                                    'use_2to3', 'use_2to3_fixers', | ||||
|                                    'convert_2to3_doctests', 'force', | ||||
|                                    'executable') | ||||
|         self.scripts = self.distribution.scripts | ||||
| 
 | ||||
|     def get_source_files(self): | ||||
|         return self.scripts | ||||
| 
 | ||||
|     def run(self): | ||||
|         if not self.scripts: | ||||
|             return | ||||
|         copied_files = self.copy_scripts() | ||||
|         if self.use_2to3 and copied_files: | ||||
|             self._run_2to3(copied_files, fixers=self.use_2to3_fixers) | ||||
| 
 | ||||
|     def copy_scripts(self): | ||||
|         """Copy each script listed in 'self.scripts'; if it's marked as a | ||||
|         Python script in the Unix way (first line matches 'first_line_re', | ||||
|         ie. starts with "\#!" and contains "python"), then adjust the first | ||||
|         line to refer to the current Python interpreter as we copy. | ||||
|         """ | ||||
|         self.mkpath(self.build_dir) | ||||
|         outfiles = [] | ||||
|         for script in self.scripts: | ||||
|             adjust = False | ||||
|             script = convert_path(script) | ||||
|             outfile = os.path.join(self.build_dir, os.path.basename(script)) | ||||
|             outfiles.append(outfile) | ||||
| 
 | ||||
|             if not self.force and not newer(script, outfile): | ||||
|                 logger.debug("not copying %s (up-to-date)", script) | ||||
|                 continue | ||||
| 
 | ||||
|             # Always open the file, but ignore failures in dry-run mode -- | ||||
|             # that way, we'll get accurate feedback if we can read the | ||||
|             # script. | ||||
|             try: | ||||
|                 f = open(script, "r") | ||||
|             except IOError: | ||||
|                 if not self.dry_run: | ||||
|                     raise | ||||
|                 f = None | ||||
|             else: | ||||
|                 first_line = f.readline() | ||||
|                 if not first_line: | ||||
|                     logger.warning('%s: %s is an empty file (skipping)', | ||||
|                                    self.get_command_name(),  script) | ||||
|                     continue | ||||
| 
 | ||||
|                 match = first_line_re.match(first_line) | ||||
|                 if match: | ||||
|                     adjust = True | ||||
|                     post_interp = match.group(1) or '' | ||||
| 
 | ||||
|             if adjust: | ||||
|                 logger.info("copying and adjusting %s -> %s", script, | ||||
|                          self.build_dir) | ||||
|                 if not self.dry_run: | ||||
|                     outf = open(outfile, "w") | ||||
|                     if not sysconfig.is_python_build(): | ||||
|                         outf.write("#!%s%s\n" % | ||||
|                                    (self.executable, | ||||
|                                     post_interp)) | ||||
|                     else: | ||||
|                         outf.write("#!%s%s\n" % | ||||
|                                    (os.path.join( | ||||
|                             sysconfig.get_config_var("BINDIR"), | ||||
|                            "python%s%s" % (sysconfig.get_config_var("VERSION"), | ||||
|                                            sysconfig.get_config_var("EXE"))), | ||||
|                                     post_interp)) | ||||
|                     outf.writelines(f.readlines()) | ||||
|                     outf.close() | ||||
|                 if f: | ||||
|                     f.close() | ||||
|             else: | ||||
|                 if f: | ||||
|                     f.close() | ||||
|                 self.copy_file(script, outfile) | ||||
| 
 | ||||
|         if os.name == 'posix': | ||||
|             for file in outfiles: | ||||
|                 if self.dry_run: | ||||
|                     logger.info("changing mode of %s", file) | ||||
|                 else: | ||||
|                     oldmode = os.stat(file).st_mode & 0o7777 | ||||
|                     newmode = (oldmode | 0o555) & 0o7777 | ||||
|                     if newmode != oldmode: | ||||
|                         logger.info("changing mode of %s from %o to %o", | ||||
|                                  file, oldmode, newmode) | ||||
|                         os.chmod(file, newmode) | ||||
|         return outfiles | ||||
							
								
								
									
										88
									
								
								Lib/packaging/command/check.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								Lib/packaging/command/check.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,88 @@ | |||
| """Check PEP compliance of metadata.""" | ||||
| 
 | ||||
| from packaging import logger | ||||
| from packaging.command.cmd import Command | ||||
| from packaging.errors import PackagingSetupError | ||||
| from packaging.util import resolve_name | ||||
| 
 | ||||
| class check(Command): | ||||
| 
 | ||||
|     description = "check PEP compliance of metadata" | ||||
| 
 | ||||
|     user_options = [('metadata', 'm', 'Verify metadata'), | ||||
|                     ('all', 'a', | ||||
|                      ('runs extended set of checks')), | ||||
|                     ('strict', 's', | ||||
|                      'Will exit with an error if a check fails')] | ||||
| 
 | ||||
|     boolean_options = ['metadata', 'all', 'strict'] | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         """Sets default values for options.""" | ||||
|         self.all = False | ||||
|         self.metadata = True | ||||
|         self.strict = False | ||||
|         self._warnings = [] | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         pass | ||||
| 
 | ||||
|     def warn(self, msg, *args): | ||||
|         """Wrapper around logging that also remembers messages.""" | ||||
|         # XXX we could use a special handler for this, but would need to test | ||||
|         # if it works even if the logger has a too high level | ||||
|         self._warnings.append((msg, args)) | ||||
|         return logger.warning(self.get_command_name() + msg, *args) | ||||
| 
 | ||||
|     def run(self): | ||||
|         """Runs the command.""" | ||||
|         # perform the various tests | ||||
|         if self.metadata: | ||||
|             self.check_metadata() | ||||
|         if self.all: | ||||
|             self.check_restructuredtext() | ||||
|             self.check_hooks_resolvable() | ||||
| 
 | ||||
|         # let's raise an error in strict mode, if we have at least | ||||
|         # one warning | ||||
|         if self.strict and len(self._warnings) > 0: | ||||
|             msg = '\n'.join(msg % args for msg, args in self._warnings) | ||||
|             raise PackagingSetupError(msg) | ||||
| 
 | ||||
|     def check_metadata(self): | ||||
|         """Ensures that all required elements of metadata are supplied. | ||||
| 
 | ||||
|         name, version, URL, author | ||||
| 
 | ||||
|         Warns if any are missing. | ||||
|         """ | ||||
|         missing, warnings = self.distribution.metadata.check(strict=True) | ||||
|         if missing != []: | ||||
|             self.warn('missing required metadata: %s', ', '.join(missing)) | ||||
|         for warning in warnings: | ||||
|             self.warn(warning) | ||||
| 
 | ||||
|     def check_restructuredtext(self): | ||||
|         """Checks if the long string fields are reST-compliant.""" | ||||
|         missing, warnings = self.distribution.metadata.check(restructuredtext=True) | ||||
|         if self.distribution.metadata.docutils_support: | ||||
|             for warning in warnings: | ||||
|                 line = warning[-1].get('line') | ||||
|                 if line is None: | ||||
|                     warning = warning[1] | ||||
|                 else: | ||||
|                     warning = '%s (line %s)' % (warning[1], line) | ||||
|                 self.warn(warning) | ||||
|         elif self.strict: | ||||
|             raise PackagingSetupError('The docutils package is needed.') | ||||
| 
 | ||||
|     def check_hooks_resolvable(self): | ||||
|         for options in self.distribution.command_options.values(): | ||||
|             for hook_kind in ("pre_hook", "post_hook"): | ||||
|                 if hook_kind not in options: | ||||
|                     break | ||||
|                 for hook_name in options[hook_kind][1].values(): | ||||
|                     try: | ||||
|                         resolve_name(hook_name) | ||||
|                     except ImportError: | ||||
|                         self.warn('name %r cannot be resolved', hook_name) | ||||
							
								
								
									
										76
									
								
								Lib/packaging/command/clean.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								Lib/packaging/command/clean.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,76 @@ | |||
| """Clean up temporary files created by the build command.""" | ||||
| 
 | ||||
| # Contributed by Bastian Kleineidam <calvin@cs.uni-sb.de> | ||||
| 
 | ||||
| import os | ||||
| from shutil import rmtree | ||||
| from packaging.command.cmd import Command | ||||
| from packaging import logger | ||||
| 
 | ||||
| class clean(Command): | ||||
| 
 | ||||
|     description = "clean up temporary files from 'build' command" | ||||
|     user_options = [ | ||||
|         ('build-base=', 'b', | ||||
|          "base build directory (default: 'build.build-base')"), | ||||
|         ('build-lib=', None, | ||||
|          "build directory for all modules (default: 'build.build-lib')"), | ||||
|         ('build-temp=', 't', | ||||
|          "temporary build directory (default: 'build.build-temp')"), | ||||
|         ('build-scripts=', None, | ||||
|          "build directory for scripts (default: 'build.build-scripts')"), | ||||
|         ('bdist-base=', None, | ||||
|          "temporary directory for built distributions"), | ||||
|         ('all', 'a', | ||||
|          "remove all build output, not just temporary by-products") | ||||
|     ] | ||||
| 
 | ||||
|     boolean_options = ['all'] | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.build_base = None | ||||
|         self.build_lib = None | ||||
|         self.build_temp = None | ||||
|         self.build_scripts = None | ||||
|         self.bdist_base = None | ||||
|         self.all = None | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         self.set_undefined_options('build', 'build_base', 'build_lib', | ||||
|                                    'build_scripts', 'build_temp') | ||||
|         self.set_undefined_options('bdist', 'bdist_base') | ||||
| 
 | ||||
|     def run(self): | ||||
|         # remove the build/temp.<plat> directory (unless it's already | ||||
|         # gone) | ||||
|         if os.path.exists(self.build_temp): | ||||
|             if self.dry_run: | ||||
|                 logger.info('removing %s', self.build_temp) | ||||
|             else: | ||||
|                 rmtree(self.build_temp) | ||||
|         else: | ||||
|             logger.debug("'%s' does not exist -- can't clean it", | ||||
|                       self.build_temp) | ||||
| 
 | ||||
|         if self.all: | ||||
|             # remove build directories | ||||
|             for directory in (self.build_lib, | ||||
|                               self.bdist_base, | ||||
|                               self.build_scripts): | ||||
|                 if os.path.exists(directory): | ||||
|                     if self.dry_run: | ||||
|                         logger.info('removing %s', directory) | ||||
|                     else: | ||||
|                         rmtree(directory) | ||||
|                 else: | ||||
|                     logger.warning("'%s' does not exist -- can't clean it", | ||||
|                                 directory) | ||||
| 
 | ||||
|         # just for the heck of it, try to remove the base build directory: | ||||
|         # we might have emptied it right now, but if not we don't care | ||||
|         if not self.dry_run: | ||||
|             try: | ||||
|                 os.rmdir(self.build_base) | ||||
|                 logger.info("removing '%s'", self.build_base) | ||||
|             except OSError: | ||||
|                 pass | ||||
							
								
								
									
										440
									
								
								Lib/packaging/command/cmd.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										440
									
								
								Lib/packaging/command/cmd.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,440 @@ | |||
| """Base class for commands.""" | ||||
| 
 | ||||
| import os | ||||
| import re | ||||
| from shutil import copyfile, move, make_archive | ||||
| from packaging import util | ||||
| from packaging import logger | ||||
| from packaging.errors import PackagingOptionError | ||||
| 
 | ||||
| 
 | ||||
| class Command: | ||||
|     """Abstract base class for defining command classes, the "worker bees" | ||||
|     of the Packaging.  A useful analogy for command classes is to think of | ||||
|     them as subroutines with local variables called "options".  The options | ||||
|     are "declared" in 'initialize_options()' and "defined" (given their | ||||
|     final values, aka "finalized") in 'finalize_options()', both of which | ||||
|     must be defined by every command class.  The distinction between the | ||||
|     two is necessary because option values might come from the outside | ||||
|     world (command line, config file, ...), and any options dependent on | ||||
|     other options must be computed *after* these outside influences have | ||||
|     been processed -- hence 'finalize_options()'.  The "body" of the | ||||
|     subroutine, where it does all its work based on the values of its | ||||
|     options, is the 'run()' method, which must also be implemented by every | ||||
|     command class. | ||||
|     """ | ||||
| 
 | ||||
|     # 'sub_commands' formalizes the notion of a "family" of commands, | ||||
|     # eg. "install_dist" as the parent with sub-commands "install_lib", | ||||
|     # "install_headers", etc.  The parent of a family of commands | ||||
|     # defines 'sub_commands' as a class attribute; it's a list of | ||||
|     #    (command_name : string, predicate : unbound_method | string | None) | ||||
|     # tuples, where 'predicate' is a method of the parent command that | ||||
|     # determines whether the corresponding command is applicable in the | ||||
|     # current situation.  (Eg. we "install_headers" is only applicable if | ||||
|     # we have any C header files to install.)  If 'predicate' is None, | ||||
|     # that command is always applicable. | ||||
|     # | ||||
|     # 'sub_commands' is usually defined at the *end* of a class, because | ||||
|     # predicates can be unbound methods, so they must already have been | ||||
|     # defined.  The canonical example is the "install_dist" command. | ||||
|     sub_commands = [] | ||||
| 
 | ||||
|     # Pre and post command hooks are run just before or just after the command | ||||
|     # itself. They are simple functions that receive the command instance. They | ||||
|     # are specified as callable objects or dotted strings (for lazy loading). | ||||
|     pre_hook = None | ||||
|     post_hook = None | ||||
| 
 | ||||
|     # -- Creation/initialization methods ------------------------------- | ||||
| 
 | ||||
|     def __init__(self, dist): | ||||
|         """Create and initialize a new Command object.  Most importantly, | ||||
|         invokes the 'initialize_options()' method, which is the real | ||||
|         initializer and depends on the actual command being instantiated. | ||||
|         """ | ||||
|         # late import because of mutual dependence between these classes | ||||
|         from packaging.dist import Distribution | ||||
| 
 | ||||
|         if not isinstance(dist, Distribution): | ||||
|             raise TypeError("dist must be a Distribution instance") | ||||
|         if self.__class__ is Command: | ||||
|             raise RuntimeError("Command is an abstract class") | ||||
| 
 | ||||
|         self.distribution = dist | ||||
|         self.initialize_options() | ||||
| 
 | ||||
|         # Per-command versions of the global flags, so that the user can | ||||
|         # customize Packaging' behaviour command-by-command and let some | ||||
|         # commands fall back on the Distribution's behaviour.  None means | ||||
|         # "not defined, check self.distribution's copy", while 0 or 1 mean | ||||
|         # false and true (duh).  Note that this means figuring out the real | ||||
|         # value of each flag is a touch complicated -- hence "self._dry_run" | ||||
|         # will be handled by a property, below. | ||||
|         # XXX This needs to be fixed. [I changed it to a property--does that | ||||
|         #     "fix" it?] | ||||
|         self._dry_run = None | ||||
| 
 | ||||
|         # Some commands define a 'self.force' option to ignore file | ||||
|         # timestamps, but methods defined *here* assume that | ||||
|         # 'self.force' exists for all commands.  So define it here | ||||
|         # just to be safe. | ||||
|         self.force = None | ||||
| 
 | ||||
|         # The 'help' flag is just used for command line parsing, so | ||||
|         # none of that complicated bureaucracy is needed. | ||||
|         self.help = False | ||||
| 
 | ||||
|         # 'finalized' records whether or not 'finalize_options()' has been | ||||
|         # called.  'finalize_options()' itself should not pay attention to | ||||
|         # this flag: it is the business of 'ensure_finalized()', which | ||||
|         # always calls 'finalize_options()', to respect/update it. | ||||
|         self.finalized = False | ||||
| 
 | ||||
|     # XXX A more explicit way to customize dry_run would be better. | ||||
|     @property | ||||
|     def dry_run(self): | ||||
|         if self._dry_run is None: | ||||
|             return getattr(self.distribution, 'dry_run') | ||||
|         else: | ||||
|             return self._dry_run | ||||
| 
 | ||||
|     def ensure_finalized(self): | ||||
|         if not self.finalized: | ||||
|             self.finalize_options() | ||||
|         self.finalized = True | ||||
| 
 | ||||
|     # Subclasses must define: | ||||
|     #   initialize_options() | ||||
|     #     provide default values for all options; may be customized by | ||||
|     #     setup script, by options from config file(s), or by command-line | ||||
|     #     options | ||||
|     #   finalize_options() | ||||
|     #     decide on the final values for all options; this is called | ||||
|     #     after all possible intervention from the outside world | ||||
|     #     (command line, option file, etc.) has been processed | ||||
|     #   run() | ||||
|     #     run the command: do whatever it is we're here to do, | ||||
|     #     controlled by the command's various option values | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         """Set default values for all the options that this command | ||||
|         supports.  Note that these defaults may be overridden by other | ||||
|         commands, by the setup script, by config files, or by the | ||||
|         command line.  Thus, this is not the place to code dependencies | ||||
|         between options; generally, 'initialize_options()' implementations | ||||
|         are just a bunch of "self.foo = None" assignments. | ||||
| 
 | ||||
|         This method must be implemented by all command classes. | ||||
|         """ | ||||
|         raise RuntimeError( | ||||
|             "abstract method -- subclass %s must override" % self.__class__) | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         """Set final values for all the options that this command supports. | ||||
|         This is always called as late as possible, ie.  after any option | ||||
|         assignments from the command line or from other commands have been | ||||
|         done.  Thus, this is the place to code option dependencies: if | ||||
|         'foo' depends on 'bar', then it is safe to set 'foo' from 'bar' as | ||||
|         long as 'foo' still has the same value it was assigned in | ||||
|         'initialize_options()'. | ||||
| 
 | ||||
|         This method must be implemented by all command classes. | ||||
|         """ | ||||
|         raise RuntimeError( | ||||
|             "abstract method -- subclass %s must override" % self.__class__) | ||||
| 
 | ||||
|     def dump_options(self, header=None, indent=""): | ||||
|         if header is None: | ||||
|             header = "command options for '%s':" % self.get_command_name() | ||||
|         logger.info(indent + header) | ||||
|         indent = indent + "  " | ||||
|         negative_opt = getattr(self, 'negative_opt', ()) | ||||
|         for option, _, _ in self.user_options: | ||||
|             if option in negative_opt: | ||||
|                 continue | ||||
|             option = option.replace('-', '_') | ||||
|             if option[-1] == "=": | ||||
|                 option = option[:-1] | ||||
|             value = getattr(self, option) | ||||
|             logger.info(indent + "%s = %s", option, value) | ||||
| 
 | ||||
|     def run(self): | ||||
|         """A command's raison d'etre: carry out the action it exists to | ||||
|         perform, controlled by the options initialized in | ||||
|         'initialize_options()', customized by other commands, the setup | ||||
|         script, the command line and config files, and finalized in | ||||
|         'finalize_options()'.  All terminal output and filesystem | ||||
|         interaction should be done by 'run()'. | ||||
| 
 | ||||
|         This method must be implemented by all command classes. | ||||
|         """ | ||||
|         raise RuntimeError( | ||||
|             "abstract method -- subclass %s must override" % self.__class__) | ||||
| 
 | ||||
|     # -- External interface -------------------------------------------- | ||||
|     # (called by outsiders) | ||||
| 
 | ||||
|     def get_source_files(self): | ||||
|         """Return the list of files that are used as inputs to this command, | ||||
|         i.e. the files used to generate the output files.  The result is used | ||||
|         by the `sdist` command in determining the set of default files. | ||||
| 
 | ||||
|         Command classes should implement this method if they operate on files | ||||
|         from the source tree. | ||||
|         """ | ||||
|         return [] | ||||
| 
 | ||||
|     def get_outputs(self): | ||||
|         """Return the list of files that would be produced if this command | ||||
|         were actually run.  Not affected by the "dry-run" flag or whether | ||||
|         any other commands have been run. | ||||
| 
 | ||||
|         Command classes should implement this method if they produce any | ||||
|         output files that get consumed by another command.  e.g., `build_ext` | ||||
|         returns the list of built extension modules, but not any temporary | ||||
|         files used in the compilation process. | ||||
|         """ | ||||
|         return [] | ||||
| 
 | ||||
|     # -- Option validation methods ------------------------------------- | ||||
|     # (these are very handy in writing the 'finalize_options()' method) | ||||
|     # | ||||
|     # NB. the general philosophy here is to ensure that a particular option | ||||
|     # value meets certain type and value constraints.  If not, we try to | ||||
|     # force it into conformance (eg. if we expect a list but have a string, | ||||
|     # split the string on comma and/or whitespace).  If we can't force the | ||||
|     # option into conformance, raise PackagingOptionError.  Thus, command | ||||
|     # classes need do nothing more than (eg.) | ||||
|     #   self.ensure_string_list('foo') | ||||
|     # and they can be guaranteed that thereafter, self.foo will be | ||||
|     # a list of strings. | ||||
| 
 | ||||
|     def _ensure_stringlike(self, option, what, default=None): | ||||
|         val = getattr(self, option) | ||||
|         if val is None: | ||||
|             setattr(self, option, default) | ||||
|             return default | ||||
|         elif not isinstance(val, str): | ||||
|             raise PackagingOptionError("'%s' must be a %s (got `%s`)" % | ||||
|                                        (option, what, val)) | ||||
|         return val | ||||
| 
 | ||||
|     def ensure_string(self, option, default=None): | ||||
|         """Ensure that 'option' is a string; if not defined, set it to | ||||
|         'default'. | ||||
|         """ | ||||
|         self._ensure_stringlike(option, "string", default) | ||||
| 
 | ||||
|     def ensure_string_list(self, option): | ||||
|         r"""Ensure that 'option' is a list of strings.  If 'option' is | ||||
|         currently a string, we split it either on /,\s*/ or /\s+/, so | ||||
|         "foo bar baz", "foo,bar,baz", and "foo,   bar baz" all become | ||||
|         ["foo", "bar", "baz"]. | ||||
|         """ | ||||
|         val = getattr(self, option) | ||||
|         if val is None: | ||||
|             return | ||||
|         elif isinstance(val, str): | ||||
|             setattr(self, option, re.split(r',\s*|\s+', val)) | ||||
|         else: | ||||
|             if isinstance(val, list): | ||||
|                 # checks if all elements are str | ||||
|                 ok = True | ||||
|                 for element in val: | ||||
|                     if not isinstance(element, str): | ||||
|                         ok = False | ||||
|                         break | ||||
|             else: | ||||
|                 ok = False | ||||
| 
 | ||||
|             if not ok: | ||||
|                 raise PackagingOptionError( | ||||
|                     "'%s' must be a list of strings (got %r)" % (option, val)) | ||||
| 
 | ||||
|     def _ensure_tested_string(self, option, tester, | ||||
|                               what, error_fmt, default=None): | ||||
|         val = self._ensure_stringlike(option, what, default) | ||||
|         if val is not None and not tester(val): | ||||
|             raise PackagingOptionError( | ||||
|                 ("error in '%s' option: " + error_fmt) % (option, val)) | ||||
| 
 | ||||
|     def ensure_filename(self, option): | ||||
|         """Ensure that 'option' is the name of an existing file.""" | ||||
|         self._ensure_tested_string(option, os.path.isfile, | ||||
|                                    "filename", | ||||
|                                    "'%s' does not exist or is not a file") | ||||
| 
 | ||||
|     def ensure_dirname(self, option): | ||||
|         self._ensure_tested_string(option, os.path.isdir, | ||||
|                                    "directory name", | ||||
|                                    "'%s' does not exist or is not a directory") | ||||
| 
 | ||||
|     # -- Convenience methods for commands ------------------------------ | ||||
| 
 | ||||
|     @classmethod | ||||
|     def get_command_name(cls): | ||||
|         if hasattr(cls, 'command_name'): | ||||
|             return cls.command_name | ||||
|         else: | ||||
|             return cls.__name__ | ||||
| 
 | ||||
|     def set_undefined_options(self, src_cmd, *options): | ||||
|         """Set values of undefined options from another command. | ||||
| 
 | ||||
|         Undefined options are options set to None, which is the convention | ||||
|         used to indicate that an option has not been changed between | ||||
|         'initialize_options()' and 'finalize_options()'.  This method is | ||||
|         usually called from 'finalize_options()' for options that depend on | ||||
|         some other command rather than another option of the same command, | ||||
|         typically subcommands. | ||||
| 
 | ||||
|         The 'src_cmd' argument is the other command from which option values | ||||
|         will be taken (a command object will be created for it if necessary); | ||||
|         the remaining positional arguments are strings that give the name of | ||||
|         the option to set. If the name is different on the source and target | ||||
|         command, you can pass a tuple with '(name_on_source, name_on_dest)' so | ||||
|         that 'self.name_on_dest' will be set from 'src_cmd.name_on_source'. | ||||
|         """ | ||||
|         src_cmd_obj = self.distribution.get_command_obj(src_cmd) | ||||
|         src_cmd_obj.ensure_finalized() | ||||
|         for obj in options: | ||||
|             if isinstance(obj, tuple): | ||||
|                 src_option, dst_option = obj | ||||
|             else: | ||||
|                 src_option, dst_option = obj, obj | ||||
|             if getattr(self, dst_option) is None: | ||||
|                 setattr(self, dst_option, | ||||
|                         getattr(src_cmd_obj, src_option)) | ||||
| 
 | ||||
|     def get_finalized_command(self, command, create=True): | ||||
|         """Wrapper around Distribution's 'get_command_obj()' method: find | ||||
|         (create if necessary and 'create' is true) the command object for | ||||
|         'command', call its 'ensure_finalized()' method, and return the | ||||
|         finalized command object. | ||||
|         """ | ||||
|         cmd_obj = self.distribution.get_command_obj(command, create) | ||||
|         cmd_obj.ensure_finalized() | ||||
|         return cmd_obj | ||||
| 
 | ||||
|     def get_reinitialized_command(self, command, reinit_subcommands=False): | ||||
|         return self.distribution.get_reinitialized_command( | ||||
|             command, reinit_subcommands) | ||||
| 
 | ||||
|     def run_command(self, command): | ||||
|         """Run some other command: uses the 'run_command()' method of | ||||
|         Distribution, which creates and finalizes the command object if | ||||
|         necessary and then invokes its 'run()' method. | ||||
|         """ | ||||
|         self.distribution.run_command(command) | ||||
| 
 | ||||
|     def get_sub_commands(self): | ||||
|         """Determine the sub-commands that are relevant in the current | ||||
|         distribution (ie., that need to be run).  This is based on the | ||||
|         'sub_commands' class attribute: each tuple in that list may include | ||||
|         a method that we call to determine if the subcommand needs to be | ||||
|         run for the current distribution.  Return a list of command names. | ||||
|         """ | ||||
|         commands = [] | ||||
|         for sub_command in self.sub_commands: | ||||
|             if len(sub_command) == 2: | ||||
|                 cmd_name, method = sub_command | ||||
|                 if method is None or method(self): | ||||
|                     commands.append(cmd_name) | ||||
|             else: | ||||
|                 commands.append(sub_command) | ||||
|         return commands | ||||
| 
 | ||||
|     # -- External world manipulation ----------------------------------- | ||||
| 
 | ||||
|     def execute(self, func, args, msg=None, level=1): | ||||
|         util.execute(func, args, msg, dry_run=self.dry_run) | ||||
| 
 | ||||
|     def mkpath(self, name, mode=0o777, dry_run=None, verbose=0): | ||||
|         if dry_run is None: | ||||
|             dry_run = self.dry_run | ||||
|         name = os.path.normpath(name) | ||||
|         if os.path.isdir(name) or name == '': | ||||
|             return | ||||
|         if dry_run: | ||||
|             head = '' | ||||
|             for part in name.split(os.sep): | ||||
|                 logger.info("created directory %s%s", head, part) | ||||
|                 head += part + os.sep | ||||
|             return | ||||
|         os.makedirs(name, mode) | ||||
| 
 | ||||
|     def copy_file(self, infile, outfile, | ||||
|                   preserve_mode=True, preserve_times=True, link=None, level=1): | ||||
|         """Copy a file respecting verbose, dry-run and force flags.  (The | ||||
|         former two default to whatever is in the Distribution object, and | ||||
|         the latter defaults to false for commands that don't define it.)""" | ||||
|         if self.dry_run: | ||||
|             # XXX add a comment | ||||
|             return | ||||
|         if os.path.isdir(outfile): | ||||
|             outfile = os.path.join(outfile, os.path.split(infile)[-1]) | ||||
|         copyfile(infile, outfile) | ||||
|         return outfile, None  # XXX | ||||
| 
 | ||||
|     def copy_tree(self, infile, outfile, preserve_mode=True, | ||||
|                   preserve_times=True, preserve_symlinks=False, level=1): | ||||
|         """Copy an entire directory tree respecting verbose, dry-run, | ||||
|         and force flags. | ||||
|         """ | ||||
|         if self.dry_run: | ||||
|             return  # see if we want to display something | ||||
| 
 | ||||
| 
 | ||||
|         return util.copy_tree(infile, outfile, preserve_mode, preserve_times, | ||||
|             preserve_symlinks, not self.force, dry_run=self.dry_run) | ||||
| 
 | ||||
|     def move_file(self, src, dst, level=1): | ||||
|         """Move a file respecting the dry-run flag.""" | ||||
|         if self.dry_run: | ||||
|             return  # XXX log ? | ||||
|         return move(src, dst) | ||||
| 
 | ||||
|     def spawn(self, cmd, search_path=True, level=1): | ||||
|         """Spawn an external command respecting dry-run flag.""" | ||||
|         from packaging.util import spawn | ||||
|         spawn(cmd, search_path, dry_run=self.dry_run) | ||||
| 
 | ||||
|     def make_archive(self, base_name, format, root_dir=None, base_dir=None, | ||||
|                      owner=None, group=None): | ||||
|         return make_archive(base_name, format, root_dir, | ||||
|                             base_dir, dry_run=self.dry_run, | ||||
|                             owner=owner, group=group) | ||||
| 
 | ||||
|     def make_file(self, infiles, outfile, func, args, | ||||
|                   exec_msg=None, skip_msg=None, level=1): | ||||
|         """Special case of 'execute()' for operations that process one or | ||||
|         more input files and generate one output file.  Works just like | ||||
|         'execute()', except the operation is skipped and a different | ||||
|         message printed if 'outfile' already exists and is newer than all | ||||
|         files listed in 'infiles'.  If the command defined 'self.force', | ||||
|         and it is true, then the command is unconditionally run -- does no | ||||
|         timestamp checks. | ||||
|         """ | ||||
|         if skip_msg is None: | ||||
|             skip_msg = "skipping %s (inputs unchanged)" % outfile | ||||
| 
 | ||||
|         # Allow 'infiles' to be a single string | ||||
|         if isinstance(infiles, str): | ||||
|             infiles = (infiles,) | ||||
|         elif not isinstance(infiles, (list, tuple)): | ||||
|             raise TypeError( | ||||
|                 "'infiles' must be a string, or a list or tuple of strings") | ||||
| 
 | ||||
|         if exec_msg is None: | ||||
|             exec_msg = "generating %s from %s" % (outfile, ', '.join(infiles)) | ||||
| 
 | ||||
|         # If 'outfile' must be regenerated (either because it doesn't | ||||
|         # exist, is out-of-date, or the 'force' flag is true) then | ||||
|         # perform the action that presumably regenerates it | ||||
|         if self.force or util.newer_group(infiles, outfile): | ||||
|             self.execute(func, args, exec_msg, level) | ||||
| 
 | ||||
|         # Otherwise, print the "skip" message | ||||
|         else: | ||||
|             logger.debug(skip_msg) | ||||
							
								
								
									
										35
									
								
								Lib/packaging/command/command_template
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								Lib/packaging/command/command_template
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | |||
| """Do X and Y.""" | ||||
| 
 | ||||
| from packaging import logger | ||||
| from packaging.command.cmd import Command | ||||
| 
 | ||||
| 
 | ||||
| class x(Command): | ||||
| 
 | ||||
|     # Brief (40-50 characters) description of the command | ||||
|     description = "" | ||||
| 
 | ||||
|     # List of option tuples: long name, short name (None if no short | ||||
|     # name), and help string. | ||||
|     user_options = [ | ||||
|         ('', '',  # long option, short option (one letter) or None | ||||
|          ""),  # help text | ||||
|         ] | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self. = None | ||||
|         self. = None | ||||
|         self. = None | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         if self.x is None: | ||||
|             self.x = ... | ||||
| 
 | ||||
|     def run(self): | ||||
|         ... | ||||
|         logger.info(...) | ||||
| 
 | ||||
|         if not self.dry_run: | ||||
|             ... | ||||
| 
 | ||||
|         self.execute(..., dry_run=self.dry_run) | ||||
							
								
								
									
										351
									
								
								Lib/packaging/command/config.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										351
									
								
								Lib/packaging/command/config.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,351 @@ | |||
| """Prepare the build. | ||||
| 
 | ||||
| This module provides config, a (mostly) empty command class | ||||
| that exists mainly to be sub-classed by specific module distributions and | ||||
| applications.  The idea is that while every "config" command is different, | ||||
| at least they're all named the same, and users always see "config" in the | ||||
| list of standard commands.  Also, this is a good place to put common | ||||
| configure-like tasks: "try to compile this C code", or "figure out where | ||||
| this header file lives". | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| import re | ||||
| 
 | ||||
| from packaging.command.cmd import Command | ||||
| from packaging.errors import PackagingExecError | ||||
| from packaging.compiler import customize_compiler | ||||
| from packaging import logger | ||||
| 
 | ||||
| LANG_EXT = {'c': '.c', 'c++': '.cxx'} | ||||
| 
 | ||||
| class config(Command): | ||||
| 
 | ||||
|     description = "prepare the build" | ||||
| 
 | ||||
|     user_options = [ | ||||
|         ('compiler=', None, | ||||
|          "specify the compiler type"), | ||||
|         ('cc=', None, | ||||
|          "specify the compiler executable"), | ||||
|         ('include-dirs=', 'I', | ||||
|          "list of directories to search for header files"), | ||||
|         ('define=', 'D', | ||||
|          "C preprocessor macros to define"), | ||||
|         ('undef=', 'U', | ||||
|          "C preprocessor macros to undefine"), | ||||
|         ('libraries=', 'l', | ||||
|          "external C libraries to link with"), | ||||
|         ('library-dirs=', 'L', | ||||
|          "directories to search for external C libraries"), | ||||
| 
 | ||||
|         ('noisy', None, | ||||
|          "show every action (compile, link, run, ...) taken"), | ||||
|         ('dump-source', None, | ||||
|          "dump generated source files before attempting to compile them"), | ||||
|         ] | ||||
| 
 | ||||
| 
 | ||||
|     # The three standard command methods: since the "config" command | ||||
|     # does nothing by default, these are empty. | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.compiler = None | ||||
|         self.cc = None | ||||
|         self.include_dirs = None | ||||
|         self.libraries = None | ||||
|         self.library_dirs = None | ||||
| 
 | ||||
|         # maximal output for now | ||||
|         self.noisy = True | ||||
|         self.dump_source = True | ||||
| 
 | ||||
|         # list of temporary files generated along-the-way that we have | ||||
|         # to clean at some point | ||||
|         self.temp_files = [] | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         if self.include_dirs is None: | ||||
|             self.include_dirs = self.distribution.include_dirs or [] | ||||
|         elif isinstance(self.include_dirs, str): | ||||
|             self.include_dirs = self.include_dirs.split(os.pathsep) | ||||
| 
 | ||||
|         if self.libraries is None: | ||||
|             self.libraries = [] | ||||
|         elif isinstance(self.libraries, str): | ||||
|             self.libraries = [self.libraries] | ||||
| 
 | ||||
|         if self.library_dirs is None: | ||||
|             self.library_dirs = [] | ||||
|         elif isinstance(self.library_dirs, str): | ||||
|             self.library_dirs = self.library_dirs.split(os.pathsep) | ||||
| 
 | ||||
|     def run(self): | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
|     # Utility methods for actual "config" commands.  The interfaces are | ||||
|     # loosely based on Autoconf macros of similar names.  Sub-classes | ||||
|     # may use these freely. | ||||
| 
 | ||||
|     def _check_compiler(self): | ||||
|         """Check that 'self.compiler' really is a CCompiler object; | ||||
|         if not, make it one. | ||||
|         """ | ||||
|         # We do this late, and only on-demand, because this is an expensive | ||||
|         # import. | ||||
|         from packaging.compiler.ccompiler import CCompiler | ||||
|         from packaging.compiler import new_compiler | ||||
|         if not isinstance(self.compiler, CCompiler): | ||||
|             self.compiler = new_compiler(compiler=self.compiler, | ||||
|                                          dry_run=self.dry_run, force=True) | ||||
|             customize_compiler(self.compiler) | ||||
|             if self.include_dirs: | ||||
|                 self.compiler.set_include_dirs(self.include_dirs) | ||||
|             if self.libraries: | ||||
|                 self.compiler.set_libraries(self.libraries) | ||||
|             if self.library_dirs: | ||||
|                 self.compiler.set_library_dirs(self.library_dirs) | ||||
| 
 | ||||
| 
 | ||||
|     def _gen_temp_sourcefile(self, body, headers, lang): | ||||
|         filename = "_configtest" + LANG_EXT[lang] | ||||
|         file = open(filename, "w") | ||||
|         if headers: | ||||
|             for header in headers: | ||||
|                 file.write("#include <%s>\n" % header) | ||||
|             file.write("\n") | ||||
|         file.write(body) | ||||
|         if body[-1] != "\n": | ||||
|             file.write("\n") | ||||
|         file.close() | ||||
|         return filename | ||||
| 
 | ||||
|     def _preprocess(self, body, headers, include_dirs, lang): | ||||
|         src = self._gen_temp_sourcefile(body, headers, lang) | ||||
|         out = "_configtest.i" | ||||
|         self.temp_files.extend((src, out)) | ||||
|         self.compiler.preprocess(src, out, include_dirs=include_dirs) | ||||
|         return src, out | ||||
| 
 | ||||
|     def _compile(self, body, headers, include_dirs, lang): | ||||
|         src = self._gen_temp_sourcefile(body, headers, lang) | ||||
|         if self.dump_source: | ||||
|             dump_file(src, "compiling '%s':" % src) | ||||
|         obj = self.compiler.object_filenames([src])[0] | ||||
|         self.temp_files.extend((src, obj)) | ||||
|         self.compiler.compile([src], include_dirs=include_dirs) | ||||
|         return src, obj | ||||
| 
 | ||||
|     def _link(self, body, headers, include_dirs, libraries, library_dirs, | ||||
|               lang): | ||||
|         src, obj = self._compile(body, headers, include_dirs, lang) | ||||
|         prog = os.path.splitext(os.path.basename(src))[0] | ||||
|         self.compiler.link_executable([obj], prog, | ||||
|                                       libraries=libraries, | ||||
|                                       library_dirs=library_dirs, | ||||
|                                       target_lang=lang) | ||||
| 
 | ||||
|         if self.compiler.exe_extension is not None: | ||||
|             prog = prog + self.compiler.exe_extension | ||||
|         self.temp_files.append(prog) | ||||
| 
 | ||||
|         return src, obj, prog | ||||
| 
 | ||||
|     def _clean(self, *filenames): | ||||
|         if not filenames: | ||||
|             filenames = self.temp_files | ||||
|             self.temp_files = [] | ||||
|         logger.info("removing: %s", ' '.join(filenames)) | ||||
|         for filename in filenames: | ||||
|             try: | ||||
|                 os.remove(filename) | ||||
|             except OSError: | ||||
|                 pass | ||||
| 
 | ||||
| 
 | ||||
|     # XXX these ignore the dry-run flag: what to do, what to do? even if | ||||
|     # you want a dry-run build, you still need some sort of configuration | ||||
|     # info.  My inclination is to make it up to the real config command to | ||||
|     # consult 'dry_run', and assume a default (minimal) configuration if | ||||
|     # true.  The problem with trying to do it here is that you'd have to | ||||
|     # return either true or false from all the 'try' methods, neither of | ||||
|     # which is correct. | ||||
| 
 | ||||
|     # XXX need access to the header search path and maybe default macros. | ||||
| 
 | ||||
|     def try_cpp(self, body=None, headers=None, include_dirs=None, lang="c"): | ||||
|         """Construct a source file from 'body' (a string containing lines | ||||
|         of C/C++ code) and 'headers' (a list of header files to include) | ||||
|         and run it through the preprocessor.  Return true if the | ||||
|         preprocessor succeeded, false if there were any errors. | ||||
|         ('body' probably isn't of much use, but what the heck.) | ||||
|         """ | ||||
|         from packaging.compiler.ccompiler import CompileError | ||||
|         self._check_compiler() | ||||
|         ok = True | ||||
|         try: | ||||
|             self._preprocess(body, headers, include_dirs, lang) | ||||
|         except CompileError: | ||||
|             ok = False | ||||
| 
 | ||||
|         self._clean() | ||||
|         return ok | ||||
| 
 | ||||
|     def search_cpp(self, pattern, body=None, headers=None, include_dirs=None, | ||||
|                    lang="c"): | ||||
|         """Construct a source file (just like 'try_cpp()'), run it through | ||||
|         the preprocessor, and return true if any line of the output matches | ||||
|         'pattern'.  'pattern' should either be a compiled regex object or a | ||||
|         string containing a regex.  If both 'body' and 'headers' are None, | ||||
|         preprocesses an empty file -- which can be useful to determine the | ||||
|         symbols the preprocessor and compiler set by default. | ||||
|         """ | ||||
|         self._check_compiler() | ||||
|         src, out = self._preprocess(body, headers, include_dirs, lang) | ||||
| 
 | ||||
|         if isinstance(pattern, str): | ||||
|             pattern = re.compile(pattern) | ||||
| 
 | ||||
|         file = open(out) | ||||
|         match = False | ||||
|         while True: | ||||
|             line = file.readline() | ||||
|             if line == '': | ||||
|                 break | ||||
|             if pattern.search(line): | ||||
|                 match = True | ||||
|                 break | ||||
| 
 | ||||
|         file.close() | ||||
|         self._clean() | ||||
|         return match | ||||
| 
 | ||||
|     def try_compile(self, body, headers=None, include_dirs=None, lang="c"): | ||||
|         """Try to compile a source file built from 'body' and 'headers'. | ||||
|         Return true on success, false otherwise. | ||||
|         """ | ||||
|         from packaging.compiler.ccompiler import CompileError | ||||
|         self._check_compiler() | ||||
|         try: | ||||
|             self._compile(body, headers, include_dirs, lang) | ||||
|             ok = True | ||||
|         except CompileError: | ||||
|             ok = False | ||||
| 
 | ||||
|         logger.info(ok and "success!" or "failure.") | ||||
|         self._clean() | ||||
|         return ok | ||||
| 
 | ||||
|     def try_link(self, body, headers=None, include_dirs=None, libraries=None, | ||||
|                  library_dirs=None, lang="c"): | ||||
|         """Try to compile and link a source file, built from 'body' and | ||||
|         'headers', to executable form.  Return true on success, false | ||||
|         otherwise. | ||||
|         """ | ||||
|         from packaging.compiler.ccompiler import CompileError, LinkError | ||||
|         self._check_compiler() | ||||
|         try: | ||||
|             self._link(body, headers, include_dirs, | ||||
|                        libraries, library_dirs, lang) | ||||
|             ok = True | ||||
|         except (CompileError, LinkError): | ||||
|             ok = False | ||||
| 
 | ||||
|         logger.info(ok and "success!" or "failure.") | ||||
|         self._clean() | ||||
|         return ok | ||||
| 
 | ||||
|     def try_run(self, body, headers=None, include_dirs=None, libraries=None, | ||||
|                 library_dirs=None, lang="c"): | ||||
|         """Try to compile, link to an executable, and run a program | ||||
|         built from 'body' and 'headers'.  Return true on success, false | ||||
|         otherwise. | ||||
|         """ | ||||
|         from packaging.compiler.ccompiler import CompileError, LinkError | ||||
|         self._check_compiler() | ||||
|         try: | ||||
|             src, obj, exe = self._link(body, headers, include_dirs, | ||||
|                                        libraries, library_dirs, lang) | ||||
|             self.spawn([exe]) | ||||
|             ok = True | ||||
|         except (CompileError, LinkError, PackagingExecError): | ||||
|             ok = False | ||||
| 
 | ||||
|         logger.info(ok and "success!" or "failure.") | ||||
|         self._clean() | ||||
|         return ok | ||||
| 
 | ||||
| 
 | ||||
|     # -- High-level methods -------------------------------------------- | ||||
|     # (these are the ones that are actually likely to be useful | ||||
|     # when implementing a real-world config command!) | ||||
| 
 | ||||
|     def check_func(self, func, headers=None, include_dirs=None, | ||||
|                    libraries=None, library_dirs=None, decl=False, call=False): | ||||
| 
 | ||||
|         """Determine if function 'func' is available by constructing a | ||||
|         source file that refers to 'func', and compiles and links it. | ||||
|         If everything succeeds, returns true; otherwise returns false. | ||||
| 
 | ||||
|         The constructed source file starts out by including the header | ||||
|         files listed in 'headers'.  If 'decl' is true, it then declares | ||||
|         'func' (as "int func()"); you probably shouldn't supply 'headers' | ||||
|         and set 'decl' true in the same call, or you might get errors about | ||||
|         a conflicting declarations for 'func'.  Finally, the constructed | ||||
|         'main()' function either references 'func' or (if 'call' is true) | ||||
|         calls it.  'libraries' and 'library_dirs' are used when | ||||
|         linking. | ||||
|         """ | ||||
| 
 | ||||
|         self._check_compiler() | ||||
|         body = [] | ||||
|         if decl: | ||||
|             body.append("int %s ();" % func) | ||||
|         body.append("int main () {") | ||||
|         if call: | ||||
|             body.append("  %s();" % func) | ||||
|         else: | ||||
|             body.append("  %s;" % func) | ||||
|         body.append("}") | ||||
|         body = "\n".join(body) + "\n" | ||||
| 
 | ||||
|         return self.try_link(body, headers, include_dirs, | ||||
|                              libraries, library_dirs) | ||||
| 
 | ||||
|     def check_lib(self, library, library_dirs=None, headers=None, | ||||
|                   include_dirs=None, other_libraries=[]): | ||||
|         """Determine if 'library' is available to be linked against, | ||||
|         without actually checking that any particular symbols are provided | ||||
|         by it.  'headers' will be used in constructing the source file to | ||||
|         be compiled, but the only effect of this is to check if all the | ||||
|         header files listed are available.  Any libraries listed in | ||||
|         'other_libraries' will be included in the link, in case 'library' | ||||
|         has symbols that depend on other libraries. | ||||
|         """ | ||||
|         self._check_compiler() | ||||
|         return self.try_link("int main (void) { }", | ||||
|                              headers, include_dirs, | ||||
|                              [library]+other_libraries, library_dirs) | ||||
| 
 | ||||
|     def check_header(self, header, include_dirs=None, library_dirs=None, | ||||
|                      lang="c"): | ||||
|         """Determine if the system header file named by 'header_file' | ||||
|         exists and can be found by the preprocessor; return true if so, | ||||
|         false otherwise. | ||||
|         """ | ||||
|         return self.try_cpp(body="/* No body */", headers=[header], | ||||
|                             include_dirs=include_dirs) | ||||
| 
 | ||||
| 
 | ||||
| def dump_file(filename, head=None): | ||||
|     """Dumps a file content into log.info. | ||||
| 
 | ||||
|     If head is not None, will be dumped before the file content. | ||||
|     """ | ||||
|     if head is None: | ||||
|         logger.info(filename) | ||||
|     else: | ||||
|         logger.info(head) | ||||
|     with open(filename) as file: | ||||
|         logger.info(file.read()) | ||||
							
								
								
									
										79
									
								
								Lib/packaging/command/install_data.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								Lib/packaging/command/install_data.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,79 @@ | |||
| """Install platform-independent data files.""" | ||||
| 
 | ||||
| # Contributed by Bastian Kleineidam | ||||
| 
 | ||||
| import os | ||||
| from shutil import Error | ||||
| from sysconfig import get_paths, format_value | ||||
| from packaging import logger | ||||
| from packaging.util import convert_path | ||||
| from packaging.command.cmd import Command | ||||
| 
 | ||||
| 
 | ||||
| class install_data(Command): | ||||
| 
 | ||||
|     description = "install platform-independent data files" | ||||
| 
 | ||||
|     user_options = [ | ||||
|         ('install-dir=', 'd', | ||||
|          "base directory for installing data files " | ||||
|          "(default: installation base dir)"), | ||||
|         ('root=', None, | ||||
|          "install everything relative to this alternate root directory"), | ||||
|         ('force', 'f', "force installation (overwrite existing files)"), | ||||
|         ] | ||||
| 
 | ||||
|     boolean_options = ['force'] | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.install_dir = None | ||||
|         self.outfiles = [] | ||||
|         self.data_files_out = [] | ||||
|         self.root = None | ||||
|         self.force = False | ||||
|         self.data_files = self.distribution.data_files | ||||
|         self.warn_dir = True | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         self.set_undefined_options('install_dist', | ||||
|                                    ('install_data', 'install_dir'), | ||||
|                                    'root', 'force') | ||||
| 
 | ||||
|     def run(self): | ||||
|         self.mkpath(self.install_dir) | ||||
|         for _file in self.data_files.items(): | ||||
|             destination = convert_path(self.expand_categories(_file[1])) | ||||
|             dir_dest = os.path.abspath(os.path.dirname(destination)) | ||||
| 
 | ||||
|             self.mkpath(dir_dest) | ||||
|             try: | ||||
|                 out = self.copy_file(_file[0], dir_dest)[0] | ||||
|             except Error as e: | ||||
|                 logger.warning('%s: %s', self.get_command_name(), e) | ||||
|                 out = destination | ||||
| 
 | ||||
|             self.outfiles.append(out) | ||||
|             self.data_files_out.append((_file[0], destination)) | ||||
| 
 | ||||
|     def expand_categories(self, path_with_categories): | ||||
|         local_vars = get_paths() | ||||
|         local_vars['distribution.name'] = self.distribution.metadata['Name'] | ||||
|         expanded_path = format_value(path_with_categories, local_vars) | ||||
|         expanded_path = format_value(expanded_path, local_vars) | ||||
|         if '{' in expanded_path and '}' in expanded_path: | ||||
|             logger.warning( | ||||
|                 '%s: unable to expand %s, some categories may be missing', | ||||
|                 self.get_command_name(), path_with_categories) | ||||
|         return expanded_path | ||||
| 
 | ||||
|     def get_source_files(self): | ||||
|         return list(self.data_files) | ||||
| 
 | ||||
|     def get_inputs(self): | ||||
|         return list(self.data_files) | ||||
| 
 | ||||
|     def get_outputs(self): | ||||
|         return self.outfiles | ||||
| 
 | ||||
|     def get_resources_out(self): | ||||
|         return self.data_files_out | ||||
							
								
								
									
										625
									
								
								Lib/packaging/command/install_dist.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										625
									
								
								Lib/packaging/command/install_dist.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,625 @@ | |||
| """Main install command, which calls the other install_* commands.""" | ||||
| 
 | ||||
| import sys | ||||
| import os | ||||
| 
 | ||||
| import sysconfig | ||||
| from sysconfig import get_config_vars, get_paths, get_path, get_config_var | ||||
| 
 | ||||
| from packaging import logger | ||||
| from packaging.command.cmd import Command | ||||
| from packaging.errors import PackagingPlatformError | ||||
| from packaging.util import write_file | ||||
| from packaging.util import convert_path, change_root, get_platform | ||||
| from packaging.errors import PackagingOptionError | ||||
| 
 | ||||
| 
 | ||||
| HAS_USER_SITE = True | ||||
| 
 | ||||
| 
 | ||||
| class install_dist(Command): | ||||
| 
 | ||||
|     description = "install everything from build directory" | ||||
| 
 | ||||
|     user_options = [ | ||||
|         # Select installation scheme and set base director(y|ies) | ||||
|         ('prefix=', None, | ||||
|          "installation prefix"), | ||||
|         ('exec-prefix=', None, | ||||
|          "(Unix only) prefix for platform-specific files"), | ||||
|         ('home=', None, | ||||
|          "(Unix only) home directory to install under"), | ||||
| 
 | ||||
|         # Or just set the base director(y|ies) | ||||
|         ('install-base=', None, | ||||
|          "base installation directory (instead of --prefix or --home)"), | ||||
|         ('install-platbase=', None, | ||||
|          "base installation directory for platform-specific files " + | ||||
|          "(instead of --exec-prefix or --home)"), | ||||
|         ('root=', None, | ||||
|          "install everything relative to this alternate root directory"), | ||||
| 
 | ||||
|         # Or explicitly set the installation scheme | ||||
|         ('install-purelib=', None, | ||||
|          "installation directory for pure Python module distributions"), | ||||
|         ('install-platlib=', None, | ||||
|          "installation directory for non-pure module distributions"), | ||||
|         ('install-lib=', None, | ||||
|          "installation directory for all module distributions " + | ||||
|          "(overrides --install-purelib and --install-platlib)"), | ||||
| 
 | ||||
|         ('install-headers=', None, | ||||
|          "installation directory for C/C++ headers"), | ||||
|         ('install-scripts=', None, | ||||
|          "installation directory for Python scripts"), | ||||
|         ('install-data=', None, | ||||
|          "installation directory for data files"), | ||||
| 
 | ||||
|         # Byte-compilation options -- see install_lib.py for details, as | ||||
|         # these are duplicated from there (but only install_lib does | ||||
|         # anything with them). | ||||
|         ('compile', 'c', "compile .py to .pyc [default]"), | ||||
|         ('no-compile', None, "don't compile .py files"), | ||||
|         ('optimize=', 'O', | ||||
|          'also compile with optimization: -O1 for "python -O", ' | ||||
|          '-O2 for "python -OO", and -O0 to disable [default: -O0]'), | ||||
| 
 | ||||
|         # Miscellaneous control options | ||||
|         ('force', 'f', | ||||
|          "force installation (overwrite any existing files)"), | ||||
|         ('skip-build', None, | ||||
|          "skip rebuilding everything (for testing/debugging)"), | ||||
| 
 | ||||
|         # Where to install documentation (eventually!) | ||||
|         #('doc-format=', None, "format of documentation to generate"), | ||||
|         #('install-man=', None, "directory for Unix man pages"), | ||||
|         #('install-html=', None, "directory for HTML documentation"), | ||||
|         #('install-info=', None, "directory for GNU info files"), | ||||
| 
 | ||||
|         # XXX use a name that makes clear this is the old format | ||||
|         ('record=', None, | ||||
|          "filename in which to record a list of installed files " | ||||
|          "(not PEP 376-compliant)"), | ||||
|         ('resources=', None, | ||||
|          "data files mapping"), | ||||
| 
 | ||||
|         # .dist-info related arguments, read by install_dist_info | ||||
|         ('no-distinfo', None, | ||||
|          "do not create a .dist-info directory"), | ||||
|         ('installer=', None, | ||||
|          "the name of the installer"), | ||||
|         ('requested', None, | ||||
|          "generate a REQUESTED file (i.e."), | ||||
|         ('no-requested', None, | ||||
|          "do not generate a REQUESTED file"), | ||||
|         ('no-record', None, | ||||
|          "do not generate a RECORD file"), | ||||
|         ] | ||||
| 
 | ||||
|     boolean_options = ['compile', 'force', 'skip-build', 'no-distinfo', | ||||
|                        'requested', 'no-record'] | ||||
| 
 | ||||
|     if HAS_USER_SITE: | ||||
|         user_options.append( | ||||
|             ('user', None, | ||||
|              "install in user site-packages directory [%s]" % | ||||
|              get_path('purelib', '%s_user' % os.name))) | ||||
| 
 | ||||
|         boolean_options.append('user') | ||||
| 
 | ||||
|     negative_opt = {'no-compile': 'compile', 'no-requested': 'requested'} | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         # High-level options: these select both an installation base | ||||
|         # and scheme. | ||||
|         self.prefix = None | ||||
|         self.exec_prefix = None | ||||
|         self.home = None | ||||
|         if HAS_USER_SITE: | ||||
|             self.user = False | ||||
| 
 | ||||
|         # These select only the installation base; it's up to the user to | ||||
|         # specify the installation scheme (currently, that means supplying | ||||
|         # the --install-{platlib,purelib,scripts,data} options). | ||||
|         self.install_base = None | ||||
|         self.install_platbase = None | ||||
|         self.root = None | ||||
| 
 | ||||
|         # These options are the actual installation directories; if not | ||||
|         # supplied by the user, they are filled in using the installation | ||||
|         # scheme implied by prefix/exec-prefix/home and the contents of | ||||
|         # that installation scheme. | ||||
|         self.install_purelib = None     # for pure module distributions | ||||
|         self.install_platlib = None     # non-pure (dists w/ extensions) | ||||
|         self.install_headers = None     # for C/C++ headers | ||||
|         self.install_lib = None         # set to either purelib or platlib | ||||
|         self.install_scripts = None | ||||
|         self.install_data = None | ||||
|         if HAS_USER_SITE: | ||||
|             self.install_userbase = get_config_var('userbase') | ||||
|             self.install_usersite = get_path('purelib', '%s_user' % os.name) | ||||
| 
 | ||||
|         self.compile = None | ||||
|         self.optimize = None | ||||
| 
 | ||||
|         # These two are for putting non-packagized distributions into their | ||||
|         # own directory and creating a .pth file if it makes sense. | ||||
|         # 'extra_path' comes from the setup file; 'install_path_file' can | ||||
|         # be turned off if it makes no sense to install a .pth file.  (But | ||||
|         # better to install it uselessly than to guess wrong and not | ||||
|         # install it when it's necessary and would be used!)  Currently, | ||||
|         # 'install_path_file' is always true unless some outsider meddles | ||||
|         # with it. | ||||
|         self.extra_path = None | ||||
|         self.install_path_file = True | ||||
| 
 | ||||
|         # 'force' forces installation, even if target files are not | ||||
|         # out-of-date.  'skip_build' skips running the "build" command, | ||||
|         # handy if you know it's not necessary.  'warn_dir' (which is *not* | ||||
|         # a user option, it's just there so the bdist_* commands can turn | ||||
|         # it off) determines whether we warn about installing to a | ||||
|         # directory not in sys.path. | ||||
|         self.force = False | ||||
|         self.skip_build = False | ||||
|         self.warn_dir = True | ||||
| 
 | ||||
|         # These are only here as a conduit from the 'build' command to the | ||||
|         # 'install_*' commands that do the real work.  ('build_base' isn't | ||||
|         # actually used anywhere, but it might be useful in future.)  They | ||||
|         # are not user options, because if the user told the install | ||||
|         # command where the build directory is, that wouldn't affect the | ||||
|         # build command. | ||||
|         self.build_base = None | ||||
|         self.build_lib = None | ||||
| 
 | ||||
|         # Not defined yet because we don't know anything about | ||||
|         # documentation yet. | ||||
|         #self.install_man = None | ||||
|         #self.install_html = None | ||||
|         #self.install_info = None | ||||
| 
 | ||||
|         self.record = None | ||||
|         self.resources = None | ||||
| 
 | ||||
|         # .dist-info related options | ||||
|         self.no_distinfo = None | ||||
|         self.installer = None | ||||
|         self.requested = None | ||||
|         self.no_record = None | ||||
|         self.no_resources = None | ||||
| 
 | ||||
|     # -- Option finalizing methods ------------------------------------- | ||||
|     # (This is rather more involved than for most commands, | ||||
|     # because this is where the policy for installing third- | ||||
|     # party Python modules on various platforms given a wide | ||||
|     # array of user input is decided.  Yes, it's quite complex!) | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         # This method (and its pliant slaves, like 'finalize_unix()', | ||||
|         # 'finalize_other()', and 'select_scheme()') is where the default | ||||
|         # installation directories for modules, extension modules, and | ||||
|         # anything else we care to install from a Python module | ||||
|         # distribution.  Thus, this code makes a pretty important policy | ||||
|         # statement about how third-party stuff is added to a Python | ||||
|         # installation!  Note that the actual work of installation is done | ||||
|         # by the relatively simple 'install_*' commands; they just take | ||||
|         # their orders from the installation directory options determined | ||||
|         # here. | ||||
| 
 | ||||
|         # Check for errors/inconsistencies in the options; first, stuff | ||||
|         # that's wrong on any platform. | ||||
| 
 | ||||
|         if ((self.prefix or self.exec_prefix or self.home) and | ||||
|             (self.install_base or self.install_platbase)): | ||||
|             raise PackagingOptionError( | ||||
|                 "must supply either prefix/exec-prefix/home or " | ||||
|                 "install-base/install-platbase -- not both") | ||||
| 
 | ||||
|         if self.home and (self.prefix or self.exec_prefix): | ||||
|             raise PackagingOptionError( | ||||
|                 "must supply either home or prefix/exec-prefix -- not both") | ||||
| 
 | ||||
|         if HAS_USER_SITE and self.user and ( | ||||
|                 self.prefix or self.exec_prefix or self.home or | ||||
|                 self.install_base or self.install_platbase): | ||||
|             raise PackagingOptionError( | ||||
|                 "can't combine user with prefix/exec_prefix/home or " | ||||
|                 "install_base/install_platbase") | ||||
| 
 | ||||
|         # Next, stuff that's wrong (or dubious) only on certain platforms. | ||||
|         if os.name != "posix": | ||||
|             if self.exec_prefix: | ||||
|                 logger.warning( | ||||
|                     '%s: exec-prefix option ignored on this platform', | ||||
|                     self.get_command_name()) | ||||
|                 self.exec_prefix = None | ||||
| 
 | ||||
|         # Now the interesting logic -- so interesting that we farm it out | ||||
|         # to other methods.  The goal of these methods is to set the final | ||||
|         # values for the install_{lib,scripts,data,...}  options, using as | ||||
|         # input a heady brew of prefix, exec_prefix, home, install_base, | ||||
|         # install_platbase, user-supplied versions of | ||||
|         # install_{purelib,platlib,lib,scripts,data,...}, and the | ||||
|         # INSTALL_SCHEME dictionary above.  Phew! | ||||
| 
 | ||||
|         self.dump_dirs("pre-finalize_{unix,other}") | ||||
| 
 | ||||
|         if os.name == 'posix': | ||||
|             self.finalize_unix() | ||||
|         else: | ||||
|             self.finalize_other() | ||||
| 
 | ||||
|         self.dump_dirs("post-finalize_{unix,other}()") | ||||
| 
 | ||||
|         # Expand configuration variables, tilde, etc. in self.install_base | ||||
|         # and self.install_platbase -- that way, we can use $base or | ||||
|         # $platbase in the other installation directories and not worry | ||||
|         # about needing recursive variable expansion (shudder). | ||||
| 
 | ||||
|         py_version = sys.version.split()[0] | ||||
|         prefix, exec_prefix, srcdir, projectbase = get_config_vars( | ||||
|             'prefix', 'exec_prefix', 'srcdir', 'projectbase') | ||||
| 
 | ||||
|         metadata = self.distribution.metadata | ||||
|         self.config_vars = { | ||||
|             'dist_name': metadata['Name'], | ||||
|             'dist_version': metadata['Version'], | ||||
|             'dist_fullname': metadata.get_fullname(), | ||||
|             'py_version': py_version, | ||||
|             'py_version_short': py_version[:3], | ||||
|             'py_version_nodot': py_version[:3:2], | ||||
|             'sys_prefix': prefix, | ||||
|             'prefix': prefix, | ||||
|             'sys_exec_prefix': exec_prefix, | ||||
|             'exec_prefix': exec_prefix, | ||||
|             'srcdir': srcdir, | ||||
|             'projectbase': projectbase, | ||||
|             } | ||||
| 
 | ||||
|         if HAS_USER_SITE: | ||||
|             self.config_vars['userbase'] = self.install_userbase | ||||
|             self.config_vars['usersite'] = self.install_usersite | ||||
| 
 | ||||
|         self.expand_basedirs() | ||||
| 
 | ||||
|         self.dump_dirs("post-expand_basedirs()") | ||||
| 
 | ||||
|         # Now define config vars for the base directories so we can expand | ||||
|         # everything else. | ||||
|         self.config_vars['base'] = self.install_base | ||||
|         self.config_vars['platbase'] = self.install_platbase | ||||
| 
 | ||||
|         # Expand "~" and configuration variables in the installation | ||||
|         # directories. | ||||
|         self.expand_dirs() | ||||
| 
 | ||||
|         self.dump_dirs("post-expand_dirs()") | ||||
| 
 | ||||
|         # Create directories in the home dir: | ||||
|         if HAS_USER_SITE and self.user: | ||||
|             self.create_home_path() | ||||
| 
 | ||||
|         # Pick the actual directory to install all modules to: either | ||||
|         # install_purelib or install_platlib, depending on whether this | ||||
|         # module distribution is pure or not.  Of course, if the user | ||||
|         # already specified install_lib, use their selection. | ||||
|         if self.install_lib is None: | ||||
|             if self.distribution.ext_modules:  # has extensions: non-pure | ||||
|                 self.install_lib = self.install_platlib | ||||
|             else: | ||||
|                 self.install_lib = self.install_purelib | ||||
| 
 | ||||
|         # Convert directories from Unix /-separated syntax to the local | ||||
|         # convention. | ||||
|         self.convert_paths('lib', 'purelib', 'platlib', | ||||
|                            'scripts', 'data', 'headers') | ||||
|         if HAS_USER_SITE: | ||||
|             self.convert_paths('userbase', 'usersite') | ||||
| 
 | ||||
|         # Well, we're not actually fully completely finalized yet: we still | ||||
|         # have to deal with 'extra_path', which is the hack for allowing | ||||
|         # non-packagized module distributions (hello, Numerical Python!) to | ||||
|         # get their own directories. | ||||
|         self.handle_extra_path() | ||||
|         self.install_libbase = self.install_lib  # needed for .pth file | ||||
|         self.install_lib = os.path.join(self.install_lib, self.extra_dirs) | ||||
| 
 | ||||
|         # If a new root directory was supplied, make all the installation | ||||
|         # dirs relative to it. | ||||
|         if self.root is not None: | ||||
|             self.change_roots('libbase', 'lib', 'purelib', 'platlib', | ||||
|                               'scripts', 'data', 'headers') | ||||
| 
 | ||||
|         self.dump_dirs("after prepending root") | ||||
| 
 | ||||
|         # Find out the build directories, ie. where to install from. | ||||
|         self.set_undefined_options('build', 'build_base', 'build_lib') | ||||
| 
 | ||||
|         # Punt on doc directories for now -- after all, we're punting on | ||||
|         # documentation completely! | ||||
| 
 | ||||
|         if self.no_distinfo is None: | ||||
|             self.no_distinfo = False | ||||
| 
 | ||||
|     def finalize_unix(self): | ||||
|         """Finalize options for posix platforms.""" | ||||
|         if self.install_base is not None or self.install_platbase is not None: | ||||
|             if ((self.install_lib is None and | ||||
|                  self.install_purelib is None and | ||||
|                  self.install_platlib is None) or | ||||
|                 self.install_headers is None or | ||||
|                 self.install_scripts is None or | ||||
|                 self.install_data is None): | ||||
|                 raise PackagingOptionError( | ||||
|                     "install-base or install-platbase supplied, but " | ||||
|                     "installation scheme is incomplete") | ||||
|             return | ||||
| 
 | ||||
|         if HAS_USER_SITE and self.user: | ||||
|             if self.install_userbase is None: | ||||
|                 raise PackagingPlatformError( | ||||
|                     "user base directory is not specified") | ||||
|             self.install_base = self.install_platbase = self.install_userbase | ||||
|             self.select_scheme("posix_user") | ||||
|         elif self.home is not None: | ||||
|             self.install_base = self.install_platbase = self.home | ||||
|             self.select_scheme("posix_home") | ||||
|         else: | ||||
|             if self.prefix is None: | ||||
|                 if self.exec_prefix is not None: | ||||
|                     raise PackagingOptionError( | ||||
|                         "must not supply exec-prefix without prefix") | ||||
| 
 | ||||
|                 self.prefix = os.path.normpath(sys.prefix) | ||||
|                 self.exec_prefix = os.path.normpath(sys.exec_prefix) | ||||
| 
 | ||||
|             else: | ||||
|                 if self.exec_prefix is None: | ||||
|                     self.exec_prefix = self.prefix | ||||
| 
 | ||||
|             self.install_base = self.prefix | ||||
|             self.install_platbase = self.exec_prefix | ||||
|             self.select_scheme("posix_prefix") | ||||
| 
 | ||||
|     def finalize_other(self): | ||||
|         """Finalize options for non-posix platforms""" | ||||
|         if HAS_USER_SITE and self.user: | ||||
|             if self.install_userbase is None: | ||||
|                 raise PackagingPlatformError( | ||||
|                     "user base directory is not specified") | ||||
|             self.install_base = self.install_platbase = self.install_userbase | ||||
|             self.select_scheme(os.name + "_user") | ||||
|         elif self.home is not None: | ||||
|             self.install_base = self.install_platbase = self.home | ||||
|             self.select_scheme("posix_home") | ||||
|         else: | ||||
|             if self.prefix is None: | ||||
|                 self.prefix = os.path.normpath(sys.prefix) | ||||
| 
 | ||||
|             self.install_base = self.install_platbase = self.prefix | ||||
|             try: | ||||
|                 self.select_scheme(os.name) | ||||
|             except KeyError: | ||||
|                 raise PackagingPlatformError( | ||||
|                     "no support for installation on '%s'" % os.name) | ||||
| 
 | ||||
|     def dump_dirs(self, msg): | ||||
|         """Dump the list of user options.""" | ||||
|         logger.debug(msg + ":") | ||||
|         for opt in self.user_options: | ||||
|             opt_name = opt[0] | ||||
|             if opt_name[-1] == "=": | ||||
|                 opt_name = opt_name[0:-1] | ||||
|             if opt_name in self.negative_opt: | ||||
|                 opt_name = self.negative_opt[opt_name] | ||||
|                 opt_name = opt_name.replace('-', '_') | ||||
|                 val = not getattr(self, opt_name) | ||||
|             else: | ||||
|                 opt_name = opt_name.replace('-', '_') | ||||
|                 val = getattr(self, opt_name) | ||||
|             logger.debug("  %s: %s", opt_name, val) | ||||
| 
 | ||||
|     def select_scheme(self, name): | ||||
|         """Set the install directories by applying the install schemes.""" | ||||
|         # it's the caller's problem if they supply a bad name! | ||||
|         scheme = get_paths(name, expand=False) | ||||
|         for key, value in scheme.items(): | ||||
|             if key == 'platinclude': | ||||
|                 key = 'headers' | ||||
|                 value = os.path.join(value, self.distribution.metadata['Name']) | ||||
|             attrname = 'install_' + key | ||||
|             if hasattr(self, attrname): | ||||
|                 if getattr(self, attrname) is None: | ||||
|                     setattr(self, attrname, value) | ||||
| 
 | ||||
|     def _expand_attrs(self, attrs): | ||||
|         for attr in attrs: | ||||
|             val = getattr(self, attr) | ||||
|             if val is not None: | ||||
|                 if os.name == 'posix' or os.name == 'nt': | ||||
|                     val = os.path.expanduser(val) | ||||
|                 # see if we want to push this work in sysconfig XXX | ||||
|                 val = sysconfig._subst_vars(val, self.config_vars) | ||||
|                 setattr(self, attr, val) | ||||
| 
 | ||||
|     def expand_basedirs(self): | ||||
|         """Call `os.path.expanduser` on install_{base,platbase} and root.""" | ||||
|         self._expand_attrs(['install_base', 'install_platbase', 'root']) | ||||
| 
 | ||||
|     def expand_dirs(self): | ||||
|         """Call `os.path.expanduser` on install dirs.""" | ||||
|         self._expand_attrs(['install_purelib', 'install_platlib', | ||||
|                             'install_lib', 'install_headers', | ||||
|                             'install_scripts', 'install_data']) | ||||
| 
 | ||||
|     def convert_paths(self, *names): | ||||
|         """Call `convert_path` over `names`.""" | ||||
|         for name in names: | ||||
|             attr = "install_" + name | ||||
|             setattr(self, attr, convert_path(getattr(self, attr))) | ||||
| 
 | ||||
|     def handle_extra_path(self): | ||||
|         """Set `path_file` and `extra_dirs` using `extra_path`.""" | ||||
|         if self.extra_path is None: | ||||
|             self.extra_path = self.distribution.extra_path | ||||
| 
 | ||||
|         if self.extra_path is not None: | ||||
|             if isinstance(self.extra_path, str): | ||||
|                 self.extra_path = self.extra_path.split(',') | ||||
| 
 | ||||
|             if len(self.extra_path) == 1: | ||||
|                 path_file = extra_dirs = self.extra_path[0] | ||||
|             elif len(self.extra_path) == 2: | ||||
|                 path_file, extra_dirs = self.extra_path | ||||
|             else: | ||||
|                 raise PackagingOptionError( | ||||
|                     "'extra_path' option must be a list, tuple, or " | ||||
|                     "comma-separated string with 1 or 2 elements") | ||||
| 
 | ||||
|             # convert to local form in case Unix notation used (as it | ||||
|             # should be in setup scripts) | ||||
|             extra_dirs = convert_path(extra_dirs) | ||||
|         else: | ||||
|             path_file = None | ||||
|             extra_dirs = '' | ||||
| 
 | ||||
|         # XXX should we warn if path_file and not extra_dirs? (in which | ||||
|         # case the path file would be harmless but pointless) | ||||
|         self.path_file = path_file | ||||
|         self.extra_dirs = extra_dirs | ||||
| 
 | ||||
|     def change_roots(self, *names): | ||||
|         """Change the install direcories pointed by name using root.""" | ||||
|         for name in names: | ||||
|             attr = "install_" + name | ||||
|             setattr(self, attr, change_root(self.root, getattr(self, attr))) | ||||
| 
 | ||||
|     def create_home_path(self): | ||||
|         """Create directories under ~.""" | ||||
|         if HAS_USER_SITE and not self.user: | ||||
|             return | ||||
|         home = convert_path(os.path.expanduser("~")) | ||||
|         for name, path in self.config_vars.items(): | ||||
|             if path.startswith(home) and not os.path.isdir(path): | ||||
|                 os.makedirs(path, 0o700) | ||||
| 
 | ||||
|     # -- Command execution methods ------------------------------------- | ||||
| 
 | ||||
|     def run(self): | ||||
|         """Runs the command.""" | ||||
|         # Obviously have to build before we can install | ||||
|         if not self.skip_build: | ||||
|             self.run_command('build') | ||||
|             # If we built for any other platform, we can't install. | ||||
|             build_plat = self.distribution.get_command_obj('build').plat_name | ||||
|             # check warn_dir - it is a clue that the 'install_dist' is happening | ||||
|             # internally, and not to sys.path, so we don't check the platform | ||||
|             # matches what we are running. | ||||
|             if self.warn_dir and build_plat != get_platform(): | ||||
|                 raise PackagingPlatformError("Can't install when " | ||||
|                                              "cross-compiling") | ||||
| 
 | ||||
|         # Run all sub-commands (at least those that need to be run) | ||||
|         for cmd_name in self.get_sub_commands(): | ||||
|             self.run_command(cmd_name) | ||||
| 
 | ||||
|         if self.path_file: | ||||
|             self.create_path_file() | ||||
| 
 | ||||
|         # write list of installed files, if requested. | ||||
|         if self.record: | ||||
|             outputs = self.get_outputs() | ||||
|             if self.root:               # strip any package prefix | ||||
|                 root_len = len(self.root) | ||||
|                 for counter in range(len(outputs)): | ||||
|                     outputs[counter] = outputs[counter][root_len:] | ||||
|             self.execute(write_file, | ||||
|                          (self.record, outputs), | ||||
|                          "writing list of installed files to '%s'" % | ||||
|                          self.record) | ||||
| 
 | ||||
|         normpath, normcase = os.path.normpath, os.path.normcase | ||||
|         sys_path = [normcase(normpath(p)) for p in sys.path] | ||||
|         install_lib = normcase(normpath(self.install_lib)) | ||||
|         if (self.warn_dir and | ||||
|             not (self.path_file and self.install_path_file) and | ||||
|             install_lib not in sys_path): | ||||
|             logger.debug(("modules installed to '%s', which is not in " | ||||
|                        "Python's module search path (sys.path) -- " | ||||
|                        "you'll have to change the search path yourself"), | ||||
|                        self.install_lib) | ||||
| 
 | ||||
|     def create_path_file(self): | ||||
|         """Creates the .pth file""" | ||||
|         filename = os.path.join(self.install_libbase, | ||||
|                                 self.path_file + ".pth") | ||||
|         if self.install_path_file: | ||||
|             self.execute(write_file, | ||||
|                          (filename, [self.extra_dirs]), | ||||
|                          "creating %s" % filename) | ||||
|         else: | ||||
|             logger.warning('%s: path file %r not created', | ||||
|                            self.get_command_name(),  filename) | ||||
| 
 | ||||
|     # -- Reporting methods --------------------------------------------- | ||||
| 
 | ||||
|     def get_outputs(self): | ||||
|         """Assembles the outputs of all the sub-commands.""" | ||||
|         outputs = [] | ||||
|         for cmd_name in self.get_sub_commands(): | ||||
|             cmd = self.get_finalized_command(cmd_name) | ||||
|             # Add the contents of cmd.get_outputs(), ensuring | ||||
|             # that outputs doesn't contain duplicate entries | ||||
|             for filename in cmd.get_outputs(): | ||||
|                 if filename not in outputs: | ||||
|                     outputs.append(filename) | ||||
| 
 | ||||
|         if self.path_file and self.install_path_file: | ||||
|             outputs.append(os.path.join(self.install_libbase, | ||||
|                                         self.path_file + ".pth")) | ||||
| 
 | ||||
|         return outputs | ||||
| 
 | ||||
|     def get_inputs(self): | ||||
|         """Returns the inputs of all the sub-commands""" | ||||
|         # XXX gee, this looks familiar ;-( | ||||
|         inputs = [] | ||||
|         for cmd_name in self.get_sub_commands(): | ||||
|             cmd = self.get_finalized_command(cmd_name) | ||||
|             inputs.extend(cmd.get_inputs()) | ||||
| 
 | ||||
|         return inputs | ||||
| 
 | ||||
|     # -- Predicates for sub-command list ------------------------------- | ||||
| 
 | ||||
|     def has_lib(self): | ||||
|         """Returns true if the current distribution has any Python | ||||
|         modules to install.""" | ||||
|         return (self.distribution.has_pure_modules() or | ||||
|                 self.distribution.has_ext_modules()) | ||||
| 
 | ||||
|     def has_headers(self): | ||||
|         """Returns true if the current distribution has any headers to | ||||
|         install.""" | ||||
|         return self.distribution.has_headers() | ||||
| 
 | ||||
|     def has_scripts(self): | ||||
|         """Returns true if the current distribution has any scripts to. | ||||
|         install.""" | ||||
|         return self.distribution.has_scripts() | ||||
| 
 | ||||
|     def has_data(self): | ||||
|         """Returns true if the current distribution has any data to. | ||||
|         install.""" | ||||
|         return self.distribution.has_data_files() | ||||
| 
 | ||||
|     # 'sub_commands': a list of commands this command might have to run to | ||||
|     # get its work done.  See cmd.py for more info. | ||||
|     sub_commands = [('install_lib', has_lib), | ||||
|                     ('install_headers', has_headers), | ||||
|                     ('install_scripts', has_scripts), | ||||
|                     ('install_data', has_data), | ||||
|                     # keep install_distinfo last, as it needs the record | ||||
|                     # with files to be completely generated | ||||
|                     ('install_distinfo', lambda self: not self.no_distinfo), | ||||
|                    ] | ||||
							
								
								
									
										175
									
								
								Lib/packaging/command/install_distinfo.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								Lib/packaging/command/install_distinfo.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,175 @@ | |||
| """Create the PEP 376-compliant .dist-info directory.""" | ||||
| 
 | ||||
| # Forked from the former install_egg_info command by Josip Djolonga | ||||
| 
 | ||||
| import csv | ||||
| import os | ||||
| import re | ||||
| import hashlib | ||||
| 
 | ||||
| from packaging.command.cmd import Command | ||||
| from packaging import logger | ||||
| from shutil import rmtree | ||||
| 
 | ||||
| 
 | ||||
| class install_distinfo(Command): | ||||
| 
 | ||||
|     description = 'create a .dist-info directory for the distribution' | ||||
| 
 | ||||
|     user_options = [ | ||||
|         ('distinfo-dir=', None, | ||||
|          "directory where the the .dist-info directory will be installed"), | ||||
|         ('installer=', None, | ||||
|          "the name of the installer"), | ||||
|         ('requested', None, | ||||
|          "generate a REQUESTED file"), | ||||
|         ('no-requested', None, | ||||
|          "do not generate a REQUESTED file"), | ||||
|         ('no-record', None, | ||||
|          "do not generate a RECORD file"), | ||||
|         ('no-resources', None, | ||||
|          "do not generate a RESSOURCES list installed file") | ||||
|     ] | ||||
| 
 | ||||
|     boolean_options = ['requested', 'no-record', 'no-resources'] | ||||
| 
 | ||||
|     negative_opt = {'no-requested': 'requested'} | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.distinfo_dir = None | ||||
|         self.installer = None | ||||
|         self.requested = None | ||||
|         self.no_record = None | ||||
|         self.no_resources = None | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         self.set_undefined_options('install_dist', | ||||
|                                    'installer', 'requested', 'no_record') | ||||
| 
 | ||||
|         self.set_undefined_options('install_lib', | ||||
|                                    ('install_dir', 'distinfo_dir')) | ||||
| 
 | ||||
|         if self.installer is None: | ||||
|             # FIXME distutils or packaging? | ||||
|             # + document default in the option help text above and in install | ||||
|             self.installer = 'distutils' | ||||
|         if self.requested is None: | ||||
|             self.requested = True | ||||
|         if self.no_record is None: | ||||
|             self.no_record = False | ||||
|         if self.no_resources is None: | ||||
|             self.no_resources = False | ||||
| 
 | ||||
|         metadata = self.distribution.metadata | ||||
| 
 | ||||
|         basename = "%s-%s.dist-info" % ( | ||||
|             to_filename(safe_name(metadata['Name'])), | ||||
|             to_filename(safe_version(metadata['Version']))) | ||||
| 
 | ||||
|         self.distinfo_dir = os.path.join(self.distinfo_dir, basename) | ||||
|         self.outputs = [] | ||||
| 
 | ||||
|     def run(self): | ||||
|         # FIXME dry-run should be used at a finer level, so that people get | ||||
|         # useful logging output and can have an idea of what the command would | ||||
|         # have done | ||||
|         if not self.dry_run: | ||||
|             target = self.distinfo_dir | ||||
| 
 | ||||
|             if os.path.isdir(target) and not os.path.islink(target): | ||||
|                 rmtree(target) | ||||
|             elif os.path.exists(target): | ||||
|                 self.execute(os.unlink, (self.distinfo_dir,), | ||||
|                              "removing " + target) | ||||
| 
 | ||||
|             self.execute(os.makedirs, (target,), "creating " + target) | ||||
| 
 | ||||
|             metadata_path = os.path.join(self.distinfo_dir, 'METADATA') | ||||
|             logger.info('creating %s', metadata_path) | ||||
|             self.distribution.metadata.write(metadata_path) | ||||
|             self.outputs.append(metadata_path) | ||||
| 
 | ||||
|             installer_path = os.path.join(self.distinfo_dir, 'INSTALLER') | ||||
|             logger.info('creating %s', installer_path) | ||||
|             with open(installer_path, 'w') as f: | ||||
|                 f.write(self.installer) | ||||
|             self.outputs.append(installer_path) | ||||
| 
 | ||||
|             if self.requested: | ||||
|                 requested_path = os.path.join(self.distinfo_dir, 'REQUESTED') | ||||
|                 logger.info('creating %s', requested_path) | ||||
|                 open(requested_path, 'w').close() | ||||
|                 self.outputs.append(requested_path) | ||||
| 
 | ||||
| 
 | ||||
|             if not self.no_resources: | ||||
|                 install_data = self.get_finalized_command('install_data') | ||||
|                 if install_data.get_resources_out() != []: | ||||
|                     resources_path = os.path.join(self.distinfo_dir, | ||||
|                                                   'RESOURCES') | ||||
|                     logger.info('creating %s', resources_path) | ||||
|                     with open(resources_path, 'wb') as f: | ||||
|                         writer = csv.writer(f, delimiter=',', | ||||
|                                             lineterminator=os.linesep, | ||||
|                                             quotechar='"') | ||||
|                         for tuple in install_data.get_resources_out(): | ||||
|                             writer.writerow(tuple) | ||||
| 
 | ||||
|                         self.outputs.append(resources_path) | ||||
| 
 | ||||
|             if not self.no_record: | ||||
|                 record_path = os.path.join(self.distinfo_dir, 'RECORD') | ||||
|                 logger.info('creating %s', record_path) | ||||
|                 with open(record_path, 'w', encoding='utf-8') as f: | ||||
|                     writer = csv.writer(f, delimiter=',', | ||||
|                                         lineterminator=os.linesep, | ||||
|                                         quotechar='"') | ||||
| 
 | ||||
|                     install = self.get_finalized_command('install_dist') | ||||
| 
 | ||||
|                     for fpath in install.get_outputs(): | ||||
|                         if fpath.endswith('.pyc') or fpath.endswith('.pyo'): | ||||
|                             # do not put size and md5 hash, as in PEP-376 | ||||
|                             writer.writerow((fpath, '', '')) | ||||
|                         else: | ||||
|                             size = os.path.getsize(fpath) | ||||
|                             with open(fpath, 'r') as fp: | ||||
|                                 hash = hashlib.md5() | ||||
|                                 hash.update(fp.read().encode()) | ||||
|                             md5sum = hash.hexdigest() | ||||
|                             writer.writerow((fpath, md5sum, size)) | ||||
| 
 | ||||
|                     # add the RECORD file itself | ||||
|                     writer.writerow((record_path, '', '')) | ||||
|                     self.outputs.append(record_path) | ||||
| 
 | ||||
|     def get_outputs(self): | ||||
|         return self.outputs | ||||
| 
 | ||||
| 
 | ||||
| # The following functions are taken from setuptools' pkg_resources module. | ||||
| 
 | ||||
| def safe_name(name): | ||||
|     """Convert an arbitrary string to a standard distribution name | ||||
| 
 | ||||
|     Any runs of non-alphanumeric/. characters are replaced with a single '-'. | ||||
|     """ | ||||
|     return re.sub('[^A-Za-z0-9.]+', '-', name) | ||||
| 
 | ||||
| 
 | ||||
| def safe_version(version): | ||||
|     """Convert an arbitrary string to a standard version string | ||||
| 
 | ||||
|     Spaces become dots, and all other non-alphanumeric characters become | ||||
|     dashes, with runs of multiple dashes condensed to a single dash. | ||||
|     """ | ||||
|     version = version.replace(' ', '.') | ||||
|     return re.sub('[^A-Za-z0-9.]+', '-', version) | ||||
| 
 | ||||
| 
 | ||||
| def to_filename(name): | ||||
|     """Convert a project or version name to its filename-escaped form | ||||
| 
 | ||||
|     Any '-' characters are currently replaced with '_'. | ||||
|     """ | ||||
|     return name.replace('-', '_') | ||||
							
								
								
									
										43
									
								
								Lib/packaging/command/install_headers.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								Lib/packaging/command/install_headers.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | |||
| """Install C/C++ header files to the Python include directory.""" | ||||
| 
 | ||||
| from packaging.command.cmd import Command | ||||
| 
 | ||||
| 
 | ||||
| # XXX force is never used | ||||
| class install_headers(Command): | ||||
| 
 | ||||
|     description = "install C/C++ header files" | ||||
| 
 | ||||
|     user_options = [('install-dir=', 'd', | ||||
|                      "directory to install header files to"), | ||||
|                     ('force', 'f', | ||||
|                      "force installation (overwrite existing files)"), | ||||
|                    ] | ||||
| 
 | ||||
|     boolean_options = ['force'] | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.install_dir = None | ||||
|         self.force = False | ||||
|         self.outfiles = [] | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         self.set_undefined_options('install_dist', | ||||
|                                    ('install_headers', 'install_dir'), | ||||
|                                    'force') | ||||
| 
 | ||||
|     def run(self): | ||||
|         headers = self.distribution.headers | ||||
|         if not headers: | ||||
|             return | ||||
| 
 | ||||
|         self.mkpath(self.install_dir) | ||||
|         for header in headers: | ||||
|             out = self.copy_file(header, self.install_dir)[0] | ||||
|             self.outfiles.append(out) | ||||
| 
 | ||||
|     def get_inputs(self): | ||||
|         return self.distribution.headers or [] | ||||
| 
 | ||||
|     def get_outputs(self): | ||||
|         return self.outfiles | ||||
							
								
								
									
										222
									
								
								Lib/packaging/command/install_lib.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								Lib/packaging/command/install_lib.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,222 @@ | |||
| """Install all modules (extensions and pure Python).""" | ||||
| 
 | ||||
| import os | ||||
| import sys | ||||
| import logging | ||||
| 
 | ||||
| from packaging import logger | ||||
| from packaging.command.cmd import Command | ||||
| from packaging.errors import PackagingOptionError | ||||
| 
 | ||||
| 
 | ||||
| # Extension for Python source files. | ||||
| if hasattr(os, 'extsep'): | ||||
|     PYTHON_SOURCE_EXTENSION = os.extsep + "py" | ||||
| else: | ||||
|     PYTHON_SOURCE_EXTENSION = ".py" | ||||
| 
 | ||||
| class install_lib(Command): | ||||
| 
 | ||||
|     description = "install all modules (extensions and pure Python)" | ||||
| 
 | ||||
|     # The byte-compilation options are a tad confusing.  Here are the | ||||
|     # possible scenarios: | ||||
|     #   1) no compilation at all (--no-compile --no-optimize) | ||||
|     #   2) compile .pyc only (--compile --no-optimize; default) | ||||
|     #   3) compile .pyc and "level 1" .pyo (--compile --optimize) | ||||
|     #   4) compile "level 1" .pyo only (--no-compile --optimize) | ||||
|     #   5) compile .pyc and "level 2" .pyo (--compile --optimize-more) | ||||
|     #   6) compile "level 2" .pyo only (--no-compile --optimize-more) | ||||
|     # | ||||
|     # The UI for this is two option, 'compile' and 'optimize'. | ||||
|     # 'compile' is strictly boolean, and only decides whether to | ||||
|     # generate .pyc files.  'optimize' is three-way (0, 1, or 2), and | ||||
|     # decides both whether to generate .pyo files and what level of | ||||
|     # optimization to use. | ||||
| 
 | ||||
|     user_options = [ | ||||
|         ('install-dir=', 'd', "directory to install to"), | ||||
|         ('build-dir=','b', "build directory (where to install from)"), | ||||
|         ('force', 'f', "force installation (overwrite existing files)"), | ||||
|         ('compile', 'c', "compile .py to .pyc [default]"), | ||||
|         ('no-compile', None, "don't compile .py files"), | ||||
|         ('optimize=', 'O', | ||||
|          "also compile with optimization: -O1 for \"python -O\", " | ||||
|          "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), | ||||
|         ('skip-build', None, "skip the build steps"), | ||||
|         ] | ||||
| 
 | ||||
|     boolean_options = ['force', 'compile', 'skip-build'] | ||||
|     negative_opt = {'no-compile' : 'compile'} | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         # let the 'install_dist' command dictate our installation directory | ||||
|         self.install_dir = None | ||||
|         self.build_dir = None | ||||
|         self.force = False | ||||
|         self.compile = None | ||||
|         self.optimize = None | ||||
|         self.skip_build = None | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         # Get all the information we need to install pure Python modules | ||||
|         # from the umbrella 'install_dist' command -- build (source) directory, | ||||
|         # install (target) directory, and whether to compile .py files. | ||||
|         self.set_undefined_options('install_dist', | ||||
|                                    ('build_lib', 'build_dir'), | ||||
|                                    ('install_lib', 'install_dir'), | ||||
|                                    'force', 'compile', 'optimize', 'skip_build') | ||||
| 
 | ||||
|         if self.compile is None: | ||||
|             self.compile = True | ||||
|         if self.optimize is None: | ||||
|             self.optimize = 0 | ||||
| 
 | ||||
|         if not isinstance(self.optimize, int): | ||||
|             try: | ||||
|                 self.optimize = int(self.optimize) | ||||
|                 if self.optimize not in (0, 1, 2): | ||||
|                     raise AssertionError | ||||
|             except (ValueError, AssertionError): | ||||
|                 raise PackagingOptionError("optimize must be 0, 1, or 2") | ||||
| 
 | ||||
|     def run(self): | ||||
|         # Make sure we have built everything we need first | ||||
|         self.build() | ||||
| 
 | ||||
|         # Install everything: simply dump the entire contents of the build | ||||
|         # directory to the installation directory (that's the beauty of | ||||
|         # having a build directory!) | ||||
|         outfiles = self.install() | ||||
| 
 | ||||
|         # (Optionally) compile .py to .pyc | ||||
|         if outfiles is not None and self.distribution.has_pure_modules(): | ||||
|             self.byte_compile(outfiles) | ||||
| 
 | ||||
|     # -- Top-level worker functions ------------------------------------ | ||||
|     # (called from 'run()') | ||||
| 
 | ||||
|     def build(self): | ||||
|         if not self.skip_build: | ||||
|             if self.distribution.has_pure_modules(): | ||||
|                 self.run_command('build_py') | ||||
|             if self.distribution.has_ext_modules(): | ||||
|                 self.run_command('build_ext') | ||||
| 
 | ||||
|     def install(self): | ||||
|         if os.path.isdir(self.build_dir): | ||||
|             outfiles = self.copy_tree(self.build_dir, self.install_dir) | ||||
|         else: | ||||
|             logger.warning( | ||||
|                 '%s: %r does not exist -- no Python modules to install', | ||||
|                 self.get_command_name(), self.build_dir) | ||||
|             return | ||||
|         return outfiles | ||||
| 
 | ||||
|     def byte_compile(self, files): | ||||
|         if getattr(sys, 'dont_write_bytecode'): | ||||
|             # XXX do we want this?  because a Python runs without bytecode | ||||
|             # doesn't mean that the *dists should not contain bytecode | ||||
|             #--or does it? | ||||
|             logger.warning('%s: byte-compiling is disabled, skipping.', | ||||
|                            self.get_command_name()) | ||||
|             return | ||||
| 
 | ||||
|         from packaging.util import byte_compile | ||||
| 
 | ||||
|         # Get the "--root" directory supplied to the "install_dist" command, | ||||
|         # and use it as a prefix to strip off the purported filename | ||||
|         # encoded in bytecode files.  This is far from complete, but it | ||||
|         # should at least generate usable bytecode in RPM distributions. | ||||
|         install_root = self.get_finalized_command('install_dist').root | ||||
| 
 | ||||
|         # Temporary kludge until we remove the verbose arguments and use | ||||
|         # logging everywhere | ||||
|         verbose = logger.getEffectiveLevel() >= logging.DEBUG | ||||
| 
 | ||||
|         if self.compile: | ||||
|             byte_compile(files, optimize=0, | ||||
|                          force=self.force, prefix=install_root, | ||||
|                          dry_run=self.dry_run) | ||||
|         if self.optimize > 0: | ||||
|             byte_compile(files, optimize=self.optimize, | ||||
|                          force=self.force, prefix=install_root, | ||||
|                          verbose=verbose, | ||||
|                          dry_run=self.dry_run) | ||||
| 
 | ||||
| 
 | ||||
|     # -- Utility methods ----------------------------------------------- | ||||
| 
 | ||||
|     def _mutate_outputs(self, has_any, build_cmd, cmd_option, output_dir): | ||||
|         if not has_any: | ||||
|             return [] | ||||
| 
 | ||||
|         build_cmd = self.get_finalized_command(build_cmd) | ||||
|         build_files = build_cmd.get_outputs() | ||||
|         build_dir = getattr(build_cmd, cmd_option) | ||||
| 
 | ||||
|         prefix_len = len(build_dir) + len(os.sep) | ||||
|         outputs = [] | ||||
|         for file in build_files: | ||||
|             outputs.append(os.path.join(output_dir, file[prefix_len:])) | ||||
| 
 | ||||
|         return outputs | ||||
| 
 | ||||
|     def _bytecode_filenames(self, py_filenames): | ||||
|         bytecode_files = [] | ||||
|         for py_file in py_filenames: | ||||
|             # Since build_py handles package data installation, the | ||||
|             # list of outputs can contain more than just .py files. | ||||
|             # Make sure we only report bytecode for the .py files. | ||||
|             ext = os.path.splitext(os.path.normcase(py_file))[1] | ||||
|             if ext != PYTHON_SOURCE_EXTENSION: | ||||
|                 continue | ||||
|             if self.compile: | ||||
|                 bytecode_files.append(py_file + "c") | ||||
|             if self.optimize > 0: | ||||
|                 bytecode_files.append(py_file + "o") | ||||
| 
 | ||||
|         return bytecode_files | ||||
| 
 | ||||
| 
 | ||||
|     # -- External interface -------------------------------------------- | ||||
|     # (called by outsiders) | ||||
| 
 | ||||
|     def get_outputs(self): | ||||
|         """Return the list of files that would be installed if this command | ||||
|         were actually run.  Not affected by the "dry-run" flag or whether | ||||
|         modules have actually been built yet. | ||||
|         """ | ||||
|         pure_outputs = \ | ||||
|             self._mutate_outputs(self.distribution.has_pure_modules(), | ||||
|                                  'build_py', 'build_lib', | ||||
|                                  self.install_dir) | ||||
|         if self.compile: | ||||
|             bytecode_outputs = self._bytecode_filenames(pure_outputs) | ||||
|         else: | ||||
|             bytecode_outputs = [] | ||||
| 
 | ||||
|         ext_outputs = \ | ||||
|             self._mutate_outputs(self.distribution.has_ext_modules(), | ||||
|                                  'build_ext', 'build_lib', | ||||
|                                  self.install_dir) | ||||
| 
 | ||||
|         return pure_outputs + bytecode_outputs + ext_outputs | ||||
| 
 | ||||
|     def get_inputs(self): | ||||
|         """Get the list of files that are input to this command, ie. the | ||||
|         files that get installed as they are named in the build tree. | ||||
|         The files in this list correspond one-to-one to the output | ||||
|         filenames returned by 'get_outputs()'. | ||||
|         """ | ||||
|         inputs = [] | ||||
| 
 | ||||
|         if self.distribution.has_pure_modules(): | ||||
|             build_py = self.get_finalized_command('build_py') | ||||
|             inputs.extend(build_py.get_outputs()) | ||||
| 
 | ||||
|         if self.distribution.has_ext_modules(): | ||||
|             build_ext = self.get_finalized_command('build_ext') | ||||
|             inputs.extend(build_ext.get_outputs()) | ||||
| 
 | ||||
|         return inputs | ||||
							
								
								
									
										59
									
								
								Lib/packaging/command/install_scripts.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								Lib/packaging/command/install_scripts.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,59 @@ | |||
| """Install scripts.""" | ||||
| 
 | ||||
| # Contributed by Bastian Kleineidam | ||||
| 
 | ||||
| import os | ||||
| from packaging.command.cmd import Command | ||||
| from packaging import logger | ||||
| 
 | ||||
| class install_scripts(Command): | ||||
| 
 | ||||
|     description = "install scripts (Python or otherwise)" | ||||
| 
 | ||||
|     user_options = [ | ||||
|         ('install-dir=', 'd', "directory to install scripts to"), | ||||
|         ('build-dir=','b', "build directory (where to install from)"), | ||||
|         ('force', 'f', "force installation (overwrite existing files)"), | ||||
|         ('skip-build', None, "skip the build steps"), | ||||
|     ] | ||||
| 
 | ||||
|     boolean_options = ['force', 'skip-build'] | ||||
| 
 | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.install_dir = None | ||||
|         self.force = False | ||||
|         self.build_dir = None | ||||
|         self.skip_build = None | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         self.set_undefined_options('build', ('build_scripts', 'build_dir')) | ||||
|         self.set_undefined_options('install_dist', | ||||
|                                    ('install_scripts', 'install_dir'), | ||||
|                                    'force', 'skip_build') | ||||
| 
 | ||||
|     def run(self): | ||||
|         if not self.skip_build: | ||||
|             self.run_command('build_scripts') | ||||
| 
 | ||||
|         if not os.path.exists(self.build_dir): | ||||
|             self.outfiles = [] | ||||
|             return | ||||
| 
 | ||||
|         self.outfiles = self.copy_tree(self.build_dir, self.install_dir) | ||||
|         if os.name == 'posix': | ||||
|             # Set the executable bits (owner, group, and world) on | ||||
|             # all the scripts we just installed. | ||||
|             for file in self.get_outputs(): | ||||
|                 if self.dry_run: | ||||
|                     logger.info("changing mode of %s", file) | ||||
|                 else: | ||||
|                     mode = (os.stat(file).st_mode | 0o555) & 0o7777 | ||||
|                     logger.info("changing mode of %s to %o", file, mode) | ||||
|                     os.chmod(file, mode) | ||||
| 
 | ||||
|     def get_inputs(self): | ||||
|         return self.distribution.scripts or [] | ||||
| 
 | ||||
|     def get_outputs(self): | ||||
|         return self.outfiles or [] | ||||
							
								
								
									
										282
									
								
								Lib/packaging/command/register.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								Lib/packaging/command/register.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,282 @@ | |||
| """Register a release with a project index.""" | ||||
| 
 | ||||
| # Contributed by Richard Jones | ||||
| 
 | ||||
| import io | ||||
| import getpass | ||||
| import urllib.error | ||||
| import urllib.parse | ||||
| import urllib.request | ||||
| 
 | ||||
| from packaging import logger | ||||
| from packaging.util import (read_pypirc, generate_pypirc, DEFAULT_REPOSITORY, | ||||
|                             DEFAULT_REALM, get_pypirc_path) | ||||
| from packaging.command.cmd import Command | ||||
| 
 | ||||
| class register(Command): | ||||
| 
 | ||||
|     description = "register a release with PyPI" | ||||
|     user_options = [ | ||||
|         ('repository=', 'r', | ||||
|          "repository URL [default: %s]" % DEFAULT_REPOSITORY), | ||||
|         ('show-response', None, | ||||
|          "display full response text from server"), | ||||
|         ('list-classifiers', None, | ||||
|          "list valid Trove classifiers"), | ||||
|         ('strict', None , | ||||
|          "stop the registration if the metadata is not fully compliant") | ||||
|         ] | ||||
| 
 | ||||
|     boolean_options = ['show-response', 'list-classifiers', 'strict'] | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.repository = None | ||||
|         self.realm = None | ||||
|         self.show_response = False | ||||
|         self.list_classifiers = False | ||||
|         self.strict = False | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         if self.repository is None: | ||||
|             self.repository = DEFAULT_REPOSITORY | ||||
|         if self.realm is None: | ||||
|             self.realm = DEFAULT_REALM | ||||
| 
 | ||||
|     def run(self): | ||||
|         self._set_config() | ||||
| 
 | ||||
|         # Check the package metadata | ||||
|         check = self.distribution.get_command_obj('check') | ||||
|         if check.strict != self.strict and not check.all: | ||||
|             # If check was already run but with different options, | ||||
|             # re-run it | ||||
|             check.strict = self.strict | ||||
|             check.all = True | ||||
|             self.distribution.have_run.pop('check', None) | ||||
|             self.run_command('check') | ||||
| 
 | ||||
|         if self.dry_run: | ||||
|             self.verify_metadata() | ||||
|         elif self.list_classifiers: | ||||
|             self.classifiers() | ||||
|         else: | ||||
|             self.send_metadata() | ||||
| 
 | ||||
|     def _set_config(self): | ||||
|         ''' Reads the configuration file and set attributes. | ||||
|         ''' | ||||
|         config = read_pypirc(self.repository, self.realm) | ||||
|         if config != {}: | ||||
|             self.username = config['username'] | ||||
|             self.password = config['password'] | ||||
|             self.repository = config['repository'] | ||||
|             self.realm = config['realm'] | ||||
|             self.has_config = True | ||||
|         else: | ||||
|             if self.repository not in ('pypi', DEFAULT_REPOSITORY): | ||||
|                 raise ValueError('%s not found in .pypirc' % self.repository) | ||||
|             if self.repository == 'pypi': | ||||
|                 self.repository = DEFAULT_REPOSITORY | ||||
|             self.has_config = False | ||||
| 
 | ||||
|     def classifiers(self): | ||||
|         ''' Fetch the list of classifiers from the server. | ||||
|         ''' | ||||
|         response = urllib.request.urlopen(self.repository+'?:action=list_classifiers') | ||||
|         logger.info(response.read()) | ||||
| 
 | ||||
|     def verify_metadata(self): | ||||
|         ''' Send the metadata to the package index server to be checked. | ||||
|         ''' | ||||
|         # send the info to the server and report the result | ||||
|         code, result = self.post_to_server(self.build_post_data('verify')) | ||||
|         logger.info('server response (%s): %s', code, result) | ||||
| 
 | ||||
| 
 | ||||
|     def send_metadata(self): | ||||
|         ''' Send the metadata to the package index server. | ||||
| 
 | ||||
|             Well, do the following: | ||||
|             1. figure who the user is, and then | ||||
|             2. send the data as a Basic auth'ed POST. | ||||
| 
 | ||||
|             First we try to read the username/password from $HOME/.pypirc, | ||||
|             which is a ConfigParser-formatted file with a section | ||||
|             [distutils] containing username and password entries (both | ||||
|             in clear text). Eg: | ||||
| 
 | ||||
|                 [distutils] | ||||
|                 index-servers = | ||||
|                     pypi | ||||
| 
 | ||||
|                 [pypi] | ||||
|                 username: fred | ||||
|                 password: sekrit | ||||
| 
 | ||||
|             Otherwise, to figure who the user is, we offer the user three | ||||
|             choices: | ||||
| 
 | ||||
|              1. use existing login, | ||||
|              2. register as a new user, or | ||||
|              3. set the password to a random string and email the user. | ||||
| 
 | ||||
|         ''' | ||||
|         # TODO factor registration out into another method | ||||
|         # TODO use print to print, not logging | ||||
| 
 | ||||
|         # see if we can short-cut and get the username/password from the | ||||
|         # config | ||||
|         if self.has_config: | ||||
|             choice = '1' | ||||
|             username = self.username | ||||
|             password = self.password | ||||
|         else: | ||||
|             choice = 'x' | ||||
|             username = password = '' | ||||
| 
 | ||||
|         # get the user's login info | ||||
|         choices = '1 2 3 4'.split() | ||||
|         while choice not in choices: | ||||
|             logger.info('''\ | ||||
| We need to know who you are, so please choose either: | ||||
|  1. use your existing login, | ||||
|  2. register as a new user, | ||||
|  3. have the server generate a new password for you (and email it to you), or | ||||
|  4. quit | ||||
| Your selection [default 1]: ''') | ||||
| 
 | ||||
|             choice = input() | ||||
|             if not choice: | ||||
|                 choice = '1' | ||||
|             elif choice not in choices: | ||||
|                 print('Please choose one of the four options!') | ||||
| 
 | ||||
|         if choice == '1': | ||||
|             # get the username and password | ||||
|             while not username: | ||||
|                 username = input('Username: ') | ||||
|             while not password: | ||||
|                 password = getpass.getpass('Password: ') | ||||
| 
 | ||||
|             # set up the authentication | ||||
|             auth = urllib.request.HTTPPasswordMgr() | ||||
|             host = urllib.parse.urlparse(self.repository)[1] | ||||
|             auth.add_password(self.realm, host, username, password) | ||||
|             # send the info to the server and report the result | ||||
|             code, result = self.post_to_server(self.build_post_data('submit'), | ||||
|                 auth) | ||||
|             logger.info('Server response (%s): %s', code, result) | ||||
| 
 | ||||
|             # possibly save the login | ||||
|             if code == 200: | ||||
|                 if self.has_config: | ||||
|                     # sharing the password in the distribution instance | ||||
|                     # so the upload command can reuse it | ||||
|                     self.distribution.password = password | ||||
|                 else: | ||||
|                     logger.info( | ||||
|                         'I can store your PyPI login so future submissions ' | ||||
|                         'will be faster.\n(the login will be stored in %s)', | ||||
|                         get_pypirc_path()) | ||||
|                     choice = 'X' | ||||
|                     while choice.lower() not in 'yn': | ||||
|                         choice = input('Save your login (y/N)?') | ||||
|                         if not choice: | ||||
|                             choice = 'n' | ||||
|                     if choice.lower() == 'y': | ||||
|                         generate_pypirc(username, password) | ||||
| 
 | ||||
|         elif choice == '2': | ||||
|             data = {':action': 'user'} | ||||
|             data['name'] = data['password'] = data['email'] = '' | ||||
|             data['confirm'] = None | ||||
|             while not data['name']: | ||||
|                 data['name'] = input('Username: ') | ||||
|             while data['password'] != data['confirm']: | ||||
|                 while not data['password']: | ||||
|                     data['password'] = getpass.getpass('Password: ') | ||||
|                 while not data['confirm']: | ||||
|                     data['confirm'] = getpass.getpass(' Confirm: ') | ||||
|                 if data['password'] != data['confirm']: | ||||
|                     data['password'] = '' | ||||
|                     data['confirm'] = None | ||||
|                     print("Password and confirm don't match!") | ||||
|             while not data['email']: | ||||
|                 data['email'] = input('   EMail: ') | ||||
|             code, result = self.post_to_server(data) | ||||
|             if code != 200: | ||||
|                 logger.info('server response (%s): %s', code, result) | ||||
|             else: | ||||
|                 logger.info('you will receive an email shortly; follow the ' | ||||
|                             'instructions in it to complete registration.') | ||||
|         elif choice == '3': | ||||
|             data = {':action': 'password_reset'} | ||||
|             data['email'] = '' | ||||
|             while not data['email']: | ||||
|                 data['email'] = input('Your email address: ') | ||||
|             code, result = self.post_to_server(data) | ||||
|             logger.info('server response (%s): %s', code, result) | ||||
| 
 | ||||
|     def build_post_data(self, action): | ||||
|         # figure the data to send - the metadata plus some additional | ||||
|         # information used by the package server | ||||
|         data = self.distribution.metadata.todict() | ||||
|         data[':action'] = action | ||||
|         return data | ||||
| 
 | ||||
|     # XXX to be refactored with upload.upload_file | ||||
|     def post_to_server(self, data, auth=None): | ||||
|         ''' Post a query to the server, and return a string response. | ||||
|         ''' | ||||
|         if 'name' in data: | ||||
|             logger.info('Registering %s to %s', data['name'], self.repository) | ||||
|         # Build up the MIME payload for the urllib2 POST data | ||||
|         boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' | ||||
|         sep_boundary = '\n--' + boundary | ||||
|         end_boundary = sep_boundary + '--' | ||||
|         body = io.StringIO() | ||||
|         for key, value in data.items(): | ||||
|             # handle multiple entries for the same name | ||||
|             if not isinstance(value, (tuple, list)): | ||||
|                 value = [value] | ||||
| 
 | ||||
|             for value in value: | ||||
|                 body.write(sep_boundary) | ||||
|                 body.write('\nContent-Disposition: form-data; name="%s"'%key) | ||||
|                 body.write("\n\n") | ||||
|                 body.write(value) | ||||
|                 if value and value[-1] == '\r': | ||||
|                     body.write('\n')  # write an extra newline (lurve Macs) | ||||
|         body.write(end_boundary) | ||||
|         body.write("\n") | ||||
|         body = body.getvalue() | ||||
| 
 | ||||
|         # build the Request | ||||
|         headers = { | ||||
|             'Content-type': 'multipart/form-data; boundary=%s; charset=utf-8'%boundary, | ||||
|             'Content-length': str(len(body)) | ||||
|         } | ||||
|         req = urllib.request.Request(self.repository, body, headers) | ||||
| 
 | ||||
|         # handle HTTP and include the Basic Auth handler | ||||
|         opener = urllib.request.build_opener( | ||||
|             urllib.request.HTTPBasicAuthHandler(password_mgr=auth) | ||||
|         ) | ||||
|         data = '' | ||||
|         try: | ||||
|             result = opener.open(req) | ||||
|         except urllib.error.HTTPError as e: | ||||
|             if self.show_response: | ||||
|                 data = e.fp.read() | ||||
|             result = e.code, e.msg | ||||
|         except urllib.error.URLError as e: | ||||
|             result = 500, str(e) | ||||
|         else: | ||||
|             if self.show_response: | ||||
|                 data = result.read() | ||||
|             result = 200, 'OK' | ||||
|         if self.show_response: | ||||
|             dashes = '-' * 75 | ||||
|             logger.info('%s%s%s', dashes, data, dashes) | ||||
| 
 | ||||
|         return result | ||||
							
								
								
									
										375
									
								
								Lib/packaging/command/sdist.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										375
									
								
								Lib/packaging/command/sdist.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,375 @@ | |||
| """Create a source distribution.""" | ||||
| 
 | ||||
| import os | ||||
| import sys | ||||
| import re | ||||
| from io import StringIO | ||||
| from glob import glob | ||||
| from shutil import get_archive_formats, rmtree | ||||
| 
 | ||||
| from packaging import logger | ||||
| from packaging.util import resolve_name | ||||
| from packaging.errors import (PackagingPlatformError, PackagingOptionError, | ||||
|                               PackagingModuleError, PackagingFileError) | ||||
| from packaging.command import get_command_names | ||||
| from packaging.command.cmd import Command | ||||
| from packaging.manifest import Manifest | ||||
| 
 | ||||
| 
 | ||||
| def show_formats(): | ||||
|     """Print all possible values for the 'formats' option (used by | ||||
|     the "--help-formats" command-line option). | ||||
|     """ | ||||
|     from packaging.fancy_getopt import FancyGetopt | ||||
|     formats = sorted(('formats=' + name, None, desc) | ||||
|                      for name, desc in get_archive_formats()) | ||||
|     FancyGetopt(formats).print_help( | ||||
|         "List of available source distribution formats:") | ||||
| 
 | ||||
| # a \ followed by some spaces + EOL | ||||
| _COLLAPSE_PATTERN = re.compile('\\\w\n', re.M) | ||||
| _COMMENTED_LINE = re.compile('^#.*\n$|^\w*\n$', re.M) | ||||
| 
 | ||||
| 
 | ||||
| class sdist(Command): | ||||
| 
 | ||||
|     description = "create a source distribution (tarball, zip file, etc.)" | ||||
| 
 | ||||
|     user_options = [ | ||||
|         ('manifest=', 'm', | ||||
|          "name of manifest file [default: MANIFEST]"), | ||||
|         ('use-defaults', None, | ||||
|          "include the default file set in the manifest " | ||||
|          "[default; disable with --no-defaults]"), | ||||
|         ('no-defaults', None, | ||||
|          "don't include the default file set"), | ||||
|         ('prune', None, | ||||
|          "specifically exclude files/directories that should not be " | ||||
|          "distributed (build tree, RCS/CVS dirs, etc.) " | ||||
|          "[default; disable with --no-prune]"), | ||||
|         ('no-prune', None, | ||||
|          "don't automatically exclude anything"), | ||||
|         ('manifest-only', 'o', | ||||
|          "just regenerate the manifest and then stop "), | ||||
|         ('formats=', None, | ||||
|          "formats for source distribution (comma-separated list)"), | ||||
|         ('keep-temp', 'k', | ||||
|          "keep the distribution tree around after creating " + | ||||
|          "archive file(s)"), | ||||
|         ('dist-dir=', 'd', | ||||
|          "directory to put the source distribution archive(s) in " | ||||
|          "[default: dist]"), | ||||
|         ('check-metadata', None, | ||||
|          "Ensure that all required elements of metadata " | ||||
|          "are supplied. Warn if any missing. [default]"), | ||||
|         ('owner=', 'u', | ||||
|          "Owner name used when creating a tar file [default: current user]"), | ||||
|         ('group=', 'g', | ||||
|          "Group name used when creating a tar file [default: current group]"), | ||||
|         ('manifest-builders=', None, | ||||
|          "manifest builders (comma-separated list)"), | ||||
|         ] | ||||
| 
 | ||||
|     boolean_options = ['use-defaults', 'prune', | ||||
|                        'manifest-only', 'keep-temp', 'check-metadata'] | ||||
| 
 | ||||
|     help_options = [ | ||||
|         ('help-formats', None, | ||||
|          "list available distribution formats", show_formats), | ||||
|         ] | ||||
| 
 | ||||
|     negative_opt = {'no-defaults': 'use-defaults', | ||||
|                     'no-prune': 'prune'} | ||||
| 
 | ||||
|     default_format = {'posix': 'gztar', | ||||
|                       'nt': 'zip'} | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.manifest = None | ||||
|         # 'use_defaults': if true, we will include the default file set | ||||
|         # in the manifest | ||||
|         self.use_defaults = True | ||||
|         self.prune = True | ||||
|         self.manifest_only = False | ||||
|         self.formats = None | ||||
|         self.keep_temp = False | ||||
|         self.dist_dir = None | ||||
| 
 | ||||
|         self.archive_files = None | ||||
|         self.metadata_check = True | ||||
|         self.owner = None | ||||
|         self.group = None | ||||
|         self.filelist = None | ||||
|         self.manifest_builders = None | ||||
| 
 | ||||
|     def _check_archive_formats(self, formats): | ||||
|         supported_formats = [name for name, desc in get_archive_formats()] | ||||
|         for format in formats: | ||||
|             if format not in supported_formats: | ||||
|                 return format | ||||
|         return None | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         if self.manifest is None: | ||||
|             self.manifest = "MANIFEST" | ||||
| 
 | ||||
|         self.ensure_string_list('formats') | ||||
|         if self.formats is None: | ||||
|             try: | ||||
|                 self.formats = [self.default_format[os.name]] | ||||
|             except KeyError: | ||||
|                 raise PackagingPlatformError("don't know how to create source " | ||||
|                        "distributions on platform %s" % os.name) | ||||
| 
 | ||||
|         bad_format = self._check_archive_formats(self.formats) | ||||
|         if bad_format: | ||||
|             raise PackagingOptionError("unknown archive format '%s'" \ | ||||
|                         % bad_format) | ||||
| 
 | ||||
|         if self.dist_dir is None: | ||||
|             self.dist_dir = "dist" | ||||
| 
 | ||||
|         if self.filelist is None: | ||||
|             self.filelist = Manifest() | ||||
| 
 | ||||
|         if self.manifest_builders is None: | ||||
|             self.manifest_builders = [] | ||||
|         else: | ||||
|             if isinstance(self.manifest_builders, str): | ||||
|                 self.manifest_builders = self.manifest_builders.split(',') | ||||
|             builders = [] | ||||
|             for builder in self.manifest_builders: | ||||
|                 builder = builder.strip() | ||||
|                 if builder == '': | ||||
|                     continue | ||||
|                 try: | ||||
|                     builder = resolve_name(builder) | ||||
|                 except ImportError as e: | ||||
|                     raise PackagingModuleError(e) | ||||
| 
 | ||||
|                 builders.append(builder) | ||||
| 
 | ||||
|             self.manifest_builders = builders | ||||
| 
 | ||||
|     def run(self): | ||||
|         # 'filelist' contains the list of files that will make up the | ||||
|         # manifest | ||||
|         self.filelist.clear() | ||||
| 
 | ||||
|         # Check the package metadata | ||||
|         if self.metadata_check: | ||||
|             self.run_command('check') | ||||
| 
 | ||||
|         # Do whatever it takes to get the list of files to process | ||||
|         # (process the manifest template, read an existing manifest, | ||||
|         # whatever).  File list is accumulated in 'self.filelist'. | ||||
|         self.get_file_list() | ||||
| 
 | ||||
|         # If user just wanted us to regenerate the manifest, stop now. | ||||
|         if self.manifest_only: | ||||
|             return | ||||
| 
 | ||||
|         # Otherwise, go ahead and create the source distribution tarball, | ||||
|         # or zipfile, or whatever. | ||||
|         self.make_distribution() | ||||
| 
 | ||||
|     def get_file_list(self): | ||||
|         """Figure out the list of files to include in the source | ||||
|         distribution, and put it in 'self.filelist'.  This might involve | ||||
|         reading the manifest template (and writing the manifest), or just | ||||
|         reading the manifest, or just using the default file set -- it all | ||||
|         depends on the user's options. | ||||
|         """ | ||||
|         template_exists = len(self.distribution.extra_files) > 0 | ||||
|         if not template_exists: | ||||
|             logger.warning('%s: using default file list', | ||||
|                            self.get_command_name()) | ||||
|         self.filelist.findall() | ||||
| 
 | ||||
|         if self.use_defaults: | ||||
|             self.add_defaults() | ||||
|         if template_exists: | ||||
|             template = '\n'.join(self.distribution.extra_files) | ||||
|             self.filelist.read_template(StringIO(template)) | ||||
| 
 | ||||
|         # call manifest builders, if any. | ||||
|         for builder in self.manifest_builders: | ||||
|             builder(self.distribution, self.filelist) | ||||
| 
 | ||||
|         if self.prune: | ||||
|             self.prune_file_list() | ||||
| 
 | ||||
|         self.filelist.write(self.manifest) | ||||
| 
 | ||||
|     def add_defaults(self): | ||||
|         """Add all the default files to self.filelist: | ||||
|           - README or README.txt | ||||
|           - test/test*.py | ||||
|           - all pure Python modules mentioned in setup script | ||||
|           - all files pointed by package_data (build_py) | ||||
|           - all files defined in data_files. | ||||
|           - all files defined as scripts. | ||||
|           - all C sources listed as part of extensions or C libraries | ||||
|             in the setup script (doesn't catch C headers!) | ||||
|         Warns if (README or README.txt) or setup.py are missing; everything | ||||
|         else is optional. | ||||
|         """ | ||||
|         standards = [('README', 'README.txt')] | ||||
|         for fn in standards: | ||||
|             if isinstance(fn, tuple): | ||||
|                 alts = fn | ||||
|                 got_it = False | ||||
|                 for fn in alts: | ||||
|                     if os.path.exists(fn): | ||||
|                         got_it = True | ||||
|                         self.filelist.append(fn) | ||||
|                         break | ||||
| 
 | ||||
|                 if not got_it: | ||||
|                     logger.warning( | ||||
|                         '%s: standard file not found: should have one of %s', | ||||
|                         self.get_command_name(), ', '.join(alts)) | ||||
|             else: | ||||
|                 if os.path.exists(fn): | ||||
|                     self.filelist.append(fn) | ||||
|                 else: | ||||
|                     logger.warning('%s: standard file %r not found', | ||||
|                                    self.get_command_name(), fn) | ||||
| 
 | ||||
|         optional = ['test/test*.py', 'setup.cfg'] | ||||
|         for pattern in optional: | ||||
|             files = [f for f in glob(pattern) if os.path.isfile(f)] | ||||
|             if files: | ||||
|                 self.filelist.extend(files) | ||||
| 
 | ||||
|         for cmd_name in get_command_names(): | ||||
|             try: | ||||
|                 cmd_obj = self.get_finalized_command(cmd_name) | ||||
|             except PackagingOptionError: | ||||
|                 pass | ||||
|             else: | ||||
|                 self.filelist.extend(cmd_obj.get_source_files()) | ||||
| 
 | ||||
|     def prune_file_list(self): | ||||
|         """Prune off branches that might slip into the file list as created | ||||
|         by 'read_template()', but really don't belong there: | ||||
|           * the build tree (typically "build") | ||||
|           * the release tree itself (only an issue if we ran "sdist" | ||||
|             previously with --keep-temp, or it aborted) | ||||
|           * any RCS, CVS, .svn, .hg, .git, .bzr, _darcs directories | ||||
|         """ | ||||
|         build = self.get_finalized_command('build') | ||||
|         base_dir = self.distribution.get_fullname() | ||||
| 
 | ||||
|         self.filelist.exclude_pattern(None, prefix=build.build_base) | ||||
|         self.filelist.exclude_pattern(None, prefix=base_dir) | ||||
| 
 | ||||
|         # pruning out vcs directories | ||||
|         # both separators are used under win32 | ||||
|         if sys.platform == 'win32': | ||||
|             seps = r'/|\\' | ||||
|         else: | ||||
|             seps = '/' | ||||
| 
 | ||||
|         vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr', | ||||
|                     '_darcs'] | ||||
|         vcs_ptrn = r'(^|%s)(%s)(%s).*' % (seps, '|'.join(vcs_dirs), seps) | ||||
|         self.filelist.exclude_pattern(vcs_ptrn, is_regex=True) | ||||
| 
 | ||||
|     def make_release_tree(self, base_dir, files): | ||||
|         """Create the directory tree that will become the source | ||||
|         distribution archive.  All directories implied by the filenames in | ||||
|         'files' are created under 'base_dir', and then we hard link or copy | ||||
|         (if hard linking is unavailable) those files into place. | ||||
|         Essentially, this duplicates the developer's source tree, but in a | ||||
|         directory named after the distribution, containing only the files | ||||
|         to be distributed. | ||||
|         """ | ||||
|         # Create all the directories under 'base_dir' necessary to | ||||
|         # put 'files' there; the 'mkpath()' is just so we don't die | ||||
|         # if the manifest happens to be empty. | ||||
|         self.mkpath(base_dir) | ||||
|         self.create_tree(base_dir, files, dry_run=self.dry_run) | ||||
| 
 | ||||
|         # And walk over the list of files, either making a hard link (if | ||||
|         # os.link exists) to each one that doesn't already exist in its | ||||
|         # corresponding location under 'base_dir', or copying each file | ||||
|         # that's out-of-date in 'base_dir'.  (Usually, all files will be | ||||
|         # out-of-date, because by default we blow away 'base_dir' when | ||||
|         # we're done making the distribution archives.) | ||||
| 
 | ||||
|         if hasattr(os, 'link'):        # can make hard links on this system | ||||
|             link = 'hard' | ||||
|             msg = "making hard links in %s..." % base_dir | ||||
|         else:                           # nope, have to copy | ||||
|             link = None | ||||
|             msg = "copying files to %s..." % base_dir | ||||
| 
 | ||||
|         if not files: | ||||
|             logger.warning("no files to distribute -- empty manifest?") | ||||
|         else: | ||||
|             logger.info(msg) | ||||
| 
 | ||||
|         for file in self.distribution.metadata.requires_files: | ||||
|             if file not in files: | ||||
|                 msg = "'%s' must be included explicitly in 'extra_files'" \ | ||||
|                         % file | ||||
|                 raise PackagingFileError(msg) | ||||
| 
 | ||||
|         for file in files: | ||||
|             if not os.path.isfile(file): | ||||
|                 logger.warning("'%s' not a regular file -- skipping", file) | ||||
|             else: | ||||
|                 dest = os.path.join(base_dir, file) | ||||
|                 self.copy_file(file, dest, link=link) | ||||
| 
 | ||||
|         self.distribution.metadata.write(os.path.join(base_dir, 'PKG-INFO')) | ||||
| 
 | ||||
|     def make_distribution(self): | ||||
|         """Create the source distribution(s).  First, we create the release | ||||
|         tree with 'make_release_tree()'; then, we create all required | ||||
|         archive files (according to 'self.formats') from the release tree. | ||||
|         Finally, we clean up by blowing away the release tree (unless | ||||
|         'self.keep_temp' is true).  The list of archive files created is | ||||
|         stored so it can be retrieved later by 'get_archive_files()'. | ||||
|         """ | ||||
|         # Don't warn about missing metadata here -- should be (and is!) | ||||
|         # done elsewhere. | ||||
|         base_dir = self.distribution.get_fullname() | ||||
|         base_name = os.path.join(self.dist_dir, base_dir) | ||||
| 
 | ||||
|         self.make_release_tree(base_dir, self.filelist.files) | ||||
|         archive_files = []              # remember names of files we create | ||||
|         # tar archive must be created last to avoid overwrite and remove | ||||
|         if 'tar' in self.formats: | ||||
|             self.formats.append(self.formats.pop(self.formats.index('tar'))) | ||||
| 
 | ||||
|         for fmt in self.formats: | ||||
|             file = self.make_archive(base_name, fmt, base_dir=base_dir, | ||||
|                                      owner=self.owner, group=self.group) | ||||
|             archive_files.append(file) | ||||
|             self.distribution.dist_files.append(('sdist', '', file)) | ||||
| 
 | ||||
|         self.archive_files = archive_files | ||||
| 
 | ||||
|         if not self.keep_temp: | ||||
|             if self.dry_run: | ||||
|                 logger.info('removing %s', base_dir) | ||||
|             else: | ||||
|                 rmtree(base_dir) | ||||
| 
 | ||||
|     def get_archive_files(self): | ||||
|         """Return the list of archive files created when the command | ||||
|         was run, or None if the command hasn't run yet. | ||||
|         """ | ||||
|         return self.archive_files | ||||
| 
 | ||||
|     def create_tree(self, base_dir, files, mode=0o777, verbose=1, | ||||
|                     dry_run=False): | ||||
|         need_dir = set() | ||||
|         for file in files: | ||||
|             need_dir.add(os.path.join(base_dir, os.path.dirname(file))) | ||||
| 
 | ||||
|         # Now create them | ||||
|         for dir in sorted(need_dir): | ||||
|             self.mkpath(dir, mode, verbose=verbose, dry_run=dry_run) | ||||
							
								
								
									
										81
									
								
								Lib/packaging/command/test.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								Lib/packaging/command/test.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,81 @@ | |||
| """Run the project's test suite.""" | ||||
| 
 | ||||
| import os | ||||
| import sys | ||||
| import logging | ||||
| import unittest | ||||
| 
 | ||||
| from packaging import logger | ||||
| from packaging.command.cmd import Command | ||||
| from packaging.database import get_distribution | ||||
| from packaging.errors import PackagingOptionError | ||||
| from packaging.util import resolve_name | ||||
| 
 | ||||
| 
 | ||||
| class test(Command): | ||||
| 
 | ||||
|     description = "run the project's test suite" | ||||
| 
 | ||||
|     user_options = [ | ||||
|         ('suite=', 's', | ||||
|          "test suite to run (for example: 'some_module.test_suite')"), | ||||
|         ('runner=', None, | ||||
|          "test runner to be called."), | ||||
|         ('tests-require=', None, | ||||
|          "list of distributions required to run the test suite."), | ||||
|     ] | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.suite = None | ||||
|         self.runner = None | ||||
|         self.tests_require = [] | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         self.build_lib = self.get_finalized_command("build").build_lib | ||||
|         for requirement in self.tests_require: | ||||
|             if get_distribution(requirement) is None: | ||||
|                 logger.warning("test dependency %s is not installed, " | ||||
|                                "tests may fail", requirement) | ||||
|         if (not self.suite and not self.runner and | ||||
|             self.get_ut_with_discovery() is None): | ||||
|             raise PackagingOptionError( | ||||
|                 "no test discovery available, please give a 'suite' or " | ||||
|                 "'runner' option or install unittest2") | ||||
| 
 | ||||
|     def get_ut_with_discovery(self): | ||||
|         if hasattr(unittest.TestLoader, "discover"): | ||||
|             return unittest | ||||
|         else: | ||||
|             try: | ||||
|                 import unittest2 | ||||
|                 return unittest2 | ||||
|             except ImportError: | ||||
|                 return None | ||||
| 
 | ||||
|     def run(self): | ||||
|         prev_syspath = sys.path[:] | ||||
|         try: | ||||
|             # build release | ||||
|             build = self.get_reinitialized_command('build') | ||||
|             self.run_command('build') | ||||
|             sys.path.insert(0, build.build_lib) | ||||
| 
 | ||||
|             # Temporary kludge until we remove the verbose arguments and use | ||||
|             # logging everywhere | ||||
|             logger = logging.getLogger('packaging') | ||||
|             verbose = logger.getEffectiveLevel() >= logging.DEBUG | ||||
|             verbosity = verbose + 1 | ||||
| 
 | ||||
|             # run the tests | ||||
|             if self.runner: | ||||
|                 resolve_name(self.runner)() | ||||
|             elif self.suite: | ||||
|                 runner = unittest.TextTestRunner(verbosity=verbosity) | ||||
|                 runner.run(resolve_name(self.suite)()) | ||||
|             elif self.get_ut_with_discovery(): | ||||
|                 ut = self.get_ut_with_discovery() | ||||
|                 test_suite = ut.TestLoader().discover(os.curdir) | ||||
|                 runner = ut.TextTestRunner(verbosity=verbosity) | ||||
|                 runner.run(test_suite) | ||||
|         finally: | ||||
|             sys.path[:] = prev_syspath | ||||
							
								
								
									
										201
									
								
								Lib/packaging/command/upload.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								Lib/packaging/command/upload.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,201 @@ | |||
| """Upload a distribution to a project index.""" | ||||
| 
 | ||||
| import os | ||||
| import socket | ||||
| import logging | ||||
| import platform | ||||
| import urllib.parse | ||||
| from io import BytesIO | ||||
| from base64 import standard_b64encode | ||||
| from hashlib import md5 | ||||
| from urllib.error import HTTPError | ||||
| from urllib.request import urlopen, Request | ||||
| 
 | ||||
| from packaging import logger | ||||
| from packaging.errors import PackagingOptionError | ||||
| from packaging.util import (spawn, read_pypirc, DEFAULT_REPOSITORY, | ||||
|                             DEFAULT_REALM) | ||||
| from packaging.command.cmd import Command | ||||
| 
 | ||||
| 
 | ||||
| class upload(Command): | ||||
| 
 | ||||
|     description = "upload distribution to PyPI" | ||||
| 
 | ||||
|     user_options = [ | ||||
|         ('repository=', 'r', | ||||
|          "repository URL [default: %s]" % DEFAULT_REPOSITORY), | ||||
|         ('show-response', None, | ||||
|          "display full response text from server"), | ||||
|         ('sign', 's', | ||||
|          "sign files to upload using gpg"), | ||||
|         ('identity=', 'i', | ||||
|          "GPG identity used to sign files"), | ||||
|         ('upload-docs', None, | ||||
|          "upload documentation too"), | ||||
|         ] | ||||
| 
 | ||||
|     boolean_options = ['show-response', 'sign'] | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.repository = None | ||||
|         self.realm = None | ||||
|         self.show_response = False | ||||
|         self.username = '' | ||||
|         self.password = '' | ||||
|         self.show_response = False | ||||
|         self.sign = False | ||||
|         self.identity = None | ||||
|         self.upload_docs = False | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         if self.repository is None: | ||||
|             self.repository = DEFAULT_REPOSITORY | ||||
|         if self.realm is None: | ||||
|             self.realm = DEFAULT_REALM | ||||
|         if self.identity and not self.sign: | ||||
|             raise PackagingOptionError( | ||||
|                 "Must use --sign for --identity to have meaning") | ||||
|         config = read_pypirc(self.repository, self.realm) | ||||
|         if config != {}: | ||||
|             self.username = config['username'] | ||||
|             self.password = config['password'] | ||||
|             self.repository = config['repository'] | ||||
|             self.realm = config['realm'] | ||||
| 
 | ||||
|         # getting the password from the distribution | ||||
|         # if previously set by the register command | ||||
|         if not self.password and self.distribution.password: | ||||
|             self.password = self.distribution.password | ||||
| 
 | ||||
|     def run(self): | ||||
|         if not self.distribution.dist_files: | ||||
|             raise PackagingOptionError( | ||||
|                 "No dist file created in earlier command") | ||||
|         for command, pyversion, filename in self.distribution.dist_files: | ||||
|             self.upload_file(command, pyversion, filename) | ||||
|         if self.upload_docs: | ||||
|             upload_docs = self.get_finalized_command("upload_docs") | ||||
|             upload_docs.repository = self.repository | ||||
|             upload_docs.username = self.username | ||||
|             upload_docs.password = self.password | ||||
|             upload_docs.run() | ||||
| 
 | ||||
|     # XXX to be refactored with register.post_to_server | ||||
|     def upload_file(self, command, pyversion, filename): | ||||
|         # Makes sure the repository URL is compliant | ||||
|         scheme, netloc, url, params, query, fragments = \ | ||||
|             urllib.parse.urlparse(self.repository) | ||||
|         if params or query or fragments: | ||||
|             raise AssertionError("Incompatible url %s" % self.repository) | ||||
| 
 | ||||
|         if scheme not in ('http', 'https'): | ||||
|             raise AssertionError("unsupported scheme " + scheme) | ||||
| 
 | ||||
|         # Sign if requested | ||||
|         if self.sign: | ||||
|             gpg_args = ["gpg", "--detach-sign", "-a", filename] | ||||
|             if self.identity: | ||||
|                 gpg_args[2:2] = ["--local-user", self.identity] | ||||
|             spawn(gpg_args, | ||||
|                   dry_run=self.dry_run) | ||||
| 
 | ||||
|         # Fill in the data - send all the metadata in case we need to | ||||
|         # register a new release | ||||
|         with open(filename, 'rb') as f: | ||||
|             content = f.read() | ||||
| 
 | ||||
|         data = self.distribution.metadata.todict() | ||||
| 
 | ||||
|         # extra upload infos | ||||
|         data[':action'] = 'file_upload' | ||||
|         data['protcol_version'] = '1' | ||||
|         data['content'] = (os.path.basename(filename), content) | ||||
|         data['filetype'] = command | ||||
|         data['pyversion'] = pyversion | ||||
|         data['md5_digest'] = md5(content).hexdigest() | ||||
| 
 | ||||
|         if command == 'bdist_dumb': | ||||
|             data['comment'] = 'built for %s' % platform.platform(terse=True) | ||||
| 
 | ||||
|         if self.sign: | ||||
|             with open(filename + '.asc') as fp: | ||||
|                 sig = fp.read() | ||||
|             data['gpg_signature'] = [ | ||||
|                 (os.path.basename(filename) + ".asc", sig)] | ||||
| 
 | ||||
|         # set up the authentication | ||||
|         # The exact encoding of the authentication string is debated. | ||||
|         # Anyway PyPI only accepts ascii for both username or password. | ||||
|         user_pass = (self.username + ":" + self.password).encode('ascii') | ||||
|         auth = b"Basic " + standard_b64encode(user_pass) | ||||
| 
 | ||||
|         # Build up the MIME payload for the POST data | ||||
|         boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' | ||||
|         sep_boundary = b'\n--' + boundary | ||||
|         end_boundary = sep_boundary + b'--' | ||||
|         body = BytesIO() | ||||
| 
 | ||||
|         file_fields = ('content', 'gpg_signature') | ||||
| 
 | ||||
|         for key, value in data.items(): | ||||
|             # handle multiple entries for the same name | ||||
|             if not isinstance(value, tuple): | ||||
|                 value = [value] | ||||
| 
 | ||||
|             content_dispo = '\nContent-Disposition: form-data; name="%s"' % key | ||||
| 
 | ||||
|             if key in file_fields: | ||||
|                 filename_, content = value | ||||
|                 filename_ = ';filename="%s"' % filename_ | ||||
|                 body.write(sep_boundary) | ||||
|                 body.write(content_dispo.encode('utf-8')) | ||||
|                 body.write(filename_.encode('utf-8')) | ||||
|                 body.write(b"\n\n") | ||||
|                 body.write(content) | ||||
|             else: | ||||
|                 for value in value: | ||||
|                     value = str(value).encode('utf-8') | ||||
|                     body.write(sep_boundary) | ||||
|                     body.write(content_dispo.encode('utf-8')) | ||||
|                     body.write(b"\n\n") | ||||
|                     body.write(value) | ||||
|                     if value and value.endswith(b'\r'): | ||||
|                         # write an extra newline (lurve Macs) | ||||
|                         body.write(b'\n') | ||||
| 
 | ||||
|         body.write(end_boundary) | ||||
|         body.write(b"\n") | ||||
|         body = body.getvalue() | ||||
| 
 | ||||
|         logger.info("Submitting %s to %s", filename, self.repository) | ||||
| 
 | ||||
|         # build the Request | ||||
|         headers = {'Content-type': | ||||
|                         'multipart/form-data; boundary=%s' % | ||||
|                         boundary.decode('ascii'), | ||||
|                    'Content-length': str(len(body)), | ||||
|                    'Authorization': auth} | ||||
| 
 | ||||
|         request = Request(self.repository, data=body, | ||||
|                           headers=headers) | ||||
|         # send the data | ||||
|         try: | ||||
|             result = urlopen(request) | ||||
|             status = result.code | ||||
|             reason = result.msg | ||||
|         except socket.error as e: | ||||
|             logger.error(e) | ||||
|             return | ||||
|         except HTTPError as e: | ||||
|             status = e.code | ||||
|             reason = e.msg | ||||
| 
 | ||||
|         if status == 200: | ||||
|             logger.info('Server response (%s): %s', status, reason) | ||||
|         else: | ||||
|             logger.error('Upload failed (%s): %s', status, reason) | ||||
| 
 | ||||
|         if self.show_response and logger.isEnabledFor(logging.INFO): | ||||
|             sep = '-' * 75 | ||||
|             logger.info('%s\n%s\n%s', sep, result.read().decode(), sep) | ||||
							
								
								
									
										173
									
								
								Lib/packaging/command/upload_docs.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								Lib/packaging/command/upload_docs.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,173 @@ | |||
| """Upload HTML documentation to a project index.""" | ||||
| 
 | ||||
| import os | ||||
| import base64 | ||||
| import socket | ||||
| import zipfile | ||||
| import logging | ||||
| import http.client | ||||
| import urllib.parse | ||||
| from io import BytesIO | ||||
| 
 | ||||
| from packaging import logger | ||||
| from packaging.util import read_pypirc, DEFAULT_REPOSITORY, DEFAULT_REALM | ||||
| from packaging.errors import PackagingFileError | ||||
| from packaging.command.cmd import Command | ||||
| 
 | ||||
| 
 | ||||
| def zip_dir(directory): | ||||
|     """Compresses recursively contents of directory into a BytesIO object""" | ||||
|     destination = BytesIO() | ||||
|     zip_file = zipfile.ZipFile(destination, "w") | ||||
|     for root, dirs, files in os.walk(directory): | ||||
|         for name in files: | ||||
|             full = os.path.join(root, name) | ||||
|             relative = root[len(directory):].lstrip(os.path.sep) | ||||
|             dest = os.path.join(relative, name) | ||||
|             zip_file.write(full, dest) | ||||
|     zip_file.close() | ||||
|     return destination | ||||
| 
 | ||||
| 
 | ||||
| # grabbed from | ||||
| #    http://code.activestate.com/recipes/ | ||||
| #    146306-http-client-to-post-using-multipartform-data/ | ||||
| # TODO factor this out for use by install and command/upload | ||||
| 
 | ||||
| def encode_multipart(fields, files, boundary=None): | ||||
|     """ | ||||
|     *fields* is a sequence of (name: str, value: str) elements for regular | ||||
|     form fields, *files* is a sequence of (name: str, filename: str, value: | ||||
|     bytes) elements for data to be uploaded as files. | ||||
| 
 | ||||
|     Returns (content_type: bytes, body: bytes) ready for http.client.HTTP. | ||||
|     """ | ||||
|     if boundary is None: | ||||
|         boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' | ||||
|     elif not isinstance(boundary, bytes): | ||||
|         raise TypeError('boundary is not bytes but %r' % type(boundary)) | ||||
| 
 | ||||
|     l = [] | ||||
|     for key, value in fields: | ||||
|         l.extend(( | ||||
|             b'--' + boundary, | ||||
|             ('Content-Disposition: form-data; name="%s"' % | ||||
|              key).encode('utf-8'), | ||||
|             b'', | ||||
|             value.encode('utf-8'))) | ||||
| 
 | ||||
|     for key, filename, value in files: | ||||
|         l.extend(( | ||||
|             b'--' + boundary, | ||||
|             ('Content-Disposition: form-data; name="%s"; filename="%s"' % | ||||
|              (key, filename)).encode('utf-8'), | ||||
|             b'', | ||||
|             value)) | ||||
|     l.append(b'--' + boundary + b'--') | ||||
|     l.append(b'') | ||||
| 
 | ||||
|     body = b'\r\n'.join(l) | ||||
| 
 | ||||
|     content_type = b'multipart/form-data; boundary=' + boundary | ||||
|     return content_type, body | ||||
| 
 | ||||
| 
 | ||||
| class upload_docs(Command): | ||||
| 
 | ||||
|     description = "upload HTML documentation to PyPI" | ||||
| 
 | ||||
|     user_options = [ | ||||
|         ('repository=', 'r', | ||||
|          "repository URL [default: %s]" % DEFAULT_REPOSITORY), | ||||
|         ('show-response', None, | ||||
|          "display full response text from server"), | ||||
|         ('upload-dir=', None, | ||||
|          "directory to upload"), | ||||
|         ] | ||||
| 
 | ||||
|     def initialize_options(self): | ||||
|         self.repository = None | ||||
|         self.realm = None | ||||
|         self.show_response = False | ||||
|         self.upload_dir = None | ||||
|         self.username = '' | ||||
|         self.password = '' | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         if self.repository is None: | ||||
|             self.repository = DEFAULT_REPOSITORY | ||||
|         if self.realm is None: | ||||
|             self.realm = DEFAULT_REALM | ||||
|         if self.upload_dir is None: | ||||
|             build = self.get_finalized_command('build') | ||||
|             self.upload_dir = os.path.join(build.build_base, "docs") | ||||
|             if not os.path.isdir(self.upload_dir): | ||||
|                 self.upload_dir = os.path.join(build.build_base, "doc") | ||||
|         logger.info('Using upload directory %s', self.upload_dir) | ||||
|         self.verify_upload_dir(self.upload_dir) | ||||
|         config = read_pypirc(self.repository, self.realm) | ||||
|         if config != {}: | ||||
|             self.username = config['username'] | ||||
|             self.password = config['password'] | ||||
|             self.repository = config['repository'] | ||||
|             self.realm = config['realm'] | ||||
| 
 | ||||
|     def verify_upload_dir(self, upload_dir): | ||||
|         self.ensure_dirname('upload_dir') | ||||
|         index_location = os.path.join(upload_dir, "index.html") | ||||
|         if not os.path.exists(index_location): | ||||
|             mesg = "No 'index.html found in docs directory (%s)" | ||||
|             raise PackagingFileError(mesg % upload_dir) | ||||
| 
 | ||||
|     def run(self): | ||||
|         name = self.distribution.metadata['Name'] | ||||
|         version = self.distribution.metadata['Version'] | ||||
|         zip_file = zip_dir(self.upload_dir) | ||||
| 
 | ||||
|         fields = [(':action', 'doc_upload'), | ||||
|                   ('name', name), ('version', version)] | ||||
|         files = [('content', name, zip_file.getvalue())] | ||||
|         content_type, body = encode_multipart(fields, files) | ||||
| 
 | ||||
|         credentials = self.username + ':' + self.password | ||||
|         auth = b"Basic " + base64.encodebytes(credentials.encode()).strip() | ||||
| 
 | ||||
|         logger.info("Submitting documentation to %s", self.repository) | ||||
| 
 | ||||
|         scheme, netloc, url, params, query, fragments = urllib.parse.urlparse( | ||||
|             self.repository) | ||||
|         if scheme == "http": | ||||
|             conn = http.client.HTTPConnection(netloc) | ||||
|         elif scheme == "https": | ||||
|             conn = http.client.HTTPSConnection(netloc) | ||||
|         else: | ||||
|             raise AssertionError("unsupported scheme %r" % scheme) | ||||
| 
 | ||||
|         try: | ||||
|             conn.connect() | ||||
|             conn.putrequest("POST", url) | ||||
|             conn.putheader('Content-type', content_type) | ||||
|             conn.putheader('Content-length', str(len(body))) | ||||
|             conn.putheader('Authorization', auth) | ||||
|             conn.endheaders() | ||||
|             conn.send(body) | ||||
| 
 | ||||
|         except socket.error as e: | ||||
|             logger.error(e) | ||||
|             return | ||||
| 
 | ||||
|         r = conn.getresponse() | ||||
| 
 | ||||
|         if r.status == 200: | ||||
|             logger.info('Server response (%s): %s', r.status, r.reason) | ||||
|         elif r.status == 301: | ||||
|             location = r.getheader('Location') | ||||
|             if location is None: | ||||
|                 location = 'http://packages.python.org/%s/' % name | ||||
|             logger.info('Upload successful. Visit %s', location) | ||||
|         else: | ||||
|             logger.error('Upload failed (%s): %s', r.status, r.reason) | ||||
| 
 | ||||
|         if self.show_response and logger.isEnabledFor(logging.INFO): | ||||
|             sep = '-' * 75 | ||||
|             logger.info('%s\n%s\n%s', sep, r.read().decode('utf-8'), sep) | ||||
							
								
								
									
										
											BIN
										
									
								
								Lib/packaging/command/wininst-10.0-amd64.exe
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Lib/packaging/command/wininst-10.0-amd64.exe
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Lib/packaging/command/wininst-10.0.exe
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Lib/packaging/command/wininst-10.0.exe
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Lib/packaging/command/wininst-6.0.exe
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Lib/packaging/command/wininst-6.0.exe
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Lib/packaging/command/wininst-7.1.exe
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Lib/packaging/command/wininst-7.1.exe
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Lib/packaging/command/wininst-8.0.exe
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Lib/packaging/command/wininst-8.0.exe
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Lib/packaging/command/wininst-9.0-amd64.exe
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Lib/packaging/command/wininst-9.0-amd64.exe
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Lib/packaging/command/wininst-9.0.exe
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Lib/packaging/command/wininst-9.0.exe
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										57
									
								
								Lib/packaging/compat.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								Lib/packaging/compat.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,57 @@ | |||
| """Compatibility helpers. | ||||
| 
 | ||||
| This module provides classes, variables and imports which are used to | ||||
| support packaging across Python 2.x and 3.x. | ||||
| """ | ||||
| 
 | ||||
| from packaging import logger | ||||
| 
 | ||||
| 
 | ||||
| # XXX Having two classes with the same name is not a good thing. | ||||
| # XXX 2to3-related code should move from util to this module | ||||
| 
 | ||||
| # TODO Move common code here: PY3 (bool indicating if we're on 3.x), any, etc. | ||||
| 
 | ||||
| try: | ||||
|     from packaging.util import Mixin2to3 as _Mixin2to3 | ||||
|     _CONVERT = True | ||||
|     _KLASS = _Mixin2to3 | ||||
| except ImportError: | ||||
|     _CONVERT = False | ||||
|     _KLASS = object | ||||
| 
 | ||||
| __all__ = ['Mixin2to3'] | ||||
| 
 | ||||
| 
 | ||||
| class Mixin2to3(_KLASS): | ||||
|     """ The base class which can be used for refactoring. When run under | ||||
|     Python 3.0, the run_2to3 method provided by Mixin2to3 is overridden. | ||||
|     When run on Python 2.x, it merely creates a class which overrides run_2to3, | ||||
|     yet does nothing in particular with it. | ||||
|     """ | ||||
|     if _CONVERT: | ||||
| 
 | ||||
|         def _run_2to3(self, files, doctests=[], fixers=[]): | ||||
|             """ Takes a list of files and doctests, and performs conversion | ||||
|             on those. | ||||
|               - First, the files which contain the code(`files`) are converted. | ||||
|               - Second, the doctests in `files` are converted. | ||||
|               - Thirdly, the doctests in `doctests` are converted. | ||||
|             """ | ||||
|             if fixers: | ||||
|                 self.fixer_names = fixers | ||||
| 
 | ||||
|             logger.info('converting Python code') | ||||
|             _KLASS.run_2to3(self, files) | ||||
| 
 | ||||
|             logger.info('converting doctests in Python files') | ||||
|             _KLASS.run_2to3(self, files, doctests_only=True) | ||||
| 
 | ||||
|             if doctests != []: | ||||
|                 logger.info('converting doctest in text files') | ||||
|                 _KLASS.run_2to3(self, doctests, doctests_only=True) | ||||
|     else: | ||||
|         # If run on Python 2.x, there is nothing to do. | ||||
| 
 | ||||
|         def _run_2to3(self, files, doctests=[], fixers=[]): | ||||
|             pass | ||||
							
								
								
									
										282
									
								
								Lib/packaging/compiler/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								Lib/packaging/compiler/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,282 @@ | |||
| """Compiler abstraction model used by packaging. | ||||
| 
 | ||||
| An abstract base class is defined in the ccompiler submodule, and | ||||
| concrete implementations suitable for various platforms are defined in | ||||
| the other submodules.  The extension module is also placed in this | ||||
| package. | ||||
| 
 | ||||
| In general, code should not instantiate compiler classes directly but | ||||
| use the new_compiler and customize_compiler functions provided in this | ||||
| module. | ||||
| 
 | ||||
| The compiler system has a registration API: get_default_compiler, | ||||
| set_compiler, show_compilers. | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| import sys | ||||
| import re | ||||
| 
 | ||||
| import sysconfig | ||||
| from packaging.util import resolve_name | ||||
| from packaging.errors import PackagingPlatformError | ||||
| 
 | ||||
| 
 | ||||
| def customize_compiler(compiler): | ||||
|     """Do any platform-specific customization of a CCompiler instance. | ||||
| 
 | ||||
|     Mainly needed on Unix, so we can plug in the information that | ||||
|     varies across Unices and is stored in Python's Makefile. | ||||
|     """ | ||||
|     if compiler.name == "unix": | ||||
|         cc, cxx, opt, cflags, ccshared, ldshared, so_ext, ar, ar_flags = ( | ||||
|             sysconfig.get_config_vars('CC', 'CXX', 'OPT', 'CFLAGS', | ||||
|                                       'CCSHARED', 'LDSHARED', 'SO', 'AR', | ||||
|                                       'ARFLAGS')) | ||||
| 
 | ||||
|         if 'CC' in os.environ: | ||||
|             cc = os.environ['CC'] | ||||
|         if 'CXX' in os.environ: | ||||
|             cxx = os.environ['CXX'] | ||||
|         if 'LDSHARED' in os.environ: | ||||
|             ldshared = os.environ['LDSHARED'] | ||||
|         if 'CPP' in os.environ: | ||||
|             cpp = os.environ['CPP'] | ||||
|         else: | ||||
|             cpp = cc + " -E"           # not always | ||||
|         if 'LDFLAGS' in os.environ: | ||||
|             ldshared = ldshared + ' ' + os.environ['LDFLAGS'] | ||||
|         if 'CFLAGS' in os.environ: | ||||
|             cflags = opt + ' ' + os.environ['CFLAGS'] | ||||
|             ldshared = ldshared + ' ' + os.environ['CFLAGS'] | ||||
|         if 'CPPFLAGS' in os.environ: | ||||
|             cpp = cpp + ' ' + os.environ['CPPFLAGS'] | ||||
|             cflags = cflags + ' ' + os.environ['CPPFLAGS'] | ||||
|             ldshared = ldshared + ' ' + os.environ['CPPFLAGS'] | ||||
|         if 'AR' in os.environ: | ||||
|             ar = os.environ['AR'] | ||||
|         if 'ARFLAGS' in os.environ: | ||||
|             archiver = ar + ' ' + os.environ['ARFLAGS'] | ||||
|         else: | ||||
|             if ar_flags is not None: | ||||
|                 archiver = ar + ' ' + ar_flags | ||||
|             else: | ||||
|                 # see if its the proper default value | ||||
|                 # mmm I don't want to backport the makefile | ||||
|                 archiver = ar + ' rc' | ||||
| 
 | ||||
|         cc_cmd = cc + ' ' + cflags | ||||
|         compiler.set_executables( | ||||
|             preprocessor=cpp, | ||||
|             compiler=cc_cmd, | ||||
|             compiler_so=cc_cmd + ' ' + ccshared, | ||||
|             compiler_cxx=cxx, | ||||
|             linker_so=ldshared, | ||||
|             linker_exe=cc, | ||||
|             archiver=archiver) | ||||
| 
 | ||||
|         compiler.shared_lib_extension = so_ext | ||||
| 
 | ||||
| 
 | ||||
| # Map a sys.platform/os.name ('posix', 'nt') to the default compiler | ||||
| # type for that platform. Keys are interpreted as re match | ||||
| # patterns. Order is important; platform mappings are preferred over | ||||
| # OS names. | ||||
| _default_compilers = ( | ||||
| 
 | ||||
|     # Platform string mappings | ||||
| 
 | ||||
|     # on a cygwin built python we can use gcc like an ordinary UNIXish | ||||
|     # compiler | ||||
|     ('cygwin.*', 'unix'), | ||||
|     ('os2emx', 'emx'), | ||||
| 
 | ||||
|     # OS name mappings | ||||
|     ('posix', 'unix'), | ||||
|     ('nt', 'msvc'), | ||||
| 
 | ||||
|     ) | ||||
| 
 | ||||
| def get_default_compiler(osname=None, platform=None): | ||||
|     """ Determine the default compiler to use for the given platform. | ||||
| 
 | ||||
|         osname should be one of the standard Python OS names (i.e. the | ||||
|         ones returned by os.name) and platform the common value | ||||
|         returned by sys.platform for the platform in question. | ||||
| 
 | ||||
|         The default values are os.name and sys.platform in case the | ||||
|         parameters are not given. | ||||
| 
 | ||||
|     """ | ||||
|     if osname is None: | ||||
|         osname = os.name | ||||
|     if platform is None: | ||||
|         platform = sys.platform | ||||
|     for pattern, compiler in _default_compilers: | ||||
|         if re.match(pattern, platform) is not None or \ | ||||
|            re.match(pattern, osname) is not None: | ||||
|             return compiler | ||||
|     # Defaults to Unix compiler | ||||
|     return 'unix' | ||||
| 
 | ||||
| 
 | ||||
| # compiler mapping | ||||
| # XXX useful to expose them? (i.e. get_compiler_names) | ||||
| _COMPILERS = { | ||||
|     'unix': 'packaging.compiler.unixccompiler.UnixCCompiler', | ||||
|     'msvc': 'packaging.compiler.msvccompiler.MSVCCompiler', | ||||
|     'cygwin': 'packaging.compiler.cygwinccompiler.CygwinCCompiler', | ||||
|     'mingw32': 'packaging.compiler.cygwinccompiler.Mingw32CCompiler', | ||||
|     'bcpp': 'packaging.compiler.bcppcompiler.BCPPCompiler', | ||||
| } | ||||
| 
 | ||||
| def set_compiler(location): | ||||
|     """Add or change a compiler""" | ||||
|     cls = resolve_name(location) | ||||
|     # XXX we want to check the class here | ||||
|     _COMPILERS[cls.name] = cls | ||||
| 
 | ||||
| 
 | ||||
| def show_compilers(): | ||||
|     """Print list of available compilers (used by the "--help-compiler" | ||||
|     options to "build", "build_ext", "build_clib"). | ||||
|     """ | ||||
|     from packaging.fancy_getopt import FancyGetopt | ||||
|     compilers = [] | ||||
| 
 | ||||
|     for name, cls in _COMPILERS.items(): | ||||
|         if isinstance(cls, str): | ||||
|             cls = resolve_name(cls) | ||||
|             _COMPILERS[name] = cls | ||||
| 
 | ||||
|         compilers.append(("compiler=" + name, None, cls.description)) | ||||
| 
 | ||||
|     compilers.sort() | ||||
|     pretty_printer = FancyGetopt(compilers) | ||||
|     pretty_printer.print_help("List of available compilers:") | ||||
| 
 | ||||
| 
 | ||||
| def new_compiler(plat=None, compiler=None, verbose=0, dry_run=False, | ||||
|                  force=False): | ||||
|     """Generate an instance of some CCompiler subclass for the supplied | ||||
|     platform/compiler combination.  'plat' defaults to 'os.name' | ||||
|     (eg. 'posix', 'nt'), and 'compiler' defaults to the default compiler | ||||
|     for that platform.  Currently only 'posix' and 'nt' are supported, and | ||||
|     the default compilers are "traditional Unix interface" (UnixCCompiler | ||||
|     class) and Visual C++ (MSVCCompiler class).  Note that it's perfectly | ||||
|     possible to ask for a Unix compiler object under Windows, and a | ||||
|     Microsoft compiler object under Unix -- if you supply a value for | ||||
|     'compiler', 'plat' is ignored. | ||||
|     """ | ||||
|     if plat is None: | ||||
|         plat = os.name | ||||
| 
 | ||||
|     try: | ||||
|         if compiler is None: | ||||
|             compiler = get_default_compiler(plat) | ||||
| 
 | ||||
|         cls = _COMPILERS[compiler] | ||||
|     except KeyError: | ||||
|         msg = "don't know how to compile C/C++ code on platform '%s'" % plat | ||||
|         if compiler is not None: | ||||
|             msg = msg + " with '%s' compiler" % compiler | ||||
|         raise PackagingPlatformError(msg) | ||||
| 
 | ||||
|     if isinstance(cls, str): | ||||
|         cls = resolve_name(cls) | ||||
|         _COMPILERS[compiler] = cls | ||||
| 
 | ||||
| 
 | ||||
|     # XXX The None is necessary to preserve backwards compatibility | ||||
|     # with classes that expect verbose to be the first positional | ||||
|     # argument. | ||||
|     return cls(None, dry_run, force) | ||||
| 
 | ||||
| 
 | ||||
| def gen_preprocess_options(macros, include_dirs): | ||||
|     """Generate C pre-processor options (-D, -U, -I) as used by at least | ||||
|     two types of compilers: the typical Unix compiler and Visual C++. | ||||
|     'macros' is the usual thing, a list of 1- or 2-tuples, where (name,) | ||||
|     means undefine (-U) macro 'name', and (name,value) means define (-D) | ||||
|     macro 'name' to 'value'.  'include_dirs' is just a list of directory | ||||
|     names to be added to the header file search path (-I).  Returns a list | ||||
|     of command-line options suitable for either Unix compilers or Visual | ||||
|     C++. | ||||
|     """ | ||||
|     # XXX it would be nice (mainly aesthetic, and so we don't generate | ||||
|     # stupid-looking command lines) to go over 'macros' and eliminate | ||||
|     # redundant definitions/undefinitions (ie. ensure that only the | ||||
|     # latest mention of a particular macro winds up on the command | ||||
|     # line).  I don't think it's essential, though, since most (all?) | ||||
|     # Unix C compilers only pay attention to the latest -D or -U | ||||
|     # mention of a macro on their command line.  Similar situation for | ||||
|     # 'include_dirs'.  I'm punting on both for now.  Anyways, weeding out | ||||
|     # redundancies like this should probably be the province of | ||||
|     # CCompiler, since the data structures used are inherited from it | ||||
|     # and therefore common to all CCompiler classes. | ||||
| 
 | ||||
|     pp_opts = [] | ||||
|     for macro in macros: | ||||
| 
 | ||||
|         if not isinstance(macro, tuple) and 1 <= len(macro) <= 2: | ||||
|             raise TypeError( | ||||
|                 "bad macro definition '%s': each element of 'macros'" | ||||
|                 "list must be a 1- or 2-tuple" % macro) | ||||
| 
 | ||||
|         if len(macro) == 1:        # undefine this macro | ||||
|             pp_opts.append("-U%s" % macro[0]) | ||||
|         elif len(macro) == 2: | ||||
|             if macro[1] is None:    # define with no explicit value | ||||
|                 pp_opts.append("-D%s" % macro[0]) | ||||
|             else: | ||||
|                 # XXX *don't* need to be clever about quoting the | ||||
|                 # macro value here, because we're going to avoid the | ||||
|                 # shell at all costs when we spawn the command! | ||||
|                 pp_opts.append("-D%s=%s" % macro) | ||||
| 
 | ||||
|     for dir in include_dirs: | ||||
|         pp_opts.append("-I%s" % dir) | ||||
| 
 | ||||
|     return pp_opts | ||||
| 
 | ||||
| 
 | ||||
| def gen_lib_options(compiler, library_dirs, runtime_library_dirs, libraries): | ||||
|     """Generate linker options for searching library directories and | ||||
|     linking with specific libraries. | ||||
| 
 | ||||
|     'libraries' and 'library_dirs' are, respectively, lists of library names | ||||
|     (not filenames!) and search directories.  Returns a list of command-line | ||||
|     options suitable for use with some compiler (depending on the two format | ||||
|     strings passed in). | ||||
|     """ | ||||
|     lib_opts = [] | ||||
| 
 | ||||
|     for dir in library_dirs: | ||||
|         lib_opts.append(compiler.library_dir_option(dir)) | ||||
| 
 | ||||
|     for dir in runtime_library_dirs: | ||||
|         opt = compiler.runtime_library_dir_option(dir) | ||||
|         if isinstance(opt, list): | ||||
|             lib_opts.extend(opt) | ||||
|         else: | ||||
|             lib_opts.append(opt) | ||||
| 
 | ||||
|     # XXX it's important that we *not* remove redundant library mentions! | ||||
|     # sometimes you really do have to say "-lfoo -lbar -lfoo" in order to | ||||
|     # resolve all symbols.  I just hope we never have to say "-lfoo obj.o | ||||
|     # -lbar" to get things to work -- that's certainly a possibility, but a | ||||
|     # pretty nasty way to arrange your C code. | ||||
| 
 | ||||
|     for lib in libraries: | ||||
|         lib_dir, lib_name = os.path.split(lib) | ||||
|         if lib_dir != '': | ||||
|             lib_file = compiler.find_library_file([lib_dir], lib_name) | ||||
|             if lib_file is not None: | ||||
|                 lib_opts.append(lib_file) | ||||
|             else: | ||||
|                 compiler.warn("no library file corresponding to " | ||||
|                               "'%s' found (skipping)" % lib) | ||||
|         else: | ||||
|             lib_opts.append(compiler.library_option(lib)) | ||||
| 
 | ||||
|     return lib_opts | ||||
							
								
								
									
										356
									
								
								Lib/packaging/compiler/bcppcompiler.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										356
									
								
								Lib/packaging/compiler/bcppcompiler.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,356 @@ | |||
| """CCompiler implementation for the Borland C++ compiler.""" | ||||
| 
 | ||||
| # This implementation by Lyle Johnson, based on the original msvccompiler.py | ||||
| # module and using the directions originally published by Gordon Williams. | ||||
| 
 | ||||
| # XXX looks like there's a LOT of overlap between these two classes: | ||||
| # someone should sit down and factor out the common code as | ||||
| # WindowsCCompiler!  --GPW | ||||
| 
 | ||||
| import os | ||||
| 
 | ||||
| from packaging.errors import (PackagingExecError, CompileError, LibError, | ||||
|                               LinkError, UnknownFileError) | ||||
| from packaging.compiler.ccompiler import CCompiler | ||||
| from packaging.compiler import gen_preprocess_options | ||||
| from packaging.file_util import write_file | ||||
| from packaging.dep_util import newer | ||||
| from packaging import logger | ||||
| 
 | ||||
| 
 | ||||
| class BCPPCompiler(CCompiler) : | ||||
|     """Concrete class that implements an interface to the Borland C/C++ | ||||
|     compiler, as defined by the CCompiler abstract class. | ||||
|     """ | ||||
| 
 | ||||
|     name = 'bcpp' | ||||
|     description = 'Borland C++ Compiler' | ||||
| 
 | ||||
|     # Just set this so CCompiler's constructor doesn't barf.  We currently | ||||
|     # don't use the 'set_executables()' bureaucracy provided by CCompiler, | ||||
|     # as it really isn't necessary for this sort of single-compiler class. | ||||
|     # Would be nice to have a consistent interface with UnixCCompiler, | ||||
|     # though, so it's worth thinking about. | ||||
|     executables = {} | ||||
| 
 | ||||
|     # Private class data (need to distinguish C from C++ source for compiler) | ||||
|     _c_extensions = ['.c'] | ||||
|     _cpp_extensions = ['.cc', '.cpp', '.cxx'] | ||||
| 
 | ||||
|     # Needed for the filename generation methods provided by the | ||||
|     # base class, CCompiler. | ||||
|     src_extensions = _c_extensions + _cpp_extensions | ||||
|     obj_extension = '.obj' | ||||
|     static_lib_extension = '.lib' | ||||
|     shared_lib_extension = '.dll' | ||||
|     static_lib_format = shared_lib_format = '%s%s' | ||||
|     exe_extension = '.exe' | ||||
| 
 | ||||
| 
 | ||||
|     def __init__(self, verbose=0, dry_run=False, force=False): | ||||
|         CCompiler.__init__(self, verbose, dry_run, force) | ||||
| 
 | ||||
|         # These executables are assumed to all be in the path. | ||||
|         # Borland doesn't seem to use any special registry settings to | ||||
|         # indicate their installation locations. | ||||
| 
 | ||||
|         self.cc = "bcc32.exe" | ||||
|         self.linker = "ilink32.exe" | ||||
|         self.lib = "tlib.exe" | ||||
| 
 | ||||
|         self.preprocess_options = None | ||||
|         self.compile_options = ['/tWM', '/O2', '/q', '/g0'] | ||||
|         self.compile_options_debug = ['/tWM', '/Od', '/q', '/g0'] | ||||
| 
 | ||||
|         self.ldflags_shared = ['/Tpd', '/Gn', '/q', '/x'] | ||||
|         self.ldflags_shared_debug = ['/Tpd', '/Gn', '/q', '/x'] | ||||
|         self.ldflags_static = [] | ||||
|         self.ldflags_exe = ['/Gn', '/q', '/x'] | ||||
|         self.ldflags_exe_debug = ['/Gn', '/q', '/x','/r'] | ||||
| 
 | ||||
| 
 | ||||
|     # -- Worker methods ------------------------------------------------ | ||||
| 
 | ||||
|     def compile(self, sources, | ||||
|                 output_dir=None, macros=None, include_dirs=None, debug=False, | ||||
|                 extra_preargs=None, extra_postargs=None, depends=None): | ||||
| 
 | ||||
|         macros, objects, extra_postargs, pp_opts, build = \ | ||||
|                 self._setup_compile(output_dir, macros, include_dirs, sources, | ||||
|                                     depends, extra_postargs) | ||||
|         compile_opts = extra_preargs or [] | ||||
|         compile_opts.append('-c') | ||||
|         if debug: | ||||
|             compile_opts.extend(self.compile_options_debug) | ||||
|         else: | ||||
|             compile_opts.extend(self.compile_options) | ||||
| 
 | ||||
|         for obj in objects: | ||||
|             try: | ||||
|                 src, ext = build[obj] | ||||
|             except KeyError: | ||||
|                 continue | ||||
|             # XXX why do the normpath here? | ||||
|             src = os.path.normpath(src) | ||||
|             obj = os.path.normpath(obj) | ||||
|             # XXX _setup_compile() did a mkpath() too but before the normpath. | ||||
|             # Is it possible to skip the normpath? | ||||
|             self.mkpath(os.path.dirname(obj)) | ||||
| 
 | ||||
|             if ext == '.res': | ||||
|                 # This is already a binary file -- skip it. | ||||
|                 continue # the 'for' loop | ||||
|             if ext == '.rc': | ||||
|                 # This needs to be compiled to a .res file -- do it now. | ||||
|                 try: | ||||
|                     self.spawn(["brcc32", "-fo", obj, src]) | ||||
|                 except PackagingExecError as msg: | ||||
|                     raise CompileError(msg) | ||||
|                 continue # the 'for' loop | ||||
| 
 | ||||
|             # The next two are both for the real compiler. | ||||
|             if ext in self._c_extensions: | ||||
|                 input_opt = "" | ||||
|             elif ext in self._cpp_extensions: | ||||
|                 input_opt = "-P" | ||||
|             else: | ||||
|                 # Unknown file type -- no extra options.  The compiler | ||||
|                 # will probably fail, but let it just in case this is a | ||||
|                 # file the compiler recognizes even if we don't. | ||||
|                 input_opt = "" | ||||
| 
 | ||||
|             output_opt = "-o" + obj | ||||
| 
 | ||||
|             # Compiler command line syntax is: "bcc32 [options] file(s)". | ||||
|             # Note that the source file names must appear at the end of | ||||
|             # the command line. | ||||
|             try: | ||||
|                 self.spawn([self.cc] + compile_opts + pp_opts + | ||||
|                            [input_opt, output_opt] + | ||||
|                            extra_postargs + [src]) | ||||
|             except PackagingExecError as msg: | ||||
|                 raise CompileError(msg) | ||||
| 
 | ||||
|         return objects | ||||
| 
 | ||||
| 
 | ||||
|     def create_static_lib(self, objects, output_libname, output_dir=None, | ||||
|                           debug=False, target_lang=None): | ||||
|         objects, output_dir = self._fix_object_args(objects, output_dir) | ||||
|         output_filename = \ | ||||
|             self.library_filename(output_libname, output_dir=output_dir) | ||||
| 
 | ||||
|         if self._need_link(objects, output_filename): | ||||
|             lib_args = [output_filename, '/u'] + objects | ||||
|             if debug: | ||||
|                 pass                    # XXX what goes here? | ||||
|             try: | ||||
|                 self.spawn([self.lib] + lib_args) | ||||
|             except PackagingExecError as msg: | ||||
|                 raise LibError(msg) | ||||
|         else: | ||||
|             logger.debug("skipping %s (up-to-date)", output_filename) | ||||
| 
 | ||||
| 
 | ||||
|     def link(self, target_desc, objects, output_filename, output_dir=None, | ||||
|              libraries=None, library_dirs=None, runtime_library_dirs=None, | ||||
|              export_symbols=None, debug=False, extra_preargs=None, | ||||
|              extra_postargs=None, build_temp=None, target_lang=None): | ||||
| 
 | ||||
|         # XXX this ignores 'build_temp'!  should follow the lead of | ||||
|         # msvccompiler.py | ||||
| 
 | ||||
|         objects, output_dir = self._fix_object_args(objects, output_dir) | ||||
|         libraries, library_dirs, runtime_library_dirs = \ | ||||
|             self._fix_lib_args(libraries, library_dirs, runtime_library_dirs) | ||||
| 
 | ||||
|         if runtime_library_dirs: | ||||
|             logger.warning("don't know what to do with " | ||||
|                            "'runtime_library_dirs': %r", runtime_library_dirs) | ||||
| 
 | ||||
|         if output_dir is not None: | ||||
|             output_filename = os.path.join(output_dir, output_filename) | ||||
| 
 | ||||
|         if self._need_link(objects, output_filename): | ||||
| 
 | ||||
|             # Figure out linker args based on type of target. | ||||
|             if target_desc == CCompiler.EXECUTABLE: | ||||
|                 startup_obj = 'c0w32' | ||||
|                 if debug: | ||||
|                     ld_args = self.ldflags_exe_debug[:] | ||||
|                 else: | ||||
|                     ld_args = self.ldflags_exe[:] | ||||
|             else: | ||||
|                 startup_obj = 'c0d32' | ||||
|                 if debug: | ||||
|                     ld_args = self.ldflags_shared_debug[:] | ||||
|                 else: | ||||
|                     ld_args = self.ldflags_shared[:] | ||||
| 
 | ||||
| 
 | ||||
|             # Create a temporary exports file for use by the linker | ||||
|             if export_symbols is None: | ||||
|                 def_file = '' | ||||
|             else: | ||||
|                 head, tail = os.path.split(output_filename) | ||||
|                 modname, ext = os.path.splitext(tail) | ||||
|                 temp_dir = os.path.dirname(objects[0]) # preserve tree structure | ||||
|                 def_file = os.path.join(temp_dir, '%s.def' % modname) | ||||
|                 contents = ['EXPORTS'] | ||||
|                 for sym in (export_symbols or []): | ||||
|                     contents.append('  %s=_%s' % (sym, sym)) | ||||
|                 self.execute(write_file, (def_file, contents), | ||||
|                              "writing %s" % def_file) | ||||
| 
 | ||||
|             # Borland C++ has problems with '/' in paths | ||||
|             objects2 = [os.path.normpath(o) for o in objects] | ||||
|             # split objects in .obj and .res files | ||||
|             # Borland C++ needs them at different positions in the command line | ||||
|             objects = [startup_obj] | ||||
|             resources = [] | ||||
|             for file in objects2: | ||||
|                 base, ext = os.path.splitext(os.path.normcase(file)) | ||||
|                 if ext == '.res': | ||||
|                     resources.append(file) | ||||
|                 else: | ||||
|                     objects.append(file) | ||||
| 
 | ||||
| 
 | ||||
|             for l in library_dirs: | ||||
|                 ld_args.append("/L%s" % os.path.normpath(l)) | ||||
|             ld_args.append("/L.") # we sometimes use relative paths | ||||
| 
 | ||||
|             # list of object files | ||||
|             ld_args.extend(objects) | ||||
| 
 | ||||
|             # XXX the command line syntax for Borland C++ is a bit wonky; | ||||
|             # certain filenames are jammed together in one big string, but | ||||
|             # comma-delimited.  This doesn't mesh too well with the | ||||
|             # Unix-centric attitude (with a DOS/Windows quoting hack) of | ||||
|             # 'spawn()', so constructing the argument list is a bit | ||||
|             # awkward.  Note that doing the obvious thing and jamming all | ||||
|             # the filenames and commas into one argument would be wrong, | ||||
|             # because 'spawn()' would quote any filenames with spaces in | ||||
|             # them.  Arghghh!.  Apparently it works fine as coded... | ||||
| 
 | ||||
|             # name of dll/exe file | ||||
|             ld_args.extend((',',output_filename)) | ||||
|             # no map file and start libraries | ||||
|             ld_args.append(',,') | ||||
| 
 | ||||
|             for lib in libraries: | ||||
|                 # see if we find it and if there is a bcpp specific lib | ||||
|                 # (xxx_bcpp.lib) | ||||
|                 libfile = self.find_library_file(library_dirs, lib, debug) | ||||
|                 if libfile is None: | ||||
|                     ld_args.append(lib) | ||||
|                     # probably a BCPP internal library -- don't warn | ||||
|                 else: | ||||
|                     # full name which prefers bcpp_xxx.lib over xxx.lib | ||||
|                     ld_args.append(libfile) | ||||
| 
 | ||||
|             # some default libraries | ||||
|             ld_args.append('import32') | ||||
|             ld_args.append('cw32mt') | ||||
| 
 | ||||
|             # def file for export symbols | ||||
|             ld_args.extend((',',def_file)) | ||||
|             # add resource files | ||||
|             ld_args.append(',') | ||||
|             ld_args.extend(resources) | ||||
| 
 | ||||
| 
 | ||||
|             if extra_preargs: | ||||
|                 ld_args[:0] = extra_preargs | ||||
|             if extra_postargs: | ||||
|                 ld_args.extend(extra_postargs) | ||||
| 
 | ||||
|             self.mkpath(os.path.dirname(output_filename)) | ||||
|             try: | ||||
|                 self.spawn([self.linker] + ld_args) | ||||
|             except PackagingExecError as msg: | ||||
|                 raise LinkError(msg) | ||||
| 
 | ||||
|         else: | ||||
|             logger.debug("skipping %s (up-to-date)", output_filename) | ||||
| 
 | ||||
|     # -- Miscellaneous methods ----------------------------------------- | ||||
| 
 | ||||
| 
 | ||||
|     def find_library_file(self, dirs, lib, debug=False): | ||||
|         # List of effective library names to try, in order of preference: | ||||
|         # xxx_bcpp.lib is better than xxx.lib | ||||
|         # and xxx_d.lib is better than xxx.lib if debug is set | ||||
|         # | ||||
|         # The "_bcpp" suffix is to handle a Python installation for people | ||||
|         # with multiple compilers (primarily Packaging hackers, I suspect | ||||
|         # ;-).  The idea is they'd have one static library for each | ||||
|         # compiler they care about, since (almost?) every Windows compiler | ||||
|         # seems to have a different format for static libraries. | ||||
|         if debug: | ||||
|             dlib = (lib + "_d") | ||||
|             try_names = (dlib + "_bcpp", lib + "_bcpp", dlib, lib) | ||||
|         else: | ||||
|             try_names = (lib + "_bcpp", lib) | ||||
| 
 | ||||
|         for dir in dirs: | ||||
|             for name in try_names: | ||||
|                 libfile = os.path.join(dir, self.library_filename(name)) | ||||
|                 if os.path.exists(libfile): | ||||
|                     return libfile | ||||
|         else: | ||||
|             # Oops, didn't find it in *any* of 'dirs' | ||||
|             return None | ||||
| 
 | ||||
|     # overwrite the one from CCompiler to support rc and res-files | ||||
|     def object_filenames(self, source_filenames, strip_dir=False, | ||||
|                          output_dir=''): | ||||
|         if output_dir is None: | ||||
|             output_dir = '' | ||||
|         obj_names = [] | ||||
|         for src_name in source_filenames: | ||||
|             # use normcase to make sure '.rc' is really '.rc' and not '.RC' | ||||
|             base, ext = os.path.splitext(os.path.normcase(src_name)) | ||||
|             if ext not in (self.src_extensions + ['.rc','.res']): | ||||
|                 raise UnknownFileError("unknown file type '%s' (from '%s')" % \ | ||||
|                       (ext, src_name)) | ||||
|             if strip_dir: | ||||
|                 base = os.path.basename(base) | ||||
|             if ext == '.res': | ||||
|                 # these can go unchanged | ||||
|                 obj_names.append(os.path.join(output_dir, base + ext)) | ||||
|             elif ext == '.rc': | ||||
|                 # these need to be compiled to .res-files | ||||
|                 obj_names.append(os.path.join(output_dir, base + '.res')) | ||||
|             else: | ||||
|                 obj_names.append(os.path.join(output_dir, | ||||
|                                               base + self.obj_extension)) | ||||
|         return obj_names | ||||
| 
 | ||||
| 
 | ||||
|     def preprocess(self, source, output_file=None, macros=None, | ||||
|                    include_dirs=None, extra_preargs=None, | ||||
|                    extra_postargs=None): | ||||
|         _, macros, include_dirs = \ | ||||
|             self._fix_compile_args(None, macros, include_dirs) | ||||
|         pp_opts = gen_preprocess_options(macros, include_dirs) | ||||
|         pp_args = ['cpp32.exe'] + pp_opts | ||||
|         if output_file is not None: | ||||
|             pp_args.append('-o' + output_file) | ||||
|         if extra_preargs: | ||||
|             pp_args[:0] = extra_preargs | ||||
|         if extra_postargs: | ||||
|             pp_args.extend(extra_postargs) | ||||
|         pp_args.append(source) | ||||
| 
 | ||||
|         # We need to preprocess: either we're being forced to, or the | ||||
|         # source file is newer than the target (or the target doesn't | ||||
|         # exist). | ||||
|         if self.force or output_file is None or newer(source, output_file): | ||||
|             if output_file: | ||||
|                 self.mkpath(os.path.dirname(output_file)) | ||||
|             try: | ||||
|                 self.spawn(pp_args) | ||||
|             except PackagingExecError as msg: | ||||
|                 print(msg) | ||||
|                 raise CompileError(msg) | ||||
							
								
								
									
										868
									
								
								Lib/packaging/compiler/ccompiler.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										868
									
								
								Lib/packaging/compiler/ccompiler.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,868 @@ | |||
| """Abstract base class for compilers. | ||||
| 
 | ||||
| This modules contains CCompiler, an abstract base class that defines the | ||||
| interface for the compiler abstraction model used by packaging. | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| import sys | ||||
| from shutil import move | ||||
| from packaging import logger | ||||
| from packaging.util import split_quoted, execute, newer_group, spawn | ||||
| from packaging.errors import (CompileError, LinkError, UnknownFileError) | ||||
| from packaging.compiler import gen_preprocess_options | ||||
| 
 | ||||
| 
 | ||||
| class CCompiler: | ||||
|     """Abstract base class to define the interface that must be implemented | ||||
|     by real compiler classes.  Also has some utility methods used by | ||||
|     several compiler classes. | ||||
| 
 | ||||
|     The basic idea behind a compiler abstraction class is that each | ||||
|     instance can be used for all the compile/link steps in building a | ||||
|     single project.  Thus, attributes common to all of those compile and | ||||
|     link steps -- include directories, macros to define, libraries to link | ||||
|     against, etc. -- are attributes of the compiler instance.  To allow for | ||||
|     variability in how individual files are treated, most of those | ||||
|     attributes may be varied on a per-compilation or per-link basis. | ||||
|     """ | ||||
| 
 | ||||
|     # 'name' is a class attribute that identifies this class.  It | ||||
|     # keeps code that wants to know what kind of compiler it's dealing with | ||||
|     # from having to import all possible compiler classes just to do an | ||||
|     # 'isinstance'. | ||||
|     name = None | ||||
|     description = None | ||||
| 
 | ||||
|     # XXX things not handled by this compiler abstraction model: | ||||
|     #   * client can't provide additional options for a compiler, | ||||
|     #     e.g. warning, optimization, debugging flags.  Perhaps this | ||||
|     #     should be the domain of concrete compiler abstraction classes | ||||
|     #     (UnixCCompiler, MSVCCompiler, etc.) -- or perhaps the base | ||||
|     #     class should have methods for the common ones. | ||||
|     #   * can't completely override the include or library searchg | ||||
|     #     path, ie. no "cc -I -Idir1 -Idir2" or "cc -L -Ldir1 -Ldir2". | ||||
|     #     I'm not sure how widely supported this is even by Unix | ||||
|     #     compilers, much less on other platforms.  And I'm even less | ||||
|     #     sure how useful it is; maybe for cross-compiling, but | ||||
|     #     support for that is a ways off.  (And anyways, cross | ||||
|     #     compilers probably have a dedicated binary with the | ||||
|     #     right paths compiled in.  I hope.) | ||||
|     #   * can't do really freaky things with the library list/library | ||||
|     #     dirs, e.g. "-Ldir1 -lfoo -Ldir2 -lfoo" to link against | ||||
|     #     different versions of libfoo.a in different locations.  I | ||||
|     #     think this is useless without the ability to null out the | ||||
|     #     library search path anyways. | ||||
| 
 | ||||
| 
 | ||||
|     # Subclasses that rely on the standard filename generation methods | ||||
|     # implemented below should override these; see the comment near | ||||
|     # those methods ('object_filenames()' et. al.) for details: | ||||
|     src_extensions = None               # list of strings | ||||
|     obj_extension = None                # string | ||||
|     static_lib_extension = None | ||||
|     shared_lib_extension = None         # string | ||||
|     static_lib_format = None            # format string | ||||
|     shared_lib_format = None            # prob. same as static_lib_format | ||||
|     exe_extension = None                # string | ||||
| 
 | ||||
|     # Default language settings. language_map is used to detect a source | ||||
|     # file or Extension target language, checking source filenames. | ||||
|     # language_order is used to detect the language precedence, when deciding | ||||
|     # what language to use when mixing source types. For example, if some | ||||
|     # extension has two files with ".c" extension, and one with ".cpp", it | ||||
|     # is still linked as c++. | ||||
|     language_map = {".c": "c", | ||||
|                     ".cc": "c++", | ||||
|                     ".cpp": "c++", | ||||
|                     ".cxx": "c++", | ||||
|                     ".m": "objc", | ||||
|                    } | ||||
|     language_order = ["c++", "objc", "c"] | ||||
| 
 | ||||
|     def __init__(self, verbose=0, dry_run=False, force=False): | ||||
|         self.dry_run = dry_run | ||||
|         self.force = force | ||||
|         self.verbose = verbose | ||||
| 
 | ||||
|         # 'output_dir': a common output directory for object, library, | ||||
|         # shared object, and shared library files | ||||
|         self.output_dir = None | ||||
| 
 | ||||
|         # 'macros': a list of macro definitions (or undefinitions).  A | ||||
|         # macro definition is a 2-tuple (name, value), where the value is | ||||
|         # either a string or None (no explicit value).  A macro | ||||
|         # undefinition is a 1-tuple (name,). | ||||
|         self.macros = [] | ||||
| 
 | ||||
|         # 'include_dirs': a list of directories to search for include files | ||||
|         self.include_dirs = [] | ||||
| 
 | ||||
|         # 'libraries': a list of libraries to include in any link | ||||
|         # (library names, not filenames: eg. "foo" not "libfoo.a") | ||||
|         self.libraries = [] | ||||
| 
 | ||||
|         # 'library_dirs': a list of directories to search for libraries | ||||
|         self.library_dirs = [] | ||||
| 
 | ||||
|         # 'runtime_library_dirs': a list of directories to search for | ||||
|         # shared libraries/objects at runtime | ||||
|         self.runtime_library_dirs = [] | ||||
| 
 | ||||
|         # 'objects': a list of object files (or similar, such as explicitly | ||||
|         # named library files) to include on any link | ||||
|         self.objects = [] | ||||
| 
 | ||||
|         for key, value in self.executables.items(): | ||||
|             self.set_executable(key, value) | ||||
| 
 | ||||
|     def set_executables(self, **args): | ||||
|         """Define the executables (and options for them) that will be run | ||||
|         to perform the various stages of compilation.  The exact set of | ||||
|         executables that may be specified here depends on the compiler | ||||
|         class (via the 'executables' class attribute), but most will have: | ||||
|           compiler      the C/C++ compiler | ||||
|           linker_so     linker used to create shared objects and libraries | ||||
|           linker_exe    linker used to create binary executables | ||||
|           archiver      static library creator | ||||
| 
 | ||||
|         On platforms with a command line (Unix, DOS/Windows), each of these | ||||
|         is a string that will be split into executable name and (optional) | ||||
|         list of arguments.  (Splitting the string is done similarly to how | ||||
|         Unix shells operate: words are delimited by spaces, but quotes and | ||||
|         backslashes can override this.  See | ||||
|         'distutils.util.split_quoted()'.) | ||||
|         """ | ||||
| 
 | ||||
|         # Note that some CCompiler implementation classes will define class | ||||
|         # attributes 'cpp', 'cc', etc. with hard-coded executable names; | ||||
|         # this is appropriate when a compiler class is for exactly one | ||||
|         # compiler/OS combination (eg. MSVCCompiler).  Other compiler | ||||
|         # classes (UnixCCompiler, in particular) are driven by information | ||||
|         # discovered at run-time, since there are many different ways to do | ||||
|         # basically the same things with Unix C compilers. | ||||
| 
 | ||||
|         for key, value in args.items(): | ||||
|             if key not in self.executables: | ||||
|                 raise ValueError("unknown executable '%s' for class %s" % \ | ||||
|                       (key, self.__class__.__name__)) | ||||
|             self.set_executable(key, value) | ||||
| 
 | ||||
|     def set_executable(self, key, value): | ||||
|         if isinstance(value, str): | ||||
|             setattr(self, key, split_quoted(value)) | ||||
|         else: | ||||
|             setattr(self, key, value) | ||||
| 
 | ||||
|     def _find_macro(self, name): | ||||
|         i = 0 | ||||
|         for defn in self.macros: | ||||
|             if defn[0] == name: | ||||
|                 return i | ||||
|             i = i + 1 | ||||
|         return None | ||||
| 
 | ||||
|     def _check_macro_definitions(self, definitions): | ||||
|         """Ensures that every element of 'definitions' is a valid macro | ||||
|         definition, ie. either (name,value) 2-tuple or a (name,) tuple.  Do | ||||
|         nothing if all definitions are OK, raise TypeError otherwise. | ||||
|         """ | ||||
|         for defn in definitions: | ||||
|             if not (isinstance(defn, tuple) and | ||||
|                     (len(defn) == 1 or | ||||
|                      (len(defn) == 2 and | ||||
|                       (isinstance(defn[1], str) or defn[1] is None))) and | ||||
|                     isinstance(defn[0], str)): | ||||
|                 raise TypeError(("invalid macro definition '%s': " % defn) + \ | ||||
|                       "must be tuple (string,), (string, string), or " + \ | ||||
|                       "(string, None)") | ||||
| 
 | ||||
| 
 | ||||
|     # -- Bookkeeping methods ------------------------------------------- | ||||
| 
 | ||||
|     def define_macro(self, name, value=None): | ||||
|         """Define a preprocessor macro for all compilations driven by this | ||||
|         compiler object.  The optional parameter 'value' should be a | ||||
|         string; if it is not supplied, then the macro will be defined | ||||
|         without an explicit value and the exact outcome depends on the | ||||
|         compiler used (XXX true? does ANSI say anything about this?) | ||||
|         """ | ||||
|         # Delete from the list of macro definitions/undefinitions if | ||||
|         # already there (so that this one will take precedence). | ||||
|         i = self._find_macro(name) | ||||
|         if i is not None: | ||||
|             del self.macros[i] | ||||
| 
 | ||||
|         defn = (name, value) | ||||
|         self.macros.append(defn) | ||||
| 
 | ||||
|     def undefine_macro(self, name): | ||||
|         """Undefine a preprocessor macro for all compilations driven by | ||||
|         this compiler object.  If the same macro is defined by | ||||
|         'define_macro()' and undefined by 'undefine_macro()' the last call | ||||
|         takes precedence (including multiple redefinitions or | ||||
|         undefinitions).  If the macro is redefined/undefined on a | ||||
|         per-compilation basis (ie. in the call to 'compile()'), then that | ||||
|         takes precedence. | ||||
|         """ | ||||
|         # Delete from the list of macro definitions/undefinitions if | ||||
|         # already there (so that this one will take precedence). | ||||
|         i = self._find_macro(name) | ||||
|         if i is not None: | ||||
|             del self.macros[i] | ||||
| 
 | ||||
|         undefn = (name,) | ||||
|         self.macros.append(undefn) | ||||
| 
 | ||||
|     def add_include_dir(self, dir): | ||||
|         """Add 'dir' to the list of directories that will be searched for | ||||
|         header files.  The compiler is instructed to search directories in | ||||
|         the order in which they are supplied by successive calls to | ||||
|         'add_include_dir()'. | ||||
|         """ | ||||
|         self.include_dirs.append(dir) | ||||
| 
 | ||||
|     def set_include_dirs(self, dirs): | ||||
|         """Set the list of directories that will be searched to 'dirs' (a | ||||
|         list of strings).  Overrides any preceding calls to | ||||
|         'add_include_dir()'; subsequence calls to 'add_include_dir()' add | ||||
|         to the list passed to 'set_include_dirs()'.  This does not affect | ||||
|         any list of standard include directories that the compiler may | ||||
|         search by default. | ||||
|         """ | ||||
|         self.include_dirs = dirs[:] | ||||
| 
 | ||||
|     def add_library(self, libname): | ||||
|         """Add 'libname' to the list of libraries that will be included in | ||||
|         all links driven by this compiler object.  Note that 'libname' | ||||
|         should *not* be the name of a file containing a library, but the | ||||
|         name of the library itself: the actual filename will be inferred by | ||||
|         the linker, the compiler, or the compiler class (depending on the | ||||
|         platform). | ||||
| 
 | ||||
|         The linker will be instructed to link against libraries in the | ||||
|         order they were supplied to 'add_library()' and/or | ||||
|         'set_libraries()'.  It is perfectly valid to duplicate library | ||||
|         names; the linker will be instructed to link against libraries as | ||||
|         many times as they are mentioned. | ||||
|         """ | ||||
|         self.libraries.append(libname) | ||||
| 
 | ||||
|     def set_libraries(self, libnames): | ||||
|         """Set the list of libraries to be included in all links driven by | ||||
|         this compiler object to 'libnames' (a list of strings).  This does | ||||
|         not affect any standard system libraries that the linker may | ||||
|         include by default. | ||||
|         """ | ||||
|         self.libraries = libnames[:] | ||||
| 
 | ||||
| 
 | ||||
|     def add_library_dir(self, dir): | ||||
|         """Add 'dir' to the list of directories that will be searched for | ||||
|         libraries specified to 'add_library()' and 'set_libraries()'.  The | ||||
|         linker will be instructed to search for libraries in the order they | ||||
|         are supplied to 'add_library_dir()' and/or 'set_library_dirs()'. | ||||
|         """ | ||||
|         self.library_dirs.append(dir) | ||||
| 
 | ||||
|     def set_library_dirs(self, dirs): | ||||
|         """Set the list of library search directories to 'dirs' (a list of | ||||
|         strings).  This does not affect any standard library search path | ||||
|         that the linker may search by default. | ||||
|         """ | ||||
|         self.library_dirs = dirs[:] | ||||
| 
 | ||||
|     def add_runtime_library_dir(self, dir): | ||||
|         """Add 'dir' to the list of directories that will be searched for | ||||
|         shared libraries at runtime. | ||||
|         """ | ||||
|         self.runtime_library_dirs.append(dir) | ||||
| 
 | ||||
|     def set_runtime_library_dirs(self, dirs): | ||||
|         """Set the list of directories to search for shared libraries at | ||||
|         runtime to 'dirs' (a list of strings).  This does not affect any | ||||
|         standard search path that the runtime linker may search by | ||||
|         default. | ||||
|         """ | ||||
|         self.runtime_library_dirs = dirs[:] | ||||
| 
 | ||||
|     def add_link_object(self, object): | ||||
|         """Add 'object' to the list of object files (or analogues, such as | ||||
|         explicitly named library files or the output of "resource | ||||
|         compilers") to be included in every link driven by this compiler | ||||
|         object. | ||||
|         """ | ||||
|         self.objects.append(object) | ||||
| 
 | ||||
|     def set_link_objects(self, objects): | ||||
|         """Set the list of object files (or analogues) to be included in | ||||
|         every link to 'objects'.  This does not affect any standard object | ||||
|         files that the linker may include by default (such as system | ||||
|         libraries). | ||||
|         """ | ||||
|         self.objects = objects[:] | ||||
| 
 | ||||
| 
 | ||||
|     # -- Private utility methods -------------------------------------- | ||||
|     # (here for the convenience of subclasses) | ||||
| 
 | ||||
|     # Helper method to prep compiler in subclass compile() methods | ||||
|     def _setup_compile(self, outdir, macros, incdirs, sources, depends, | ||||
|                        extra): | ||||
|         """Process arguments and decide which source files to compile.""" | ||||
|         if outdir is None: | ||||
|             outdir = self.output_dir | ||||
|         elif not isinstance(outdir, str): | ||||
|             raise TypeError("'output_dir' must be a string or None") | ||||
| 
 | ||||
|         if macros is None: | ||||
|             macros = self.macros | ||||
|         elif isinstance(macros, list): | ||||
|             macros = macros + (self.macros or []) | ||||
|         else: | ||||
|             raise TypeError("'macros' (if supplied) must be a list of tuples") | ||||
| 
 | ||||
|         if incdirs is None: | ||||
|             incdirs = self.include_dirs | ||||
|         elif isinstance(incdirs, (list, tuple)): | ||||
|             incdirs = list(incdirs) + (self.include_dirs or []) | ||||
|         else: | ||||
|             raise TypeError( | ||||
|                 "'include_dirs' (if supplied) must be a list of strings") | ||||
| 
 | ||||
|         if extra is None: | ||||
|             extra = [] | ||||
| 
 | ||||
|         # Get the list of expected output (object) files | ||||
|         objects = self.object_filenames(sources, | ||||
|                                         strip_dir=False, | ||||
|                                         output_dir=outdir) | ||||
|         assert len(objects) == len(sources) | ||||
| 
 | ||||
|         pp_opts = gen_preprocess_options(macros, incdirs) | ||||
| 
 | ||||
|         build = {} | ||||
|         for i in range(len(sources)): | ||||
|             src = sources[i] | ||||
|             obj = objects[i] | ||||
|             ext = os.path.splitext(src)[1] | ||||
|             self.mkpath(os.path.dirname(obj)) | ||||
|             build[obj] = (src, ext) | ||||
| 
 | ||||
|         return macros, objects, extra, pp_opts, build | ||||
| 
 | ||||
|     def _get_cc_args(self, pp_opts, debug, before): | ||||
|         # works for unixccompiler, emxccompiler, cygwinccompiler | ||||
|         cc_args = pp_opts + ['-c'] | ||||
|         if debug: | ||||
|             cc_args[:0] = ['-g'] | ||||
|         if before: | ||||
|             cc_args[:0] = before | ||||
|         return cc_args | ||||
| 
 | ||||
|     def _fix_compile_args(self, output_dir, macros, include_dirs): | ||||
|         """Typecheck and fix-up some of the arguments to the 'compile()' | ||||
|         method, and return fixed-up values.  Specifically: if 'output_dir' | ||||
|         is None, replaces it with 'self.output_dir'; ensures that 'macros' | ||||
|         is a list, and augments it with 'self.macros'; ensures that | ||||
|         'include_dirs' is a list, and augments it with 'self.include_dirs'. | ||||
|         Guarantees that the returned values are of the correct type, | ||||
|         i.e. for 'output_dir' either string or None, and for 'macros' and | ||||
|         'include_dirs' either list or None. | ||||
|         """ | ||||
|         if output_dir is None: | ||||
|             output_dir = self.output_dir | ||||
|         elif not isinstance(output_dir, str): | ||||
|             raise TypeError("'output_dir' must be a string or None") | ||||
| 
 | ||||
|         if macros is None: | ||||
|             macros = self.macros | ||||
|         elif isinstance(macros, list): | ||||
|             macros = macros + (self.macros or []) | ||||
|         else: | ||||
|             raise TypeError("'macros' (if supplied) must be a list of tuples") | ||||
| 
 | ||||
|         if include_dirs is None: | ||||
|             include_dirs = self.include_dirs | ||||
|         elif isinstance(include_dirs, (list, tuple)): | ||||
|             include_dirs = list(include_dirs) + (self.include_dirs or []) | ||||
|         else: | ||||
|             raise TypeError( | ||||
|                 "'include_dirs' (if supplied) must be a list of strings") | ||||
| 
 | ||||
|         return output_dir, macros, include_dirs | ||||
| 
 | ||||
|     def _fix_object_args(self, objects, output_dir): | ||||
|         """Typecheck and fix up some arguments supplied to various methods. | ||||
|         Specifically: ensure that 'objects' is a list; if output_dir is | ||||
|         None, replace with self.output_dir.  Return fixed versions of | ||||
|         'objects' and 'output_dir'. | ||||
|         """ | ||||
|         if not isinstance(objects, (list, tuple)): | ||||
|             raise TypeError("'objects' must be a list or tuple of strings") | ||||
|         objects = list(objects) | ||||
| 
 | ||||
|         if output_dir is None: | ||||
|             output_dir = self.output_dir | ||||
|         elif not isinstance(output_dir, str): | ||||
|             raise TypeError("'output_dir' must be a string or None") | ||||
| 
 | ||||
|         return objects, output_dir | ||||
| 
 | ||||
|     def _fix_lib_args(self, libraries, library_dirs, runtime_library_dirs): | ||||
|         """Typecheck and fix up some of the arguments supplied to the | ||||
|         'link_*' methods.  Specifically: ensure that all arguments are | ||||
|         lists, and augment them with their permanent versions | ||||
|         (eg. 'self.libraries' augments 'libraries').  Return a tuple with | ||||
|         fixed versions of all arguments. | ||||
|         """ | ||||
|         if libraries is None: | ||||
|             libraries = self.libraries | ||||
|         elif isinstance(libraries, (list, tuple)): | ||||
|             libraries = list(libraries) + (self.libraries or []) | ||||
|         else: | ||||
|             raise TypeError( | ||||
|                 "'libraries' (if supplied) must be a list of strings") | ||||
| 
 | ||||
|         if library_dirs is None: | ||||
|             library_dirs = self.library_dirs | ||||
|         elif isinstance(library_dirs, (list, tuple)): | ||||
|             library_dirs = list(library_dirs) + (self.library_dirs or []) | ||||
|         else: | ||||
|             raise TypeError( | ||||
|                 "'library_dirs' (if supplied) must be a list of strings") | ||||
| 
 | ||||
|         if runtime_library_dirs is None: | ||||
|             runtime_library_dirs = self.runtime_library_dirs | ||||
|         elif isinstance(runtime_library_dirs, (list, tuple)): | ||||
|             runtime_library_dirs = (list(runtime_library_dirs) + | ||||
|                                     (self.runtime_library_dirs or [])) | ||||
|         else: | ||||
|             raise TypeError("'runtime_library_dirs' (if supplied) " | ||||
|                             "must be a list of strings") | ||||
| 
 | ||||
|         return libraries, library_dirs, runtime_library_dirs | ||||
| 
 | ||||
|     def _need_link(self, objects, output_file): | ||||
|         """Return true if we need to relink the files listed in 'objects' | ||||
|         to recreate 'output_file'. | ||||
|         """ | ||||
|         if self.force: | ||||
|             return True | ||||
|         else: | ||||
|             if self.dry_run: | ||||
|                 newer = newer_group(objects, output_file, missing='newer') | ||||
|             else: | ||||
|                 newer = newer_group(objects, output_file) | ||||
|             return newer | ||||
| 
 | ||||
|     def detect_language(self, sources): | ||||
|         """Detect the language of a given file, or list of files. Uses | ||||
|         language_map, and language_order to do the job. | ||||
|         """ | ||||
|         if not isinstance(sources, list): | ||||
|             sources = [sources] | ||||
|         lang = None | ||||
|         index = len(self.language_order) | ||||
|         for source in sources: | ||||
|             base, ext = os.path.splitext(source) | ||||
|             extlang = self.language_map.get(ext) | ||||
|             try: | ||||
|                 extindex = self.language_order.index(extlang) | ||||
|                 if extindex < index: | ||||
|                     lang = extlang | ||||
|                     index = extindex | ||||
|             except ValueError: | ||||
|                 pass | ||||
|         return lang | ||||
| 
 | ||||
|     # -- Worker methods ------------------------------------------------ | ||||
|     # (must be implemented by subclasses) | ||||
| 
 | ||||
|     def preprocess(self, source, output_file=None, macros=None, | ||||
|                    include_dirs=None, extra_preargs=None, extra_postargs=None): | ||||
|         """Preprocess a single C/C++ source file, named in 'source'. | ||||
|         Output will be written to file named 'output_file', or stdout if | ||||
|         'output_file' not supplied.  'macros' is a list of macro | ||||
|         definitions as for 'compile()', which will augment the macros set | ||||
|         with 'define_macro()' and 'undefine_macro()'.  'include_dirs' is a | ||||
|         list of directory names that will be added to the default list. | ||||
| 
 | ||||
|         Raises PreprocessError on failure. | ||||
|         """ | ||||
|         pass | ||||
| 
 | ||||
|     def compile(self, sources, output_dir=None, macros=None, | ||||
|                 include_dirs=None, debug=False, extra_preargs=None, | ||||
|                 extra_postargs=None, depends=None): | ||||
|         """Compile one or more source files. | ||||
| 
 | ||||
|         'sources' must be a list of filenames, most likely C/C++ | ||||
|         files, but in reality anything that can be handled by a | ||||
|         particular compiler and compiler class (eg. MSVCCompiler can | ||||
|         handle resource files in 'sources').  Return a list of object | ||||
|         filenames, one per source filename in 'sources'.  Depending on | ||||
|         the implementation, not all source files will necessarily be | ||||
|         compiled, but all corresponding object filenames will be | ||||
|         returned. | ||||
| 
 | ||||
|         If 'output_dir' is given, object files will be put under it, while | ||||
|         retaining their original path component.  That is, "foo/bar.c" | ||||
|         normally compiles to "foo/bar.o" (for a Unix implementation); if | ||||
|         'output_dir' is "build", then it would compile to | ||||
|         "build/foo/bar.o". | ||||
| 
 | ||||
|         'macros', if given, must be a list of macro definitions.  A macro | ||||
|         definition is either a (name, value) 2-tuple or a (name,) 1-tuple. | ||||
|         The former defines a macro; if the value is None, the macro is | ||||
|         defined without an explicit value.  The 1-tuple case undefines a | ||||
|         macro.  Later definitions/redefinitions/ undefinitions take | ||||
|         precedence. | ||||
| 
 | ||||
|         'include_dirs', if given, must be a list of strings, the | ||||
|         directories to add to the default include file search path for this | ||||
|         compilation only. | ||||
| 
 | ||||
|         'debug' is a boolean; if true, the compiler will be instructed to | ||||
|         output debug symbols in (or alongside) the object file(s). | ||||
| 
 | ||||
|         'extra_preargs' and 'extra_postargs' are implementation- dependent. | ||||
|         On platforms that have the notion of a command line (e.g. Unix, | ||||
|         DOS/Windows), they are most likely lists of strings: extra | ||||
|         command-line arguments to prepand/append to the compiler command | ||||
|         line.  On other platforms, consult the implementation class | ||||
|         documentation.  In any event, they are intended as an escape hatch | ||||
|         for those occasions when the abstract compiler framework doesn't | ||||
|         cut the mustard. | ||||
| 
 | ||||
|         'depends', if given, is a list of filenames that all targets | ||||
|         depend on.  If a source file is older than any file in | ||||
|         depends, then the source file will be recompiled.  This | ||||
|         supports dependency tracking, but only at a coarse | ||||
|         granularity. | ||||
| 
 | ||||
|         Raises CompileError on failure. | ||||
|         """ | ||||
|         # A concrete compiler class can either override this method | ||||
|         # entirely or implement _compile(). | ||||
| 
 | ||||
|         macros, objects, extra_postargs, pp_opts, build = \ | ||||
|                 self._setup_compile(output_dir, macros, include_dirs, sources, | ||||
|                                     depends, extra_postargs) | ||||
|         cc_args = self._get_cc_args(pp_opts, debug, extra_preargs) | ||||
| 
 | ||||
|         for obj in objects: | ||||
|             try: | ||||
|                 src, ext = build[obj] | ||||
|             except KeyError: | ||||
|                 continue | ||||
|             self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) | ||||
| 
 | ||||
|         # Return *all* object filenames, not just the ones we just built. | ||||
|         return objects | ||||
| 
 | ||||
|     def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): | ||||
|         """Compile 'src' to product 'obj'.""" | ||||
| 
 | ||||
|         # A concrete compiler class that does not override compile() | ||||
|         # should implement _compile(). | ||||
|         pass | ||||
| 
 | ||||
|     def create_static_lib(self, objects, output_libname, output_dir=None, | ||||
|                           debug=False, target_lang=None): | ||||
|         """Link a bunch of stuff together to create a static library file. | ||||
|         The "bunch of stuff" consists of the list of object files supplied | ||||
|         as 'objects', the extra object files supplied to | ||||
|         'add_link_object()' and/or 'set_link_objects()', the libraries | ||||
|         supplied to 'add_library()' and/or 'set_libraries()', and the | ||||
|         libraries supplied as 'libraries' (if any). | ||||
| 
 | ||||
|         'output_libname' should be a library name, not a filename; the | ||||
|         filename will be inferred from the library name.  'output_dir' is | ||||
|         the directory where the library file will be put. | ||||
| 
 | ||||
|         'debug' is a boolean; if true, debugging information will be | ||||
|         included in the library (note that on most platforms, it is the | ||||
|         compile step where this matters: the 'debug' flag is included here | ||||
|         just for consistency). | ||||
| 
 | ||||
|         'target_lang' is the target language for which the given objects | ||||
|         are being compiled. This allows specific linkage time treatment of | ||||
|         certain languages. | ||||
| 
 | ||||
|         Raises LibError on failure. | ||||
|         """ | ||||
|         pass | ||||
| 
 | ||||
|     # values for target_desc parameter in link() | ||||
|     SHARED_OBJECT = "shared_object" | ||||
|     SHARED_LIBRARY = "shared_library" | ||||
|     EXECUTABLE = "executable" | ||||
| 
 | ||||
|     def link(self, target_desc, objects, output_filename, output_dir=None, | ||||
|              libraries=None, library_dirs=None, runtime_library_dirs=None, | ||||
|              export_symbols=None, debug=False, extra_preargs=None, | ||||
|              extra_postargs=None, build_temp=None, target_lang=None): | ||||
|         """Link a bunch of stuff together to create an executable or | ||||
|         shared library file. | ||||
| 
 | ||||
|         The "bunch of stuff" consists of the list of object files supplied | ||||
|         as 'objects'.  'output_filename' should be a filename.  If | ||||
|         'output_dir' is supplied, 'output_filename' is relative to it | ||||
|         (i.e. 'output_filename' can provide directory components if | ||||
|         needed). | ||||
| 
 | ||||
|         'libraries' is a list of libraries to link against.  These are | ||||
|         library names, not filenames, since they're translated into | ||||
|         filenames in a platform-specific way (eg. "foo" becomes "libfoo.a" | ||||
|         on Unix and "foo.lib" on DOS/Windows).  However, they can include a | ||||
|         directory component, which means the linker will look in that | ||||
|         specific directory rather than searching all the normal locations. | ||||
| 
 | ||||
|         'library_dirs', if supplied, should be a list of directories to | ||||
|         search for libraries that were specified as bare library names | ||||
|         (ie. no directory component).  These are on top of the system | ||||
|         default and those supplied to 'add_library_dir()' and/or | ||||
|         'set_library_dirs()'.  'runtime_library_dirs' is a list of | ||||
|         directories that will be embedded into the shared library and used | ||||
|         to search for other shared libraries that *it* depends on at | ||||
|         run-time.  (This may only be relevant on Unix.) | ||||
| 
 | ||||
|         'export_symbols' is a list of symbols that the shared library will | ||||
|         export.  (This appears to be relevant only on Windows.) | ||||
| 
 | ||||
|         'debug' is as for 'compile()' and 'create_static_lib()', with the | ||||
|         slight distinction that it actually matters on most platforms (as | ||||
|         opposed to 'create_static_lib()', which includes a 'debug' flag | ||||
|         mostly for form's sake). | ||||
| 
 | ||||
|         'extra_preargs' and 'extra_postargs' are as for 'compile()' (except | ||||
|         of course that they supply command-line arguments for the | ||||
|         particular linker being used). | ||||
| 
 | ||||
|         'target_lang' is the target language for which the given objects | ||||
|         are being compiled. This allows specific linkage time treatment of | ||||
|         certain languages. | ||||
| 
 | ||||
|         Raises LinkError on failure. | ||||
|         """ | ||||
|         raise NotImplementedError | ||||
| 
 | ||||
| 
 | ||||
|     # Old 'link_*()' methods, rewritten to use the new 'link()' method. | ||||
| 
 | ||||
|     def link_shared_lib(self, objects, output_libname, output_dir=None, | ||||
|                         libraries=None, library_dirs=None, | ||||
|                         runtime_library_dirs=None, export_symbols=None, | ||||
|                         debug=False, extra_preargs=None, extra_postargs=None, | ||||
|                         build_temp=None, target_lang=None): | ||||
|         self.link(CCompiler.SHARED_LIBRARY, objects, | ||||
|                   self.library_filename(output_libname, lib_type='shared'), | ||||
|                   output_dir, | ||||
|                   libraries, library_dirs, runtime_library_dirs, | ||||
|                   export_symbols, debug, | ||||
|                   extra_preargs, extra_postargs, build_temp, target_lang) | ||||
| 
 | ||||
|     def link_shared_object(self, objects, output_filename, output_dir=None, | ||||
|                            libraries=None, library_dirs=None, | ||||
|                            runtime_library_dirs=None, export_symbols=None, | ||||
|                            debug=False, extra_preargs=None, extra_postargs=None, | ||||
|                            build_temp=None, target_lang=None): | ||||
|         self.link(CCompiler.SHARED_OBJECT, objects, | ||||
|                   output_filename, output_dir, | ||||
|                   libraries, library_dirs, runtime_library_dirs, | ||||
|                   export_symbols, debug, | ||||
|                   extra_preargs, extra_postargs, build_temp, target_lang) | ||||
| 
 | ||||
|     def link_executable(self, objects, output_progname, output_dir=None, | ||||
|                         libraries=None, library_dirs=None, | ||||
|                         runtime_library_dirs=None, debug=False, | ||||
|                         extra_preargs=None, extra_postargs=None, | ||||
|                         target_lang=None): | ||||
|         self.link(CCompiler.EXECUTABLE, objects, | ||||
|                   self.executable_filename(output_progname), output_dir, | ||||
|                   libraries, library_dirs, runtime_library_dirs, None, | ||||
|                   debug, extra_preargs, extra_postargs, None, target_lang) | ||||
| 
 | ||||
| 
 | ||||
|     # -- Miscellaneous methods ----------------------------------------- | ||||
|     # These are all used by the 'gen_lib_options() function; there is | ||||
|     # no appropriate default implementation so subclasses should | ||||
|     # implement all of these. | ||||
| 
 | ||||
|     def library_dir_option(self, dir): | ||||
|         """Return the compiler option to add 'dir' to the list of | ||||
|         directories searched for libraries. | ||||
|         """ | ||||
|         raise NotImplementedError | ||||
| 
 | ||||
|     def runtime_library_dir_option(self, dir): | ||||
|         """Return the compiler option to add 'dir' to the list of | ||||
|         directories searched for runtime libraries. | ||||
|         """ | ||||
|         raise NotImplementedError | ||||
| 
 | ||||
|     def library_option(self, lib): | ||||
|         """Return the compiler option to add 'dir' to the list of libraries | ||||
|         linked into the shared library or executable. | ||||
|         """ | ||||
|         raise NotImplementedError | ||||
| 
 | ||||
|     def has_function(self, funcname, includes=None, include_dirs=None, | ||||
|                      libraries=None, library_dirs=None): | ||||
|         """Return a boolean indicating whether funcname is supported on | ||||
|         the current platform.  The optional arguments can be used to | ||||
|         augment the compilation environment. | ||||
|         """ | ||||
| 
 | ||||
|         # this can't be included at module scope because it tries to | ||||
|         # import math which might not be available at that point - maybe | ||||
|         # the necessary logic should just be inlined? | ||||
|         import tempfile | ||||
|         if includes is None: | ||||
|             includes = [] | ||||
|         if include_dirs is None: | ||||
|             include_dirs = [] | ||||
|         if libraries is None: | ||||
|             libraries = [] | ||||
|         if library_dirs is None: | ||||
|             library_dirs = [] | ||||
|         fd, fname = tempfile.mkstemp(".c", funcname, text=True) | ||||
|         f = os.fdopen(fd, "w") | ||||
|         try: | ||||
|             for incl in includes: | ||||
|                 f.write("""#include "%s"\n""" % incl) | ||||
|             f.write("""\ | ||||
| main (int argc, char **argv) { | ||||
|     %s(); | ||||
| } | ||||
| """ % funcname) | ||||
|         finally: | ||||
|             f.close() | ||||
|         try: | ||||
|             objects = self.compile([fname], include_dirs=include_dirs) | ||||
|         except CompileError: | ||||
|             return False | ||||
| 
 | ||||
|         try: | ||||
|             self.link_executable(objects, "a.out", | ||||
|                                  libraries=libraries, | ||||
|                                  library_dirs=library_dirs) | ||||
|         except (LinkError, TypeError): | ||||
|             return False | ||||
|         return True | ||||
| 
 | ||||
|     def find_library_file(self, dirs, lib, debug=False): | ||||
|         """Search the specified list of directories for a static or shared | ||||
|         library file 'lib' and return the full path to that file.  If | ||||
|         'debug' is true, look for a debugging version (if that makes sense on | ||||
|         the current platform).  Return None if 'lib' wasn't found in any of | ||||
|         the specified directories. | ||||
|         """ | ||||
|         raise NotImplementedError | ||||
| 
 | ||||
|     # -- Filename generation methods ----------------------------------- | ||||
| 
 | ||||
|     # The default implementation of the filename generating methods are | ||||
|     # prejudiced towards the Unix/DOS/Windows view of the world: | ||||
|     #   * object files are named by replacing the source file extension | ||||
|     #     (eg. .c/.cpp -> .o/.obj) | ||||
|     #   * library files (shared or static) are named by plugging the | ||||
|     #     library name and extension into a format string, eg. | ||||
|     #     "lib%s.%s" % (lib_name, ".a") for Unix static libraries | ||||
|     #   * executables are named by appending an extension (possibly | ||||
|     #     empty) to the program name: eg. progname + ".exe" for | ||||
|     #     Windows | ||||
|     # | ||||
|     # To reduce redundant code, these methods expect to find | ||||
|     # several attributes in the current object (presumably defined | ||||
|     # as class attributes): | ||||
|     #   * src_extensions - | ||||
|     #     list of C/C++ source file extensions, eg. ['.c', '.cpp'] | ||||
|     #   * obj_extension - | ||||
|     #     object file extension, eg. '.o' or '.obj' | ||||
|     #   * static_lib_extension - | ||||
|     #     extension for static library files, eg. '.a' or '.lib' | ||||
|     #   * shared_lib_extension - | ||||
|     #     extension for shared library/object files, eg. '.so', '.dll' | ||||
|     #   * static_lib_format - | ||||
|     #     format string for generating static library filenames, | ||||
|     #     eg. 'lib%s.%s' or '%s.%s' | ||||
|     #   * shared_lib_format | ||||
|     #     format string for generating shared library filenames | ||||
|     #     (probably same as static_lib_format, since the extension | ||||
|     #     is one of the intended parameters to the format string) | ||||
|     #   * exe_extension - | ||||
|     #     extension for executable files, eg. '' or '.exe' | ||||
| 
 | ||||
|     def object_filenames(self, source_filenames, strip_dir=False, output_dir=''): | ||||
|         if output_dir is None: | ||||
|             output_dir = '' | ||||
|         obj_names = [] | ||||
|         for src_name in source_filenames: | ||||
|             base, ext = os.path.splitext(src_name) | ||||
|             base = os.path.splitdrive(base)[1]  # Chop off the drive | ||||
|             base = base[os.path.isabs(base):]  # If abs, chop off leading / | ||||
|             if ext not in self.src_extensions: | ||||
|                 raise UnknownFileError("unknown file type '%s' (from '%s')" % | ||||
|                                        (ext, src_name)) | ||||
|             if strip_dir: | ||||
|                 base = os.path.basename(base) | ||||
|             obj_names.append(os.path.join(output_dir, | ||||
|                                           base + self.obj_extension)) | ||||
|         return obj_names | ||||
| 
 | ||||
|     def shared_object_filename(self, basename, strip_dir=False, output_dir=''): | ||||
|         assert output_dir is not None | ||||
|         if strip_dir: | ||||
|             basename = os.path.basename(basename) | ||||
|         return os.path.join(output_dir, basename + self.shared_lib_extension) | ||||
| 
 | ||||
|     def executable_filename(self, basename, strip_dir=False, output_dir=''): | ||||
|         assert output_dir is not None | ||||
|         if strip_dir: | ||||
|             basename = os.path.basename(basename) | ||||
|         return os.path.join(output_dir, basename + (self.exe_extension or '')) | ||||
| 
 | ||||
|     def library_filename(self, libname, lib_type='static',     # or 'shared' | ||||
|                          strip_dir=False, output_dir=''): | ||||
|         assert output_dir is not None | ||||
|         if lib_type not in ("static", "shared", "dylib"): | ||||
|             raise ValueError( | ||||
|                 "'lib_type' must be 'static', 'shared' or 'dylib'") | ||||
|         fmt = getattr(self, lib_type + "_lib_format") | ||||
|         ext = getattr(self, lib_type + "_lib_extension") | ||||
| 
 | ||||
|         dir, base = os.path.split(libname) | ||||
|         filename = fmt % (base, ext) | ||||
|         if strip_dir: | ||||
|             dir = '' | ||||
| 
 | ||||
|         return os.path.join(output_dir, dir, filename) | ||||
| 
 | ||||
| 
 | ||||
|     # -- Utility methods ----------------------------------------------- | ||||
| 
 | ||||
|     def execute(self, func, args, msg=None, level=1): | ||||
|         execute(func, args, msg, self.dry_run) | ||||
| 
 | ||||
|     def spawn(self, cmd): | ||||
|         spawn(cmd, dry_run=self.dry_run) | ||||
| 
 | ||||
|     def move_file(self, src, dst): | ||||
|         logger.info("moving %r to %r", src, dst) | ||||
|         if self.dry_run: | ||||
|             return | ||||
|         return move(src, dst) | ||||
| 
 | ||||
|     def mkpath(self, name, mode=0o777): | ||||
|         name = os.path.normpath(name) | ||||
|         if os.path.isdir(name) or name == '': | ||||
|             return | ||||
|         if self.dry_run: | ||||
|             head = '' | ||||
|             for part in name.split(os.sep): | ||||
|                 logger.info("created directory %s%s", head, part) | ||||
|                 head += part + os.sep | ||||
|             return | ||||
|         os.makedirs(name, mode) | ||||
							
								
								
									
										355
									
								
								Lib/packaging/compiler/cygwinccompiler.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										355
									
								
								Lib/packaging/compiler/cygwinccompiler.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,355 @@ | |||
| """CCompiler implementations for Cygwin and mingw32 versions of GCC. | ||||
| 
 | ||||
| This module contains the CygwinCCompiler class, a subclass of | ||||
| UnixCCompiler that handles the Cygwin port of the GNU C compiler to | ||||
| Windows, and the Mingw32CCompiler class which handles the mingw32 port | ||||
| of GCC (same as cygwin in no-cygwin mode). | ||||
| """ | ||||
| 
 | ||||
| # problems: | ||||
| # | ||||
| # * if you use a msvc compiled python version (1.5.2) | ||||
| #   1. you have to insert a __GNUC__ section in its config.h | ||||
| #   2. you have to generate a import library for its dll | ||||
| #      - create a def-file for python??.dll | ||||
| #      - create a import library using | ||||
| #             dlltool --dllname python15.dll --def python15.def \ | ||||
| #                       --output-lib libpython15.a | ||||
| # | ||||
| #   see also http://starship.python.net/crew/kernr/mingw32/Notes.html | ||||
| # | ||||
| # * We put export_symbols in a def-file, and don't use | ||||
| #   --export-all-symbols because it doesn't worked reliable in some | ||||
| #   tested configurations. And because other windows compilers also | ||||
| #   need their symbols specified this no serious problem. | ||||
| # | ||||
| # tested configurations: | ||||
| # | ||||
| # * cygwin gcc 2.91.57/ld 2.9.4/dllwrap 0.2.4 works | ||||
| #   (after patching python's config.h and for C++ some other include files) | ||||
| #   see also http://starship.python.net/crew/kernr/mingw32/Notes.html | ||||
| # * mingw32 gcc 2.95.2/ld 2.9.4/dllwrap 0.2.4 works | ||||
| #   (ld doesn't support -shared, so we use dllwrap) | ||||
| # * cygwin gcc 2.95.2/ld 2.10.90/dllwrap 2.10.90 works now | ||||
| #   - its dllwrap doesn't work, there is a bug in binutils 2.10.90 | ||||
| #     see also http://sources.redhat.com/ml/cygwin/2000-06/msg01274.html | ||||
| #   - using gcc -mdll instead dllwrap doesn't work without -static because | ||||
| #     it tries to link against dlls instead their import libraries. (If | ||||
| #     it finds the dll first.) | ||||
| #     By specifying -static we force ld to link against the import libraries, | ||||
| #     this is windows standard and there are normally not the necessary symbols | ||||
| #     in the dlls. | ||||
| #   *** only the version of June 2000 shows these problems | ||||
| # * cygwin gcc 3.2/ld 2.13.90 works | ||||
| #   (ld supports -shared) | ||||
| # * mingw gcc 3.2/ld 2.13 works | ||||
| #   (ld supports -shared) | ||||
| 
 | ||||
| 
 | ||||
| import os | ||||
| import sys | ||||
| import copy | ||||
| 
 | ||||
| from packaging import logger | ||||
| from packaging.compiler.unixccompiler import UnixCCompiler | ||||
| from packaging.util import write_file | ||||
| from packaging.errors import PackagingExecError, CompileError, UnknownFileError | ||||
| from packaging.util import get_compiler_versions | ||||
| import sysconfig | ||||
| 
 | ||||
| 
 | ||||
| def get_msvcr(): | ||||
|     """Include the appropriate MSVC runtime library if Python was built | ||||
|     with MSVC 7.0 or later. | ||||
|     """ | ||||
|     msc_pos = sys.version.find('MSC v.') | ||||
|     if msc_pos != -1: | ||||
|         msc_ver = sys.version[msc_pos+6:msc_pos+10] | ||||
|         if msc_ver == '1300': | ||||
|             # MSVC 7.0 | ||||
|             return ['msvcr70'] | ||||
|         elif msc_ver == '1310': | ||||
|             # MSVC 7.1 | ||||
|             return ['msvcr71'] | ||||
|         elif msc_ver == '1400': | ||||
|             # VS2005 / MSVC 8.0 | ||||
|             return ['msvcr80'] | ||||
|         elif msc_ver == '1500': | ||||
|             # VS2008 / MSVC 9.0 | ||||
|             return ['msvcr90'] | ||||
|         else: | ||||
|             raise ValueError("Unknown MS Compiler version %s " % msc_ver) | ||||
| 
 | ||||
| 
 | ||||
| class CygwinCCompiler(UnixCCompiler): | ||||
|     """ Handles the Cygwin port of the GNU C compiler to Windows. | ||||
|     """ | ||||
|     name = 'cygwin' | ||||
|     description = 'Cygwin port of GNU C Compiler for Win32' | ||||
|     obj_extension = ".o" | ||||
|     static_lib_extension = ".a" | ||||
|     shared_lib_extension = ".dll" | ||||
|     static_lib_format = "lib%s%s" | ||||
|     shared_lib_format = "%s%s" | ||||
|     exe_extension = ".exe" | ||||
| 
 | ||||
|     def __init__(self, verbose=0, dry_run=False, force=False): | ||||
| 
 | ||||
|         UnixCCompiler.__init__(self, verbose, dry_run, force) | ||||
| 
 | ||||
|         status, details = check_config_h() | ||||
|         logger.debug("Python's GCC status: %s (details: %s)", status, details) | ||||
|         if status is not CONFIG_H_OK: | ||||
|             self.warn( | ||||
|                 "Python's pyconfig.h doesn't seem to support your compiler. " | ||||
|                 "Reason: %s. " | ||||
|                 "Compiling may fail because of undefined preprocessor macros." | ||||
|                 % details) | ||||
| 
 | ||||
|         self.gcc_version, self.ld_version, self.dllwrap_version = \ | ||||
|             get_compiler_versions() | ||||
|         logger.debug(self.name + ": gcc %s, ld %s, dllwrap %s\n", | ||||
|                      self.gcc_version, | ||||
|                      self.ld_version, | ||||
|                      self.dllwrap_version) | ||||
| 
 | ||||
|         # ld_version >= "2.10.90" and < "2.13" should also be able to use | ||||
|         # gcc -mdll instead of dllwrap | ||||
|         # Older dllwraps had own version numbers, newer ones use the | ||||
|         # same as the rest of binutils ( also ld ) | ||||
|         # dllwrap 2.10.90 is buggy | ||||
|         if self.ld_version >= "2.10.90": | ||||
|             self.linker_dll = "gcc" | ||||
|         else: | ||||
|             self.linker_dll = "dllwrap" | ||||
| 
 | ||||
|         # ld_version >= "2.13" support -shared so use it instead of | ||||
|         # -mdll -static | ||||
|         if self.ld_version >= "2.13": | ||||
|             shared_option = "-shared" | ||||
|         else: | ||||
|             shared_option = "-mdll -static" | ||||
| 
 | ||||
|         # Hard-code GCC because that's what this is all about. | ||||
|         # XXX optimization, warnings etc. should be customizable. | ||||
|         self.set_executables(compiler='gcc -mcygwin -O -Wall', | ||||
|                              compiler_so='gcc -mcygwin -mdll -O -Wall', | ||||
|                              compiler_cxx='g++ -mcygwin -O -Wall', | ||||
|                              linker_exe='gcc -mcygwin', | ||||
|                              linker_so=('%s -mcygwin %s' % | ||||
|                                         (self.linker_dll, shared_option))) | ||||
| 
 | ||||
|         # cygwin and mingw32 need different sets of libraries | ||||
|         if self.gcc_version == "2.91.57": | ||||
|             # cygwin shouldn't need msvcrt, but without the dlls will crash | ||||
|             # (gcc version 2.91.57) -- perhaps something about initialization | ||||
|             self.dll_libraries=["msvcrt"] | ||||
|             self.warn( | ||||
|                 "Consider upgrading to a newer version of gcc") | ||||
|         else: | ||||
|             # Include the appropriate MSVC runtime library if Python was built | ||||
|             # with MSVC 7.0 or later. | ||||
|             self.dll_libraries = get_msvcr() | ||||
| 
 | ||||
|     def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): | ||||
|         """Compile the source by spawning GCC and windres if needed.""" | ||||
|         if ext == '.rc' or ext == '.res': | ||||
|             # gcc needs '.res' and '.rc' compiled to object files !!! | ||||
|             try: | ||||
|                 self.spawn(["windres", "-i", src, "-o", obj]) | ||||
|             except PackagingExecError as msg: | ||||
|                 raise CompileError(msg) | ||||
|         else: # for other files use the C-compiler | ||||
|             try: | ||||
|                 self.spawn(self.compiler_so + cc_args + [src, '-o', obj] + | ||||
|                            extra_postargs) | ||||
|             except PackagingExecError as msg: | ||||
|                 raise CompileError(msg) | ||||
| 
 | ||||
|     def link(self, target_desc, objects, output_filename, output_dir=None, | ||||
|              libraries=None, library_dirs=None, runtime_library_dirs=None, | ||||
|              export_symbols=None, debug=False, extra_preargs=None, | ||||
|              extra_postargs=None, build_temp=None, target_lang=None): | ||||
|         """Link the objects.""" | ||||
|         # use separate copies, so we can modify the lists | ||||
|         extra_preargs = copy.copy(extra_preargs or []) | ||||
|         libraries = copy.copy(libraries or []) | ||||
|         objects = copy.copy(objects or []) | ||||
| 
 | ||||
|         # Additional libraries | ||||
|         libraries.extend(self.dll_libraries) | ||||
| 
 | ||||
|         # handle export symbols by creating a def-file | ||||
|         # with executables this only works with gcc/ld as linker | ||||
|         if ((export_symbols is not None) and | ||||
|             (target_desc != self.EXECUTABLE or self.linker_dll == "gcc")): | ||||
|             # (The linker doesn't do anything if output is up-to-date. | ||||
|             # So it would probably better to check if we really need this, | ||||
|             # but for this we had to insert some unchanged parts of | ||||
|             # UnixCCompiler, and this is not what we want.) | ||||
| 
 | ||||
|             # we want to put some files in the same directory as the | ||||
|             # object files are, build_temp doesn't help much | ||||
|             # where are the object files | ||||
|             temp_dir = os.path.dirname(objects[0]) | ||||
|             # name of dll to give the helper files the same base name | ||||
|             dll_name, dll_extension = os.path.splitext( | ||||
|                 os.path.basename(output_filename)) | ||||
| 
 | ||||
|             # generate the filenames for these files | ||||
|             def_file = os.path.join(temp_dir, dll_name + ".def") | ||||
|             lib_file = os.path.join(temp_dir, 'lib' + dll_name + ".a") | ||||
| 
 | ||||
|             # Generate .def file | ||||
|             contents = [ | ||||
|                 "LIBRARY %s" % os.path.basename(output_filename), | ||||
|                 "EXPORTS"] | ||||
|             for sym in export_symbols: | ||||
|                 contents.append(sym) | ||||
|             self.execute(write_file, (def_file, contents), | ||||
|                          "writing %s" % def_file) | ||||
| 
 | ||||
|             # next add options for def-file and to creating import libraries | ||||
| 
 | ||||
|             # dllwrap uses different options than gcc/ld | ||||
|             if self.linker_dll == "dllwrap": | ||||
|                 extra_preargs.extend(("--output-lib", lib_file)) | ||||
|                 # for dllwrap we have to use a special option | ||||
|                 extra_preargs.extend(("--def", def_file)) | ||||
|             # we use gcc/ld here and can be sure ld is >= 2.9.10 | ||||
|             else: | ||||
|                 # doesn't work: bfd_close build\...\libfoo.a: Invalid operation | ||||
|                 #extra_preargs.extend(("-Wl,--out-implib,%s" % lib_file)) | ||||
|                 # for gcc/ld the def-file is specified as any object files | ||||
|                 objects.append(def_file) | ||||
| 
 | ||||
|         #end: if ((export_symbols is not None) and | ||||
|         #        (target_desc != self.EXECUTABLE or self.linker_dll == "gcc")): | ||||
| 
 | ||||
|         # who wants symbols and a many times larger output file | ||||
|         # should explicitly switch the debug mode on | ||||
|         # otherwise we let dllwrap/ld strip the output file | ||||
|         # (On my machine: 10KB < stripped_file < ??100KB | ||||
|         #   unstripped_file = stripped_file + XXX KB | ||||
|         #  ( XXX=254 for a typical python extension)) | ||||
|         if not debug: | ||||
|             extra_preargs.append("-s") | ||||
| 
 | ||||
|         UnixCCompiler.link(self, target_desc, objects, output_filename, | ||||
|                            output_dir, libraries, library_dirs, | ||||
|                            runtime_library_dirs, | ||||
|                            None, # export_symbols, we do this in our def-file | ||||
|                            debug, extra_preargs, extra_postargs, build_temp, | ||||
|                            target_lang) | ||||
| 
 | ||||
|     # -- Miscellaneous methods ----------------------------------------- | ||||
| 
 | ||||
|     def object_filenames(self, source_filenames, strip_dir=False, | ||||
|                          output_dir=''): | ||||
|         """Adds supports for rc and res files.""" | ||||
|         if output_dir is None: | ||||
|             output_dir = '' | ||||
|         obj_names = [] | ||||
|         for src_name in source_filenames: | ||||
|             # use normcase to make sure '.rc' is really '.rc' and not '.RC' | ||||
|             base, ext = os.path.splitext(os.path.normcase(src_name)) | ||||
|             if ext not in (self.src_extensions + ['.rc','.res']): | ||||
|                 raise UnknownFileError("unknown file type '%s' (from '%s')" % (ext, src_name)) | ||||
|             if strip_dir: | ||||
|                 base = os.path.basename (base) | ||||
|             if ext in ('.res', '.rc'): | ||||
|                 # these need to be compiled to object files | ||||
|                 obj_names.append (os.path.join(output_dir, | ||||
|                                               base + ext + self.obj_extension)) | ||||
|             else: | ||||
|                 obj_names.append (os.path.join(output_dir, | ||||
|                                                base + self.obj_extension)) | ||||
|         return obj_names | ||||
| 
 | ||||
| # the same as cygwin plus some additional parameters | ||||
| class Mingw32CCompiler(CygwinCCompiler): | ||||
|     """ Handles the Mingw32 port of the GNU C compiler to Windows. | ||||
|     """ | ||||
|     name = 'mingw32' | ||||
|     description = 'MinGW32 compiler' | ||||
| 
 | ||||
|     def __init__(self, verbose=0, dry_run=False, force=False): | ||||
| 
 | ||||
|         CygwinCCompiler.__init__ (self, verbose, dry_run, force) | ||||
| 
 | ||||
|         # ld_version >= "2.13" support -shared so use it instead of | ||||
|         # -mdll -static | ||||
|         if self.ld_version >= "2.13": | ||||
|             shared_option = "-shared" | ||||
|         else: | ||||
|             shared_option = "-mdll -static" | ||||
| 
 | ||||
|         # A real mingw32 doesn't need to specify a different entry point, | ||||
|         # but cygwin 2.91.57 in no-cygwin-mode needs it. | ||||
|         if self.gcc_version <= "2.91.57": | ||||
|             entry_point = '--entry _DllMain@12' | ||||
|         else: | ||||
|             entry_point = '' | ||||
| 
 | ||||
|         self.set_executables(compiler='gcc -mno-cygwin -O -Wall', | ||||
|                              compiler_so='gcc -mno-cygwin -mdll -O -Wall', | ||||
|                              compiler_cxx='g++ -mno-cygwin -O -Wall', | ||||
|                              linker_exe='gcc -mno-cygwin', | ||||
|                              linker_so='%s -mno-cygwin %s %s' | ||||
|                                         % (self.linker_dll, shared_option, | ||||
|                                            entry_point)) | ||||
|         # Maybe we should also append -mthreads, but then the finished | ||||
|         # dlls need another dll (mingwm10.dll see Mingw32 docs) | ||||
|         # (-mthreads: Support thread-safe exception handling on `Mingw32') | ||||
| 
 | ||||
|         # no additional libraries needed | ||||
|         self.dll_libraries=[] | ||||
| 
 | ||||
|         # Include the appropriate MSVC runtime library if Python was built | ||||
|         # with MSVC 7.0 or later. | ||||
|         self.dll_libraries = get_msvcr() | ||||
| 
 | ||||
| # Because these compilers aren't configured in Python's pyconfig.h file by | ||||
| # default, we should at least warn the user if he is using a unmodified | ||||
| # version. | ||||
| 
 | ||||
| CONFIG_H_OK = "ok" | ||||
| CONFIG_H_NOTOK = "not ok" | ||||
| CONFIG_H_UNCERTAIN = "uncertain" | ||||
| 
 | ||||
| def check_config_h(): | ||||
|     """Check if the current Python installation appears amenable to building | ||||
|     extensions with GCC. | ||||
| 
 | ||||
|     Returns a tuple (status, details), where 'status' is one of the following | ||||
|     constants: | ||||
| 
 | ||||
|     - CONFIG_H_OK: all is well, go ahead and compile | ||||
|     - CONFIG_H_NOTOK: doesn't look good | ||||
|     - CONFIG_H_UNCERTAIN: not sure -- unable to read pyconfig.h | ||||
| 
 | ||||
|     'details' is a human-readable string explaining the situation. | ||||
| 
 | ||||
|     Note there are two ways to conclude "OK": either 'sys.version' contains | ||||
|     the string "GCC" (implying that this Python was built with GCC), or the | ||||
|     installed "pyconfig.h" contains the string "__GNUC__". | ||||
|     """ | ||||
| 
 | ||||
|     # XXX since this function also checks sys.version, it's not strictly a | ||||
|     # "pyconfig.h" check -- should probably be renamed... | ||||
|     # if sys.version contains GCC then python was compiled with GCC, and the | ||||
|     # pyconfig.h file should be OK | ||||
|     if "GCC" in sys.version: | ||||
|         return CONFIG_H_OK, "sys.version mentions 'GCC'" | ||||
| 
 | ||||
|     # let's see if __GNUC__ is mentioned in python.h | ||||
|     fn = sysconfig.get_config_h_filename() | ||||
|     try: | ||||
|         with open(fn) as config_h: | ||||
|             if "__GNUC__" in config_h.read(): | ||||
|                 return CONFIG_H_OK, "'%s' mentions '__GNUC__'" % fn | ||||
|             else: | ||||
|                 return CONFIG_H_NOTOK, "'%s' does not mention '__GNUC__'" % fn | ||||
|     except IOError as exc: | ||||
|         return (CONFIG_H_UNCERTAIN, | ||||
|                 "couldn't read '%s': %s" % (fn, exc.strerror)) | ||||
							
								
								
									
										121
									
								
								Lib/packaging/compiler/extension.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								Lib/packaging/compiler/extension.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,121 @@ | |||
| """Class representing C/C++ extension modules.""" | ||||
| 
 | ||||
| from packaging import logger | ||||
| 
 | ||||
| # This class is really only used by the "build_ext" command, so it might | ||||
| # make sense to put it in distutils.command.build_ext.  However, that | ||||
| # module is already big enough, and I want to make this class a bit more | ||||
| # complex to simplify some common cases ("foo" module in "foo.c") and do | ||||
| # better error-checking ("foo.c" actually exists). | ||||
| # | ||||
| # Also, putting this in build_ext.py means every setup script would have to | ||||
| # import that large-ish module (indirectly, through distutils.core) in | ||||
| # order to do anything. | ||||
| 
 | ||||
| 
 | ||||
| class Extension: | ||||
|     """Just a collection of attributes that describes an extension | ||||
|     module and everything needed to build it (hopefully in a portable | ||||
|     way, but there are hooks that let you be as unportable as you need). | ||||
| 
 | ||||
|     Instance attributes: | ||||
|       name : string | ||||
|         the full name of the extension, including any packages -- ie. | ||||
|         *not* a filename or pathname, but Python dotted name | ||||
|       sources : [string] | ||||
|         list of source filenames, relative to the distribution root | ||||
|         (where the setup script lives), in Unix form (slash-separated) | ||||
|         for portability.  Source files may be C, C++, SWIG (.i), | ||||
|         platform-specific resource files, or whatever else is recognized | ||||
|         by the "build_ext" command as source for a Python extension. | ||||
|       include_dirs : [string] | ||||
|         list of directories to search for C/C++ header files (in Unix | ||||
|         form for portability) | ||||
|       define_macros : [(name : string, value : string|None)] | ||||
|         list of macros to define; each macro is defined using a 2-tuple, | ||||
|         where 'value' is either the string to define it to or None to | ||||
|         define it without a particular value (equivalent of "#define | ||||
|         FOO" in source or -DFOO on Unix C compiler command line) | ||||
|       undef_macros : [string] | ||||
|         list of macros to undefine explicitly | ||||
|       library_dirs : [string] | ||||
|         list of directories to search for C/C++ libraries at link time | ||||
|       libraries : [string] | ||||
|         list of library names (not filenames or paths) to link against | ||||
|       runtime_library_dirs : [string] | ||||
|         list of directories to search for C/C++ libraries at run time | ||||
|         (for shared extensions, this is when the extension is loaded) | ||||
|       extra_objects : [string] | ||||
|         list of extra files to link with (eg. object files not implied | ||||
|         by 'sources', static library that must be explicitly specified, | ||||
|         binary resource files, etc.) | ||||
|       extra_compile_args : [string] | ||||
|         any extra platform- and compiler-specific information to use | ||||
|         when compiling the source files in 'sources'.  For platforms and | ||||
|         compilers where "command line" makes sense, this is typically a | ||||
|         list of command-line arguments, but for other platforms it could | ||||
|         be anything. | ||||
|       extra_link_args : [string] | ||||
|         any extra platform- and compiler-specific information to use | ||||
|         when linking object files together to create the extension (or | ||||
|         to create a new static Python interpreter).  Similar | ||||
|         interpretation as for 'extra_compile_args'. | ||||
|       export_symbols : [string] | ||||
|         list of symbols to be exported from a shared extension.  Not | ||||
|         used on all platforms, and not generally necessary for Python | ||||
|         extensions, which typically export exactly one symbol: "init" + | ||||
|         extension_name. | ||||
|       swig_opts : [string] | ||||
|         any extra options to pass to SWIG if a source file has the .i | ||||
|         extension. | ||||
|       depends : [string] | ||||
|         list of files that the extension depends on | ||||
|       language : string | ||||
|         extension language (i.e. "c", "c++", "objc"). Will be detected | ||||
|         from the source extensions if not provided. | ||||
|       optional : boolean | ||||
|         specifies that a build failure in the extension should not abort the | ||||
|         build process, but simply not install the failing extension. | ||||
|     """ | ||||
| 
 | ||||
|     # **kwargs are allowed so that a warning is emitted instead of an | ||||
|     # exception | ||||
|     def __init__(self, name, sources, include_dirs=None, define_macros=None, | ||||
|                  undef_macros=None, library_dirs=None, libraries=None, | ||||
|                  runtime_library_dirs=None, extra_objects=None, | ||||
|                  extra_compile_args=None, extra_link_args=None, | ||||
|                  export_symbols=None, swig_opts=None, depends=None, | ||||
|                  language=None, optional=None, **kw): | ||||
|         if not isinstance(name, str): | ||||
|             raise AssertionError("'name' must be a string") | ||||
| 
 | ||||
|         if not isinstance(sources, list): | ||||
|             raise AssertionError("'sources' must be a list of strings") | ||||
| 
 | ||||
|         for v in sources: | ||||
|             if not isinstance(v, str): | ||||
|                 raise AssertionError("'sources' must be a list of strings") | ||||
| 
 | ||||
|         self.name = name | ||||
|         self.sources = sources | ||||
|         self.include_dirs = include_dirs or [] | ||||
|         self.define_macros = define_macros or [] | ||||
|         self.undef_macros = undef_macros or [] | ||||
|         self.library_dirs = library_dirs or [] | ||||
|         self.libraries = libraries or [] | ||||
|         self.runtime_library_dirs = runtime_library_dirs or [] | ||||
|         self.extra_objects = extra_objects or [] | ||||
|         self.extra_compile_args = extra_compile_args or [] | ||||
|         self.extra_link_args = extra_link_args or [] | ||||
|         self.export_symbols = export_symbols or [] | ||||
|         self.swig_opts = swig_opts or [] | ||||
|         self.depends = depends or [] | ||||
|         self.language = language | ||||
|         self.optional = optional | ||||
| 
 | ||||
|         # If there are unknown keyword options, warn about them | ||||
|         if len(kw) > 0: | ||||
|             options = [repr(option) for option in kw] | ||||
|             options = ', '.join(sorted(options)) | ||||
|             logger.warning( | ||||
|                 'unknown arguments given to Extension: %s', options) | ||||
							
								
								
									
										720
									
								
								Lib/packaging/compiler/msvc9compiler.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										720
									
								
								Lib/packaging/compiler/msvc9compiler.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,720 @@ | |||
| """CCompiler implementation for the Microsoft Visual Studio 2008 compiler. | ||||
| 
 | ||||
| The MSVCCompiler class is compatible with VS 2005 and VS 2008.  Legacy | ||||
| support for older versions of VS are in the msvccompiler module. | ||||
| """ | ||||
| 
 | ||||
| # Written by Perry Stoll | ||||
| # hacked by Robin Becker and Thomas Heller to do a better job of | ||||
| #   finding DevStudio (through the registry) | ||||
| # ported to VS2005 and VS 2008 by Christian Heimes | ||||
| import os | ||||
| import subprocess | ||||
| import sys | ||||
| import re | ||||
| 
 | ||||
| from packaging.errors import (PackagingExecError, PackagingPlatformError, | ||||
|                               CompileError, LibError, LinkError) | ||||
| from packaging.compiler.ccompiler import CCompiler | ||||
| from packaging.compiler import gen_lib_options | ||||
| from packaging import logger | ||||
| from packaging.util import get_platform | ||||
| 
 | ||||
| import winreg | ||||
| 
 | ||||
| RegOpenKeyEx = winreg.OpenKeyEx | ||||
| RegEnumKey = winreg.EnumKey | ||||
| RegEnumValue = winreg.EnumValue | ||||
| RegError = winreg.error | ||||
| 
 | ||||
| HKEYS = (winreg.HKEY_USERS, | ||||
|          winreg.HKEY_CURRENT_USER, | ||||
|          winreg.HKEY_LOCAL_MACHINE, | ||||
|          winreg.HKEY_CLASSES_ROOT) | ||||
| 
 | ||||
| VS_BASE = r"Software\Microsoft\VisualStudio\%0.1f" | ||||
| WINSDK_BASE = r"Software\Microsoft\Microsoft SDKs\Windows" | ||||
| NET_BASE = r"Software\Microsoft\.NETFramework" | ||||
| 
 | ||||
| # A map keyed by get_platform() return values to values accepted by | ||||
| # 'vcvarsall.bat'.  Note a cross-compile may combine these (eg, 'x86_amd64' is | ||||
| # the param to cross-compile on x86 targetting amd64.) | ||||
| PLAT_TO_VCVARS = { | ||||
|     'win32' : 'x86', | ||||
|     'win-amd64' : 'amd64', | ||||
|     'win-ia64' : 'ia64', | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| class Reg: | ||||
|     """Helper class to read values from the registry | ||||
|     """ | ||||
| 
 | ||||
|     def get_value(cls, path, key): | ||||
|         for base in HKEYS: | ||||
|             d = cls.read_values(base, path) | ||||
|             if d and key in d: | ||||
|                 return d[key] | ||||
|         raise KeyError(key) | ||||
|     get_value = classmethod(get_value) | ||||
| 
 | ||||
|     def read_keys(cls, base, key): | ||||
|         """Return list of registry keys.""" | ||||
|         try: | ||||
|             handle = RegOpenKeyEx(base, key) | ||||
|         except RegError: | ||||
|             return None | ||||
|         L = [] | ||||
|         i = 0 | ||||
|         while True: | ||||
|             try: | ||||
|                 k = RegEnumKey(handle, i) | ||||
|             except RegError: | ||||
|                 break | ||||
|             L.append(k) | ||||
|             i += 1 | ||||
|         return L | ||||
|     read_keys = classmethod(read_keys) | ||||
| 
 | ||||
|     def read_values(cls, base, key): | ||||
|         """Return dict of registry keys and values. | ||||
| 
 | ||||
|         All names are converted to lowercase. | ||||
|         """ | ||||
|         try: | ||||
|             handle = RegOpenKeyEx(base, key) | ||||
|         except RegError: | ||||
|             return None | ||||
|         d = {} | ||||
|         i = 0 | ||||
|         while True: | ||||
|             try: | ||||
|                 name, value, type = RegEnumValue(handle, i) | ||||
|             except RegError: | ||||
|                 break | ||||
|             name = name.lower() | ||||
|             d[cls.convert_mbcs(name)] = cls.convert_mbcs(value) | ||||
|             i += 1 | ||||
|         return d | ||||
|     read_values = classmethod(read_values) | ||||
| 
 | ||||
|     def convert_mbcs(s): | ||||
|         dec = getattr(s, "decode", None) | ||||
|         if dec is not None: | ||||
|             try: | ||||
|                 s = dec("mbcs") | ||||
|             except UnicodeError: | ||||
|                 pass | ||||
|         return s | ||||
|     convert_mbcs = staticmethod(convert_mbcs) | ||||
| 
 | ||||
| class MacroExpander: | ||||
| 
 | ||||
|     def __init__(self, version): | ||||
|         self.macros = {} | ||||
|         self.vsbase = VS_BASE % version | ||||
|         self.load_macros(version) | ||||
| 
 | ||||
|     def set_macro(self, macro, path, key): | ||||
|         self.macros["$(%s)" % macro] = Reg.get_value(path, key) | ||||
| 
 | ||||
|     def load_macros(self, version): | ||||
|         self.set_macro("VCInstallDir", self.vsbase + r"\Setup\VC", "productdir") | ||||
|         self.set_macro("VSInstallDir", self.vsbase + r"\Setup\VS", "productdir") | ||||
|         self.set_macro("FrameworkDir", NET_BASE, "installroot") | ||||
|         try: | ||||
|             if version >= 8.0: | ||||
|                 self.set_macro("FrameworkSDKDir", NET_BASE, | ||||
|                                "sdkinstallrootv2.0") | ||||
|             else: | ||||
|                 raise KeyError("sdkinstallrootv2.0") | ||||
|         except KeyError: | ||||
|             raise PackagingPlatformError( | ||||
|             """Python was built with Visual Studio 2008; | ||||
| extensions must be built with a compiler than can generate compatible binaries. | ||||
| Visual Studio 2008 was not found on this system. If you have Cygwin installed, | ||||
| you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""") | ||||
| 
 | ||||
|         if version >= 9.0: | ||||
|             self.set_macro("FrameworkVersion", self.vsbase, "clr version") | ||||
|             self.set_macro("WindowsSdkDir", WINSDK_BASE, "currentinstallfolder") | ||||
|         else: | ||||
|             p = r"Software\Microsoft\NET Framework Setup\Product" | ||||
|             for base in HKEYS: | ||||
|                 try: | ||||
|                     h = RegOpenKeyEx(base, p) | ||||
|                 except RegError: | ||||
|                     continue | ||||
|                 key = RegEnumKey(h, 0) | ||||
|                 d = Reg.get_value(base, r"%s\%s" % (p, key)) | ||||
|                 self.macros["$(FrameworkVersion)"] = d["version"] | ||||
| 
 | ||||
|     def sub(self, s): | ||||
|         for k, v in self.macros.items(): | ||||
|             s = s.replace(k, v) | ||||
|         return s | ||||
| 
 | ||||
| def get_build_version(): | ||||
|     """Return the version of MSVC that was used to build Python. | ||||
| 
 | ||||
|     For Python 2.3 and up, the version number is included in | ||||
|     sys.version.  For earlier versions, assume the compiler is MSVC 6. | ||||
|     """ | ||||
|     prefix = "MSC v." | ||||
|     i = sys.version.find(prefix) | ||||
|     if i == -1: | ||||
|         return 6 | ||||
|     i = i + len(prefix) | ||||
|     s, rest = sys.version[i:].split(" ", 1) | ||||
|     majorVersion = int(s[:-2]) - 6 | ||||
|     minorVersion = int(s[2:3]) / 10.0 | ||||
|     # I don't think paths are affected by minor version in version 6 | ||||
|     if majorVersion == 6: | ||||
|         minorVersion = 0 | ||||
|     if majorVersion >= 6: | ||||
|         return majorVersion + minorVersion | ||||
|     # else we don't know what version of the compiler this is | ||||
|     return None | ||||
| 
 | ||||
| def normalize_and_reduce_paths(paths): | ||||
|     """Return a list of normalized paths with duplicates removed. | ||||
| 
 | ||||
|     The current order of paths is maintained. | ||||
|     """ | ||||
|     # Paths are normalized so things like:  /a and /a/ aren't both preserved. | ||||
|     reduced_paths = [] | ||||
|     for p in paths: | ||||
|         np = os.path.normpath(p) | ||||
|         # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set. | ||||
|         if np not in reduced_paths: | ||||
|             reduced_paths.append(np) | ||||
|     return reduced_paths | ||||
| 
 | ||||
| def removeDuplicates(variable): | ||||
|     """Remove duplicate values of an environment variable. | ||||
|     """ | ||||
|     oldList = variable.split(os.pathsep) | ||||
|     newList = [] | ||||
|     for i in oldList: | ||||
|         if i not in newList: | ||||
|             newList.append(i) | ||||
|     newVariable = os.pathsep.join(newList) | ||||
|     return newVariable | ||||
| 
 | ||||
| def find_vcvarsall(version): | ||||
|     """Find the vcvarsall.bat file | ||||
| 
 | ||||
|     At first it tries to find the productdir of VS 2008 in the registry. If | ||||
|     that fails it falls back to the VS90COMNTOOLS env var. | ||||
|     """ | ||||
|     vsbase = VS_BASE % version | ||||
|     try: | ||||
|         productdir = Reg.get_value(r"%s\Setup\VC" % vsbase, | ||||
|                                    "productdir") | ||||
|     except KeyError: | ||||
|         logger.debug("Unable to find productdir in registry") | ||||
|         productdir = None | ||||
| 
 | ||||
|     if not productdir or not os.path.isdir(productdir): | ||||
|         toolskey = "VS%0.f0COMNTOOLS" % version | ||||
|         toolsdir = os.environ.get(toolskey, None) | ||||
| 
 | ||||
|         if toolsdir and os.path.isdir(toolsdir): | ||||
|             productdir = os.path.join(toolsdir, os.pardir, os.pardir, "VC") | ||||
|             productdir = os.path.abspath(productdir) | ||||
|             if not os.path.isdir(productdir): | ||||
|                 logger.debug("%s is not a valid directory", productdir) | ||||
|                 return None | ||||
|         else: | ||||
|             logger.debug("env var %s is not set or invalid", toolskey) | ||||
|     if not productdir: | ||||
|         logger.debug("no productdir found") | ||||
|         return None | ||||
|     vcvarsall = os.path.join(productdir, "vcvarsall.bat") | ||||
|     if os.path.isfile(vcvarsall): | ||||
|         return vcvarsall | ||||
|     logger.debug("unable to find vcvarsall.bat") | ||||
|     return None | ||||
| 
 | ||||
| def query_vcvarsall(version, arch="x86"): | ||||
|     """Launch vcvarsall.bat and read the settings from its environment | ||||
|     """ | ||||
|     vcvarsall = find_vcvarsall(version) | ||||
|     interesting = set(("include", "lib", "libpath", "path")) | ||||
|     result = {} | ||||
| 
 | ||||
|     if vcvarsall is None: | ||||
|         raise PackagingPlatformError("Unable to find vcvarsall.bat") | ||||
|     logger.debug("calling 'vcvarsall.bat %s' (version=%s)", arch, version) | ||||
|     popen = subprocess.Popen('"%s" %s & set' % (vcvarsall, arch), | ||||
|                              stdout=subprocess.PIPE, | ||||
|                              stderr=subprocess.PIPE) | ||||
| 
 | ||||
|     stdout, stderr = popen.communicate() | ||||
|     if popen.wait() != 0: | ||||
|         raise PackagingPlatformError(stderr.decode("mbcs")) | ||||
| 
 | ||||
|     stdout = stdout.decode("mbcs") | ||||
|     for line in stdout.split("\n"): | ||||
|         line = Reg.convert_mbcs(line) | ||||
|         if '=' not in line: | ||||
|             continue | ||||
|         line = line.strip() | ||||
|         key, value = line.split('=', 1) | ||||
|         key = key.lower() | ||||
|         if key in interesting: | ||||
|             if value.endswith(os.pathsep): | ||||
|                 value = value[:-1] | ||||
|             result[key] = removeDuplicates(value) | ||||
| 
 | ||||
|     if len(result) != len(interesting): | ||||
|         raise ValueError(str(list(result))) | ||||
| 
 | ||||
|     return result | ||||
| 
 | ||||
| # More globals | ||||
| VERSION = get_build_version() | ||||
| if VERSION < 8.0: | ||||
|     raise PackagingPlatformError("VC %0.1f is not supported by this module" % VERSION) | ||||
| # MACROS = MacroExpander(VERSION) | ||||
| 
 | ||||
| class MSVCCompiler(CCompiler) : | ||||
|     """Concrete class that implements an interface to Microsoft Visual C++, | ||||
|        as defined by the CCompiler abstract class.""" | ||||
| 
 | ||||
|     name = 'msvc' | ||||
|     description = 'Microsoft Visual C++' | ||||
| 
 | ||||
|     # Just set this so CCompiler's constructor doesn't barf.  We currently | ||||
|     # don't use the 'set_executables()' bureaucracy provided by CCompiler, | ||||
|     # as it really isn't necessary for this sort of single-compiler class. | ||||
|     # Would be nice to have a consistent interface with UnixCCompiler, | ||||
|     # though, so it's worth thinking about. | ||||
|     executables = {} | ||||
| 
 | ||||
|     # Private class data (need to distinguish C from C++ source for compiler) | ||||
|     _c_extensions = ['.c'] | ||||
|     _cpp_extensions = ['.cc', '.cpp', '.cxx'] | ||||
|     _rc_extensions = ['.rc'] | ||||
|     _mc_extensions = ['.mc'] | ||||
| 
 | ||||
|     # Needed for the filename generation methods provided by the | ||||
|     # base class, CCompiler. | ||||
|     src_extensions = (_c_extensions + _cpp_extensions + | ||||
|                       _rc_extensions + _mc_extensions) | ||||
|     res_extension = '.res' | ||||
|     obj_extension = '.obj' | ||||
|     static_lib_extension = '.lib' | ||||
|     shared_lib_extension = '.dll' | ||||
|     static_lib_format = shared_lib_format = '%s%s' | ||||
|     exe_extension = '.exe' | ||||
| 
 | ||||
|     def __init__(self, verbose=0, dry_run=False, force=False): | ||||
|         CCompiler.__init__(self, verbose, dry_run, force) | ||||
|         self.__version = VERSION | ||||
|         self.__root = r"Software\Microsoft\VisualStudio" | ||||
|         # self.__macros = MACROS | ||||
|         self.__paths = [] | ||||
|         # target platform (.plat_name is consistent with 'bdist') | ||||
|         self.plat_name = None | ||||
|         self.__arch = None # deprecated name | ||||
|         self.initialized = False | ||||
| 
 | ||||
|     def initialize(self, plat_name=None): | ||||
|         # multi-init means we would need to check platform same each time... | ||||
|         assert not self.initialized, "don't init multiple times" | ||||
|         if plat_name is None: | ||||
|             plat_name = get_platform() | ||||
|         # sanity check for platforms to prevent obscure errors later. | ||||
|         ok_plats = 'win32', 'win-amd64', 'win-ia64' | ||||
|         if plat_name not in ok_plats: | ||||
|             raise PackagingPlatformError("--plat-name must be one of %s" % | ||||
|                                          (ok_plats,)) | ||||
| 
 | ||||
|         if "DISTUTILS_USE_SDK" in os.environ and "MSSdk" in os.environ and self.find_exe("cl.exe"): | ||||
|             # Assume that the SDK set up everything alright; don't try to be | ||||
|             # smarter | ||||
|             self.cc = "cl.exe" | ||||
|             self.linker = "link.exe" | ||||
|             self.lib = "lib.exe" | ||||
|             self.rc = "rc.exe" | ||||
|             self.mc = "mc.exe" | ||||
|         else: | ||||
|             # On x86, 'vcvars32.bat amd64' creates an env that doesn't work; | ||||
|             # to cross compile, you use 'x86_amd64'. | ||||
|             # On AMD64, 'vcvars32.bat amd64' is a native build env; to cross | ||||
|             # compile use 'x86' (ie, it runs the x86 compiler directly) | ||||
|             # No idea how itanium handles this, if at all. | ||||
|             if plat_name == get_platform() or plat_name == 'win32': | ||||
|                 # native build or cross-compile to win32 | ||||
|                 plat_spec = PLAT_TO_VCVARS[plat_name] | ||||
|             else: | ||||
|                 # cross compile from win32 -> some 64bit | ||||
|                 plat_spec = PLAT_TO_VCVARS[get_platform()] + '_' + \ | ||||
|                             PLAT_TO_VCVARS[plat_name] | ||||
| 
 | ||||
|             vc_env = query_vcvarsall(VERSION, plat_spec) | ||||
| 
 | ||||
|             # take care to only use strings in the environment. | ||||
|             self.__paths = vc_env['path'].encode('mbcs').split(os.pathsep) | ||||
|             os.environ['lib'] = vc_env['lib'].encode('mbcs') | ||||
|             os.environ['include'] = vc_env['include'].encode('mbcs') | ||||
| 
 | ||||
|             if len(self.__paths) == 0: | ||||
|                 raise PackagingPlatformError("Python was built with %s, " | ||||
|                        "and extensions need to be built with the same " | ||||
|                        "version of the compiler, but it isn't installed." | ||||
|                        % self.__product) | ||||
| 
 | ||||
|             self.cc = self.find_exe("cl.exe") | ||||
|             self.linker = self.find_exe("link.exe") | ||||
|             self.lib = self.find_exe("lib.exe") | ||||
|             self.rc = self.find_exe("rc.exe")   # resource compiler | ||||
|             self.mc = self.find_exe("mc.exe")   # message compiler | ||||
|             #self.set_path_env_var('lib') | ||||
|             #self.set_path_env_var('include') | ||||
| 
 | ||||
|         # extend the MSVC path with the current path | ||||
|         try: | ||||
|             for p in os.environ['path'].split(';'): | ||||
|                 self.__paths.append(p) | ||||
|         except KeyError: | ||||
|             pass | ||||
|         self.__paths = normalize_and_reduce_paths(self.__paths) | ||||
|         os.environ['path'] = ";".join(self.__paths) | ||||
| 
 | ||||
|         self.preprocess_options = None | ||||
|         if self.__arch == "x86": | ||||
|             self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', | ||||
|                                      '/DNDEBUG'] | ||||
|             self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', | ||||
|                                           '/Z7', '/D_DEBUG'] | ||||
|         else: | ||||
|             # Win64 | ||||
|             self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', '/GS-' , | ||||
|                                      '/DNDEBUG'] | ||||
|             self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GS-', | ||||
|                                           '/Z7', '/D_DEBUG'] | ||||
| 
 | ||||
|         self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO'] | ||||
|         if self.__version >= 7: | ||||
|             self.ldflags_shared_debug = [ | ||||
|                 '/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG', '/pdb:None' | ||||
|                 ] | ||||
|         self.ldflags_static = [ '/nologo'] | ||||
| 
 | ||||
|         self.initialized = True | ||||
| 
 | ||||
|     # -- Worker methods ------------------------------------------------ | ||||
| 
 | ||||
|     def object_filenames(self, | ||||
|                          source_filenames, | ||||
|                          strip_dir=False, | ||||
|                          output_dir=''): | ||||
|         # Copied from ccompiler.py, extended to return .res as 'object'-file | ||||
|         # for .rc input file | ||||
|         if output_dir is None: output_dir = '' | ||||
|         obj_names = [] | ||||
|         for src_name in source_filenames: | ||||
|             base, ext = os.path.splitext(src_name) | ||||
|             base = os.path.splitdrive(base)[1] # Chop off the drive | ||||
|             base = base[os.path.isabs(base):]  # If abs, chop off leading / | ||||
|             if ext not in self.src_extensions: | ||||
|                 # Better to raise an exception instead of silently continuing | ||||
|                 # and later complain about sources and targets having | ||||
|                 # different lengths | ||||
|                 raise CompileError("Don't know how to compile %s" % src_name) | ||||
|             if strip_dir: | ||||
|                 base = os.path.basename(base) | ||||
|             if ext in self._rc_extensions: | ||||
|                 obj_names.append(os.path.join(output_dir, | ||||
|                                               base + self.res_extension)) | ||||
|             elif ext in self._mc_extensions: | ||||
|                 obj_names.append(os.path.join(output_dir, | ||||
|                                               base + self.res_extension)) | ||||
|             else: | ||||
|                 obj_names.append(os.path.join(output_dir, | ||||
|                                               base + self.obj_extension)) | ||||
|         return obj_names | ||||
| 
 | ||||
| 
 | ||||
|     def compile(self, sources, | ||||
|                 output_dir=None, macros=None, include_dirs=None, debug=False, | ||||
|                 extra_preargs=None, extra_postargs=None, depends=None): | ||||
| 
 | ||||
|         if not self.initialized: | ||||
|             self.initialize() | ||||
|         compile_info = self._setup_compile(output_dir, macros, include_dirs, | ||||
|                                            sources, depends, extra_postargs) | ||||
|         macros, objects, extra_postargs, pp_opts, build = compile_info | ||||
| 
 | ||||
|         compile_opts = extra_preargs or [] | ||||
|         compile_opts.append('/c') | ||||
|         if debug: | ||||
|             compile_opts.extend(self.compile_options_debug) | ||||
|         else: | ||||
|             compile_opts.extend(self.compile_options) | ||||
| 
 | ||||
|         for obj in objects: | ||||
|             try: | ||||
|                 src, ext = build[obj] | ||||
|             except KeyError: | ||||
|                 continue | ||||
|             if debug: | ||||
|                 # pass the full pathname to MSVC in debug mode, | ||||
|                 # this allows the debugger to find the source file | ||||
|                 # without asking the user to browse for it | ||||
|                 src = os.path.abspath(src) | ||||
| 
 | ||||
|             if ext in self._c_extensions: | ||||
|                 input_opt = "/Tc" + src | ||||
|             elif ext in self._cpp_extensions: | ||||
|                 input_opt = "/Tp" + src | ||||
|             elif ext in self._rc_extensions: | ||||
|                 # compile .RC to .RES file | ||||
|                 input_opt = src | ||||
|                 output_opt = "/fo" + obj | ||||
|                 try: | ||||
|                     self.spawn([self.rc] + pp_opts + | ||||
|                                [output_opt] + [input_opt]) | ||||
|                 except PackagingExecError as msg: | ||||
|                     raise CompileError(msg) | ||||
|                 continue | ||||
|             elif ext in self._mc_extensions: | ||||
|                 # Compile .MC to .RC file to .RES file. | ||||
|                 #   * '-h dir' specifies the directory for the | ||||
|                 #     generated include file | ||||
|                 #   * '-r dir' specifies the target directory of the | ||||
|                 #     generated RC file and the binary message resource | ||||
|                 #     it includes | ||||
|                 # | ||||
|                 # For now (since there are no options to change this), | ||||
|                 # we use the source-directory for the include file and | ||||
|                 # the build directory for the RC file and message | ||||
|                 # resources. This works at least for win32all. | ||||
|                 h_dir = os.path.dirname(src) | ||||
|                 rc_dir = os.path.dirname(obj) | ||||
|                 try: | ||||
|                     # first compile .MC to .RC and .H file | ||||
|                     self.spawn([self.mc] + | ||||
|                                ['-h', h_dir, '-r', rc_dir] + [src]) | ||||
|                     base, _ = os.path.splitext(os.path.basename(src)) | ||||
|                     rc_file = os.path.join(rc_dir, base + '.rc') | ||||
|                     # then compile .RC to .RES file | ||||
|                     self.spawn([self.rc] + | ||||
|                                ["/fo" + obj] + [rc_file]) | ||||
| 
 | ||||
|                 except PackagingExecError as msg: | ||||
|                     raise CompileError(msg) | ||||
|                 continue | ||||
|             else: | ||||
|                 # how to handle this file? | ||||
|                 raise CompileError("Don't know how to compile %s to %s" | ||||
|                                    % (src, obj)) | ||||
| 
 | ||||
|             output_opt = "/Fo" + obj | ||||
|             try: | ||||
|                 self.spawn([self.cc] + compile_opts + pp_opts + | ||||
|                            [input_opt, output_opt] + | ||||
|                            extra_postargs) | ||||
|             except PackagingExecError as msg: | ||||
|                 raise CompileError(msg) | ||||
| 
 | ||||
|         return objects | ||||
| 
 | ||||
| 
 | ||||
|     def create_static_lib(self, | ||||
|                           objects, | ||||
|                           output_libname, | ||||
|                           output_dir=None, | ||||
|                           debug=False, | ||||
|                           target_lang=None): | ||||
| 
 | ||||
|         if not self.initialized: | ||||
|             self.initialize() | ||||
|         objects, output_dir = self._fix_object_args(objects, output_dir) | ||||
|         output_filename = self.library_filename(output_libname, | ||||
|                                                 output_dir=output_dir) | ||||
| 
 | ||||
|         if self._need_link(objects, output_filename): | ||||
|             lib_args = objects + ['/OUT:' + output_filename] | ||||
|             if debug: | ||||
|                 pass # XXX what goes here? | ||||
|             try: | ||||
|                 self.spawn([self.lib] + lib_args) | ||||
|             except PackagingExecError as msg: | ||||
|                 raise LibError(msg) | ||||
|         else: | ||||
|             logger.debug("skipping %s (up-to-date)", output_filename) | ||||
| 
 | ||||
| 
 | ||||
|     def link(self, target_desc, objects, output_filename, output_dir=None, | ||||
|              libraries=None, library_dirs=None, runtime_library_dirs=None, | ||||
|              export_symbols=None, debug=False, extra_preargs=None, | ||||
|              extra_postargs=None, build_temp=None, target_lang=None): | ||||
|         if not self.initialized: | ||||
|             self.initialize() | ||||
|         objects, output_dir = self._fix_object_args(objects, output_dir) | ||||
|         fixed_args = self._fix_lib_args(libraries, library_dirs, | ||||
|                                         runtime_library_dirs) | ||||
|         libraries, library_dirs, runtime_library_dirs = fixed_args | ||||
| 
 | ||||
|         if runtime_library_dirs: | ||||
|             self.warn("don't know what to do with 'runtime_library_dirs': " | ||||
|                       + str(runtime_library_dirs)) | ||||
| 
 | ||||
|         lib_opts = gen_lib_options(self, | ||||
|                                    library_dirs, runtime_library_dirs, | ||||
|                                    libraries) | ||||
|         if output_dir is not None: | ||||
|             output_filename = os.path.join(output_dir, output_filename) | ||||
| 
 | ||||
|         if self._need_link(objects, output_filename): | ||||
|             if target_desc == CCompiler.EXECUTABLE: | ||||
|                 if debug: | ||||
|                     ldflags = self.ldflags_shared_debug[1:] | ||||
|                 else: | ||||
|                     ldflags = self.ldflags_shared[1:] | ||||
|             else: | ||||
|                 if debug: | ||||
|                     ldflags = self.ldflags_shared_debug | ||||
|                 else: | ||||
|                     ldflags = self.ldflags_shared | ||||
| 
 | ||||
|             export_opts = [] | ||||
|             for sym in (export_symbols or []): | ||||
|                 export_opts.append("/EXPORT:" + sym) | ||||
| 
 | ||||
|             ld_args = (ldflags + lib_opts + export_opts + | ||||
|                        objects + ['/OUT:' + output_filename]) | ||||
| 
 | ||||
|             # The MSVC linker generates .lib and .exp files, which cannot be | ||||
|             # suppressed by any linker switches. The .lib files may even be | ||||
|             # needed! Make sure they are generated in the temporary build | ||||
|             # directory. Since they have different names for debug and release | ||||
|             # builds, they can go into the same directory. | ||||
|             build_temp = os.path.dirname(objects[0]) | ||||
|             if export_symbols is not None: | ||||
|                 dll_name, dll_ext = os.path.splitext( | ||||
|                     os.path.basename(output_filename)) | ||||
|                 implib_file = os.path.join( | ||||
|                     build_temp, | ||||
|                     self.library_filename(dll_name)) | ||||
|                 ld_args.append('/IMPLIB:' + implib_file) | ||||
| 
 | ||||
|             # Embedded manifests are recommended - see MSDN article titled | ||||
|             # "How to: Embed a Manifest Inside a C/C++ Application" | ||||
|             # (currently at http://msdn2.microsoft.com/en-us/library/ms235591(VS.80).aspx) | ||||
|             # Ask the linker to generate the manifest in the temp dir, so | ||||
|             # we can embed it later. | ||||
|             temp_manifest = os.path.join( | ||||
|                     build_temp, | ||||
|                     os.path.basename(output_filename) + ".manifest") | ||||
|             ld_args.append('/MANIFESTFILE:' + temp_manifest) | ||||
| 
 | ||||
|             if extra_preargs: | ||||
|                 ld_args[:0] = extra_preargs | ||||
|             if extra_postargs: | ||||
|                 ld_args.extend(extra_postargs) | ||||
| 
 | ||||
|             self.mkpath(os.path.dirname(output_filename)) | ||||
|             try: | ||||
|                 self.spawn([self.linker] + ld_args) | ||||
|             except PackagingExecError as msg: | ||||
|                 raise LinkError(msg) | ||||
| 
 | ||||
|             # embed the manifest | ||||
|             # XXX - this is somewhat fragile - if mt.exe fails, distutils | ||||
|             # will still consider the DLL up-to-date, but it will not have a | ||||
|             # manifest.  Maybe we should link to a temp file?  OTOH, that | ||||
|             # implies a build environment error that shouldn't go undetected. | ||||
|             if target_desc == CCompiler.EXECUTABLE: | ||||
|                 mfid = 1 | ||||
|             else: | ||||
|                 mfid = 2 | ||||
|                 self._remove_visual_c_ref(temp_manifest) | ||||
|             out_arg = '-outputresource:%s;%s' % (output_filename, mfid) | ||||
|             try: | ||||
|                 self.spawn(['mt.exe', '-nologo', '-manifest', | ||||
|                             temp_manifest, out_arg]) | ||||
|             except PackagingExecError as msg: | ||||
|                 raise LinkError(msg) | ||||
|         else: | ||||
|             logger.debug("skipping %s (up-to-date)", output_filename) | ||||
| 
 | ||||
|     def _remove_visual_c_ref(self, manifest_file): | ||||
|         try: | ||||
|             # Remove references to the Visual C runtime, so they will | ||||
|             # fall through to the Visual C dependency of Python.exe. | ||||
|             # This way, when installed for a restricted user (e.g. | ||||
|             # runtimes are not in WinSxS folder, but in Python's own | ||||
|             # folder), the runtimes do not need to be in every folder | ||||
|             # with .pyd's. | ||||
|             with open(manifest_file) as manifest_f: | ||||
|                 manifest_buf = manifest_f.read() | ||||
|             pattern = re.compile( | ||||
|                 r"""<assemblyIdentity.*?name=("|')Microsoft\."""\ | ||||
|                 r"""VC\d{2}\.CRT("|').*?(/>|</assemblyIdentity>)""", | ||||
|                 re.DOTALL) | ||||
|             manifest_buf = re.sub(pattern, "", manifest_buf) | ||||
|             pattern = "<dependentAssembly>\s*</dependentAssembly>" | ||||
|             manifest_buf = re.sub(pattern, "", manifest_buf) | ||||
|             with open(manifest_file, 'w') as manifest_f: | ||||
|                 manifest_f.write(manifest_buf) | ||||
|         except IOError: | ||||
|             pass | ||||
| 
 | ||||
|     # -- Miscellaneous methods ----------------------------------------- | ||||
|     # These are all used by the 'gen_lib_options() function, in | ||||
|     # ccompiler.py. | ||||
| 
 | ||||
|     def library_dir_option(self, dir): | ||||
|         return "/LIBPATH:" + dir | ||||
| 
 | ||||
|     def runtime_library_dir_option(self, dir): | ||||
|         raise PackagingPlatformError( | ||||
|               "don't know how to set runtime library search path for MSVC++") | ||||
| 
 | ||||
|     def library_option(self, lib): | ||||
|         return self.library_filename(lib) | ||||
| 
 | ||||
| 
 | ||||
|     def find_library_file(self, dirs, lib, debug=False): | ||||
|         # Prefer a debugging library if found (and requested), but deal | ||||
|         # with it if we don't have one. | ||||
|         if debug: | ||||
|             try_names = [lib + "_d", lib] | ||||
|         else: | ||||
|             try_names = [lib] | ||||
|         for dir in dirs: | ||||
|             for name in try_names: | ||||
|                 libfile = os.path.join(dir, self.library_filename(name)) | ||||
|                 if os.path.exists(libfile): | ||||
|                     return libfile | ||||
|         else: | ||||
|             # Oops, didn't find it in *any* of 'dirs' | ||||
|             return None | ||||
| 
 | ||||
|     # Helper methods for using the MSVC registry settings | ||||
| 
 | ||||
|     def find_exe(self, exe): | ||||
|         """Return path to an MSVC executable program. | ||||
| 
 | ||||
|         Tries to find the program in several places: first, one of the | ||||
|         MSVC program search paths from the registry; next, the directories | ||||
|         in the PATH environment variable.  If any of those work, return an | ||||
|         absolute path that is known to exist.  If none of them work, just | ||||
|         return the original program name, 'exe'. | ||||
|         """ | ||||
|         for p in self.__paths: | ||||
|             fn = os.path.join(os.path.abspath(p), exe) | ||||
|             if os.path.isfile(fn): | ||||
|                 return fn | ||||
| 
 | ||||
|         # didn't find it; try existing path | ||||
|         for p in os.environ['Path'].split(';'): | ||||
|             fn = os.path.join(os.path.abspath(p),exe) | ||||
|             if os.path.isfile(fn): | ||||
|                 return fn | ||||
| 
 | ||||
|         return exe | ||||
							
								
								
									
										636
									
								
								Lib/packaging/compiler/msvccompiler.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										636
									
								
								Lib/packaging/compiler/msvccompiler.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,636 @@ | |||
| """CCompiler implementation for old Microsoft Visual Studio compilers. | ||||
| 
 | ||||
| For a compiler compatible with VS 2005 and 2008, use msvc9compiler. | ||||
| """ | ||||
| 
 | ||||
| # Written by Perry Stoll | ||||
| # hacked by Robin Becker and Thomas Heller to do a better job of | ||||
| #   finding DevStudio (through the registry) | ||||
| 
 | ||||
| 
 | ||||
| import sys | ||||
| import os | ||||
| 
 | ||||
| from packaging.errors import (PackagingExecError, PackagingPlatformError, | ||||
|                               CompileError, LibError, LinkError) | ||||
| from packaging.compiler.ccompiler import CCompiler | ||||
| from packaging.compiler import gen_lib_options | ||||
| from packaging import logger | ||||
| 
 | ||||
| _can_read_reg = False | ||||
| try: | ||||
|     import winreg | ||||
| 
 | ||||
|     _can_read_reg = True | ||||
|     hkey_mod = winreg | ||||
| 
 | ||||
|     RegOpenKeyEx = winreg.OpenKeyEx | ||||
|     RegEnumKey = winreg.EnumKey | ||||
|     RegEnumValue = winreg.EnumValue | ||||
|     RegError = winreg.error | ||||
| 
 | ||||
| except ImportError: | ||||
|     try: | ||||
|         import win32api | ||||
|         import win32con | ||||
|         _can_read_reg = True | ||||
|         hkey_mod = win32con | ||||
| 
 | ||||
|         RegOpenKeyEx = win32api.RegOpenKeyEx | ||||
|         RegEnumKey = win32api.RegEnumKey | ||||
|         RegEnumValue = win32api.RegEnumValue | ||||
|         RegError = win32api.error | ||||
| 
 | ||||
|     except ImportError: | ||||
|         logger.warning( | ||||
|             "can't read registry to find the necessary compiler setting;\n" | ||||
|             "make sure that Python modules _winreg, win32api or win32con " | ||||
|             "are installed.") | ||||
| 
 | ||||
| if _can_read_reg: | ||||
|     HKEYS = (hkey_mod.HKEY_USERS, | ||||
|              hkey_mod.HKEY_CURRENT_USER, | ||||
|              hkey_mod.HKEY_LOCAL_MACHINE, | ||||
|              hkey_mod.HKEY_CLASSES_ROOT) | ||||
| 
 | ||||
| 
 | ||||
| def read_keys(base, key): | ||||
|     """Return list of registry keys.""" | ||||
| 
 | ||||
|     try: | ||||
|         handle = RegOpenKeyEx(base, key) | ||||
|     except RegError: | ||||
|         return None | ||||
|     L = [] | ||||
|     i = 0 | ||||
|     while True: | ||||
|         try: | ||||
|             k = RegEnumKey(handle, i) | ||||
|         except RegError: | ||||
|             break | ||||
|         L.append(k) | ||||
|         i = i + 1 | ||||
|     return L | ||||
| 
 | ||||
| 
 | ||||
| def read_values(base, key): | ||||
|     """Return dict of registry keys and values. | ||||
| 
 | ||||
|     All names are converted to lowercase. | ||||
|     """ | ||||
|     try: | ||||
|         handle = RegOpenKeyEx(base, key) | ||||
|     except RegError: | ||||
|         return None | ||||
|     d = {} | ||||
|     i = 0 | ||||
|     while True: | ||||
|         try: | ||||
|             name, value, type = RegEnumValue(handle, i) | ||||
|         except RegError: | ||||
|             break | ||||
|         name = name.lower() | ||||
|         d[convert_mbcs(name)] = convert_mbcs(value) | ||||
|         i = i + 1 | ||||
|     return d | ||||
| 
 | ||||
| 
 | ||||
| def convert_mbcs(s): | ||||
|     enc = getattr(s, "encode", None) | ||||
|     if enc is not None: | ||||
|         try: | ||||
|             s = enc("mbcs") | ||||
|         except UnicodeError: | ||||
|             pass | ||||
|     return s | ||||
| 
 | ||||
| 
 | ||||
| class MacroExpander: | ||||
| 
 | ||||
|     def __init__(self, version): | ||||
|         self.macros = {} | ||||
|         self.load_macros(version) | ||||
| 
 | ||||
|     def set_macro(self, macro, path, key): | ||||
|         for base in HKEYS: | ||||
|             d = read_values(base, path) | ||||
|             if d: | ||||
|                 self.macros["$(%s)" % macro] = d[key] | ||||
|                 break | ||||
| 
 | ||||
|     def load_macros(self, version): | ||||
|         vsbase = r"Software\Microsoft\VisualStudio\%0.1f" % version | ||||
|         self.set_macro("VCInstallDir", vsbase + r"\Setup\VC", "productdir") | ||||
|         self.set_macro("VSInstallDir", vsbase + r"\Setup\VS", "productdir") | ||||
|         net = r"Software\Microsoft\.NETFramework" | ||||
|         self.set_macro("FrameworkDir", net, "installroot") | ||||
|         try: | ||||
|             if version > 7.0: | ||||
|                 self.set_macro("FrameworkSDKDir", net, "sdkinstallrootv1.1") | ||||
|             else: | ||||
|                 self.set_macro("FrameworkSDKDir", net, "sdkinstallroot") | ||||
|         except KeyError: | ||||
|             raise PackagingPlatformError( | ||||
| """Python was built with Visual Studio 2003; extensions must be built with | ||||
| a compiler than can generate compatible binaries. Visual Studio 2003 was | ||||
| not found on this system. If you have Cygwin installed, you can try | ||||
| compiling with MingW32, by passing "-c mingw32" to setup.py.""") | ||||
| # XXX update this comment for setup.cfg | ||||
| 
 | ||||
|         p = r"Software\Microsoft\NET Framework Setup\Product" | ||||
|         for base in HKEYS: | ||||
|             try: | ||||
|                 h = RegOpenKeyEx(base, p) | ||||
|             except RegError: | ||||
|                 continue | ||||
|             key = RegEnumKey(h, 0) | ||||
|             d = read_values(base, r"%s\%s" % (p, key)) | ||||
|             self.macros["$(FrameworkVersion)"] = d["version"] | ||||
| 
 | ||||
|     def sub(self, s): | ||||
|         for k, v in self.macros.items(): | ||||
|             s = s.replace(k, v) | ||||
|         return s | ||||
| 
 | ||||
| 
 | ||||
| def get_build_version(): | ||||
|     """Return the version of MSVC that was used to build Python. | ||||
| 
 | ||||
|     For Python 2.3 and up, the version number is included in | ||||
|     sys.version.  For earlier versions, assume the compiler is MSVC 6. | ||||
|     """ | ||||
| 
 | ||||
|     prefix = "MSC v." | ||||
|     i = sys.version.find(prefix) | ||||
|     if i == -1: | ||||
|         return 6 | ||||
|     i = i + len(prefix) | ||||
|     s, rest = sys.version[i:].split(" ", 1) | ||||
|     majorVersion = int(s[:-2]) - 6 | ||||
|     minorVersion = int(s[2:3]) / 10.0 | ||||
|     # I don't think paths are affected by minor version in version 6 | ||||
|     if majorVersion == 6: | ||||
|         minorVersion = 0 | ||||
|     if majorVersion >= 6: | ||||
|         return majorVersion + minorVersion | ||||
|     # else we don't know what version of the compiler this is | ||||
|     return None | ||||
| 
 | ||||
| 
 | ||||
| def get_build_architecture(): | ||||
|     """Return the processor architecture. | ||||
| 
 | ||||
|     Possible results are "Intel", "Itanium", or "AMD64". | ||||
|     """ | ||||
| 
 | ||||
|     prefix = " bit (" | ||||
|     i = sys.version.find(prefix) | ||||
|     if i == -1: | ||||
|         return "Intel" | ||||
|     j = sys.version.find(")", i) | ||||
|     return sys.version[i+len(prefix):j] | ||||
| 
 | ||||
| 
 | ||||
| def normalize_and_reduce_paths(paths): | ||||
|     """Return a list of normalized paths with duplicates removed. | ||||
| 
 | ||||
|     The current order of paths is maintained. | ||||
|     """ | ||||
|     # Paths are normalized so things like:  /a and /a/ aren't both preserved. | ||||
|     reduced_paths = [] | ||||
|     for p in paths: | ||||
|         np = os.path.normpath(p) | ||||
|         # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set. | ||||
|         if np not in reduced_paths: | ||||
|             reduced_paths.append(np) | ||||
|     return reduced_paths | ||||
| 
 | ||||
| 
 | ||||
| class MSVCCompiler(CCompiler): | ||||
|     """Concrete class that implements an interface to Microsoft Visual C++, | ||||
|        as defined by the CCompiler abstract class.""" | ||||
| 
 | ||||
|     name = 'msvc' | ||||
|     description = "Microsoft Visual C++" | ||||
| 
 | ||||
|     # Just set this so CCompiler's constructor doesn't barf.  We currently | ||||
|     # don't use the 'set_executables()' bureaucracy provided by CCompiler, | ||||
|     # as it really isn't necessary for this sort of single-compiler class. | ||||
|     # Would be nice to have a consistent interface with UnixCCompiler, | ||||
|     # though, so it's worth thinking about. | ||||
|     executables = {} | ||||
| 
 | ||||
|     # Private class data (need to distinguish C from C++ source for compiler) | ||||
|     _c_extensions = ['.c'] | ||||
|     _cpp_extensions = ['.cc', '.cpp', '.cxx'] | ||||
|     _rc_extensions = ['.rc'] | ||||
|     _mc_extensions = ['.mc'] | ||||
| 
 | ||||
|     # Needed for the filename generation methods provided by the | ||||
|     # base class, CCompiler. | ||||
|     src_extensions = (_c_extensions + _cpp_extensions + | ||||
|                       _rc_extensions + _mc_extensions) | ||||
|     res_extension = '.res' | ||||
|     obj_extension = '.obj' | ||||
|     static_lib_extension = '.lib' | ||||
|     shared_lib_extension = '.dll' | ||||
|     static_lib_format = shared_lib_format = '%s%s' | ||||
|     exe_extension = '.exe' | ||||
| 
 | ||||
|     def __init__(self, verbose=0, dry_run=False, force=False): | ||||
|         CCompiler.__init__(self, verbose, dry_run, force) | ||||
|         self.__version = get_build_version() | ||||
|         self.__arch = get_build_architecture() | ||||
|         if self.__arch == "Intel": | ||||
|             # x86 | ||||
|             if self.__version >= 7: | ||||
|                 self.__root = r"Software\Microsoft\VisualStudio" | ||||
|                 self.__macros = MacroExpander(self.__version) | ||||
|             else: | ||||
|                 self.__root = r"Software\Microsoft\Devstudio" | ||||
|             self.__product = "Visual Studio version %s" % self.__version | ||||
|         else: | ||||
|             # Win64. Assume this was built with the platform SDK | ||||
|             self.__product = "Microsoft SDK compiler %s" % (self.__version + 6) | ||||
| 
 | ||||
|         self.initialized = False | ||||
| 
 | ||||
|     def initialize(self): | ||||
|         self.__paths = [] | ||||
|         if ("DISTUTILS_USE_SDK" in os.environ and "MSSdk" in os.environ and | ||||
|             self.find_exe("cl.exe")): | ||||
|             # Assume that the SDK set up everything alright; don't try to be | ||||
|             # smarter | ||||
|             self.cc = "cl.exe" | ||||
|             self.linker = "link.exe" | ||||
|             self.lib = "lib.exe" | ||||
|             self.rc = "rc.exe" | ||||
|             self.mc = "mc.exe" | ||||
|         else: | ||||
|             self.__paths = self.get_msvc_paths("path") | ||||
| 
 | ||||
|             if len(self.__paths) == 0: | ||||
|                 raise PackagingPlatformError("Python was built with %s " | ||||
|                     "and extensions need to be built with the same " | ||||
|                     "version of the compiler, but it isn't installed." % | ||||
|                     self.__product) | ||||
| 
 | ||||
|             self.cc = self.find_exe("cl.exe") | ||||
|             self.linker = self.find_exe("link.exe") | ||||
|             self.lib = self.find_exe("lib.exe") | ||||
|             self.rc = self.find_exe("rc.exe")   # resource compiler | ||||
|             self.mc = self.find_exe("mc.exe")   # message compiler | ||||
|             self.set_path_env_var('lib') | ||||
|             self.set_path_env_var('include') | ||||
| 
 | ||||
|         # extend the MSVC path with the current path | ||||
|         try: | ||||
|             for p in os.environ['path'].split(';'): | ||||
|                 self.__paths.append(p) | ||||
|         except KeyError: | ||||
|             pass | ||||
|         self.__paths = normalize_and_reduce_paths(self.__paths) | ||||
|         os.environ['path'] = ';'.join(self.__paths) | ||||
| 
 | ||||
|         self.preprocess_options = None | ||||
|         if self.__arch == "Intel": | ||||
|             self.compile_options = ['/nologo', '/Ox', '/MD', '/W3', '/GX', | ||||
|                                     '/DNDEBUG'] | ||||
|             self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GX', | ||||
|                                           '/Z7', '/D_DEBUG'] | ||||
|         else: | ||||
|             # Win64 | ||||
|             self.compile_options = ['/nologo', '/Ox', '/MD', '/W3', '/GS-', | ||||
|                                     '/DNDEBUG'] | ||||
|             self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GS-', | ||||
|                                           '/Z7', '/D_DEBUG'] | ||||
| 
 | ||||
|         self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO'] | ||||
|         if self.__version >= 7: | ||||
|             self.ldflags_shared_debug = [ | ||||
|                 '/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG' | ||||
|                 ] | ||||
|         else: | ||||
|             self.ldflags_shared_debug = [ | ||||
|                 '/DLL', '/nologo', '/INCREMENTAL:no', '/pdb:None', '/DEBUG' | ||||
|                 ] | ||||
|         self.ldflags_static = [ '/nologo'] | ||||
| 
 | ||||
|         self.initialized = True | ||||
| 
 | ||||
|     # -- Worker methods ------------------------------------------------ | ||||
| 
 | ||||
|     def object_filenames(self, source_filenames, strip_dir=False, output_dir=''): | ||||
|         # Copied from ccompiler.py, extended to return .res as 'object'-file | ||||
|         # for .rc input file | ||||
|         if output_dir is None: | ||||
|             output_dir = '' | ||||
|         obj_names = [] | ||||
|         for src_name in source_filenames: | ||||
|             base, ext = os.path.splitext(src_name) | ||||
|             base = os.path.splitdrive(base)[1]  # Chop off the drive | ||||
|             base = base[os.path.isabs(base):]  # If abs, chop off leading / | ||||
|             if ext not in self.src_extensions: | ||||
|                 # Better to raise an exception instead of silently continuing | ||||
|                 # and later complain about sources and targets having | ||||
|                 # different lengths | ||||
|                 raise CompileError("Don't know how to compile %s" % src_name) | ||||
|             if strip_dir: | ||||
|                 base = os.path.basename(base) | ||||
|             if ext in self._rc_extensions: | ||||
|                 obj_names.append(os.path.join(output_dir, | ||||
|                                               base + self.res_extension)) | ||||
|             elif ext in self._mc_extensions: | ||||
|                 obj_names.append(os.path.join(output_dir, | ||||
|                                               base + self.res_extension)) | ||||
|             else: | ||||
|                 obj_names.append(os.path.join(output_dir, | ||||
|                                               base + self.obj_extension)) | ||||
|         return obj_names | ||||
| 
 | ||||
|     def compile(self, sources, | ||||
|                 output_dir=None, macros=None, include_dirs=None, debug=False, | ||||
|                 extra_preargs=None, extra_postargs=None, depends=None): | ||||
| 
 | ||||
|         if not self.initialized: | ||||
|             self.initialize() | ||||
|         macros, objects, extra_postargs, pp_opts, build = \ | ||||
|                 self._setup_compile(output_dir, macros, include_dirs, sources, | ||||
|                                     depends, extra_postargs) | ||||
| 
 | ||||
|         compile_opts = extra_preargs or [] | ||||
|         compile_opts.append('/c') | ||||
|         if debug: | ||||
|             compile_opts.extend(self.compile_options_debug) | ||||
|         else: | ||||
|             compile_opts.extend(self.compile_options) | ||||
| 
 | ||||
|         for obj in objects: | ||||
|             try: | ||||
|                 src, ext = build[obj] | ||||
|             except KeyError: | ||||
|                 continue | ||||
|             if debug: | ||||
|                 # pass the full pathname to MSVC in debug mode, | ||||
|                 # this allows the debugger to find the source file | ||||
|                 # without asking the user to browse for it | ||||
|                 src = os.path.abspath(src) | ||||
| 
 | ||||
|             if ext in self._c_extensions: | ||||
|                 input_opt = "/Tc" + src | ||||
|             elif ext in self._cpp_extensions: | ||||
|                 input_opt = "/Tp" + src | ||||
|             elif ext in self._rc_extensions: | ||||
|                 # compile .RC to .RES file | ||||
|                 input_opt = src | ||||
|                 output_opt = "/fo" + obj | ||||
|                 try: | ||||
|                     self.spawn([self.rc] + pp_opts + | ||||
|                                [output_opt] + [input_opt]) | ||||
|                 except PackagingExecError as msg: | ||||
|                     raise CompileError(msg) | ||||
|                 continue | ||||
|             elif ext in self._mc_extensions: | ||||
| 
 | ||||
|                 # Compile .MC to .RC file to .RES file. | ||||
|                 #   * '-h dir' specifies the directory for the | ||||
|                 #     generated include file | ||||
|                 #   * '-r dir' specifies the target directory of the | ||||
|                 #     generated RC file and the binary message resource | ||||
|                 #     it includes | ||||
|                 # | ||||
|                 # For now (since there are no options to change this), | ||||
|                 # we use the source-directory for the include file and | ||||
|                 # the build directory for the RC file and message | ||||
|                 # resources. This works at least for win32all. | ||||
| 
 | ||||
|                 h_dir = os.path.dirname(src) | ||||
|                 rc_dir = os.path.dirname(obj) | ||||
|                 try: | ||||
|                     # first compile .MC to .RC and .H file | ||||
|                     self.spawn([self.mc] + | ||||
|                                ['-h', h_dir, '-r', rc_dir] + [src]) | ||||
|                     base, _ = os.path.splitext(os.path.basename(src)) | ||||
|                     rc_file = os.path.join(rc_dir, base + '.rc') | ||||
|                     # then compile .RC to .RES file | ||||
|                     self.spawn([self.rc] + | ||||
|                                 ["/fo" + obj] + [rc_file]) | ||||
| 
 | ||||
|                 except PackagingExecError as msg: | ||||
|                     raise CompileError(msg) | ||||
|                 continue | ||||
|             else: | ||||
|                 # how to handle this file? | ||||
|                 raise CompileError( | ||||
|                     "Don't know how to compile %s to %s" % | ||||
|                     (src, obj)) | ||||
| 
 | ||||
|             output_opt = "/Fo" + obj | ||||
|             try: | ||||
|                 self.spawn([self.cc] + compile_opts + pp_opts + | ||||
|                            [input_opt, output_opt] + | ||||
|                            extra_postargs) | ||||
|             except PackagingExecError as msg: | ||||
|                 raise CompileError(msg) | ||||
| 
 | ||||
|         return objects | ||||
| 
 | ||||
|     def create_static_lib(self, objects, output_libname, output_dir=None, | ||||
|                           debug=False, target_lang=None): | ||||
|         if not self.initialized: | ||||
|             self.initialize() | ||||
|         objects, output_dir = self._fix_object_args(objects, output_dir) | ||||
|         output_filename = \ | ||||
|             self.library_filename(output_libname, output_dir=output_dir) | ||||
| 
 | ||||
|         if self._need_link(objects, output_filename): | ||||
|             lib_args = objects + ['/OUT:' + output_filename] | ||||
|             if debug: | ||||
|                 pass                    # XXX what goes here? | ||||
|             try: | ||||
|                 self.spawn([self.lib] + lib_args) | ||||
|             except PackagingExecError as msg: | ||||
|                 raise LibError(msg) | ||||
| 
 | ||||
|         else: | ||||
|             logger.debug("skipping %s (up-to-date)", output_filename) | ||||
| 
 | ||||
|     def link(self, target_desc, objects, output_filename, output_dir=None, | ||||
|              libraries=None, library_dirs=None, runtime_library_dirs=None, | ||||
|              export_symbols=None, debug=False, extra_preargs=None, | ||||
|              extra_postargs=None, build_temp=None, target_lang=None): | ||||
| 
 | ||||
|         if not self.initialized: | ||||
|             self.initialize() | ||||
|         objects, output_dir = self._fix_object_args(objects, output_dir) | ||||
|         libraries, library_dirs, runtime_library_dirs = \ | ||||
|             self._fix_lib_args(libraries, library_dirs, runtime_library_dirs) | ||||
| 
 | ||||
|         if runtime_library_dirs: | ||||
|             self.warn("don't know what to do with 'runtime_library_dirs': %s" | ||||
|                       % (runtime_library_dirs,)) | ||||
| 
 | ||||
|         lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, | ||||
|                                    libraries) | ||||
|         if output_dir is not None: | ||||
|             output_filename = os.path.join(output_dir, output_filename) | ||||
| 
 | ||||
|         if self._need_link(objects, output_filename): | ||||
| 
 | ||||
|             if target_desc == CCompiler.EXECUTABLE: | ||||
|                 if debug: | ||||
|                     ldflags = self.ldflags_shared_debug[1:] | ||||
|                 else: | ||||
|                     ldflags = self.ldflags_shared[1:] | ||||
|             else: | ||||
|                 if debug: | ||||
|                     ldflags = self.ldflags_shared_debug | ||||
|                 else: | ||||
|                     ldflags = self.ldflags_shared | ||||
| 
 | ||||
|             export_opts = [] | ||||
|             for sym in (export_symbols or []): | ||||
|                 export_opts.append("/EXPORT:" + sym) | ||||
| 
 | ||||
|             ld_args = (ldflags + lib_opts + export_opts + | ||||
|                        objects + ['/OUT:' + output_filename]) | ||||
| 
 | ||||
|             # The MSVC linker generates .lib and .exp files, which cannot be | ||||
|             # suppressed by any linker switches. The .lib files may even be | ||||
|             # needed! Make sure they are generated in the temporary build | ||||
|             # directory. Since they have different names for debug and release | ||||
|             # builds, they can go into the same directory. | ||||
|             if export_symbols is not None: | ||||
|                 dll_name, dll_ext = os.path.splitext( | ||||
|                     os.path.basename(output_filename)) | ||||
|                 implib_file = os.path.join( | ||||
|                     os.path.dirname(objects[0]), | ||||
|                     self.library_filename(dll_name)) | ||||
|                 ld_args.append('/IMPLIB:' + implib_file) | ||||
| 
 | ||||
|             if extra_preargs: | ||||
|                 ld_args[:0] = extra_preargs | ||||
|             if extra_postargs: | ||||
|                 ld_args.extend(extra_postargs) | ||||
| 
 | ||||
|             self.mkpath(os.path.dirname(output_filename)) | ||||
|             try: | ||||
|                 self.spawn([self.linker] + ld_args) | ||||
|             except PackagingExecError as msg: | ||||
|                 raise LinkError(msg) | ||||
| 
 | ||||
|         else: | ||||
|             logger.debug("skipping %s (up-to-date)", output_filename) | ||||
| 
 | ||||
|     # -- Miscellaneous methods ----------------------------------------- | ||||
|     # These are all used by the 'gen_lib_options() function, in | ||||
|     # ccompiler.py. | ||||
| 
 | ||||
|     def library_dir_option(self, dir): | ||||
|         return "/LIBPATH:" + dir | ||||
| 
 | ||||
|     def runtime_library_dir_option(self, dir): | ||||
|         raise PackagingPlatformError("don't know how to set runtime library search path for MSVC++") | ||||
| 
 | ||||
|     def library_option(self, lib): | ||||
|         return self.library_filename(lib) | ||||
| 
 | ||||
|     def find_library_file(self, dirs, lib, debug=False): | ||||
|         # Prefer a debugging library if found (and requested), but deal | ||||
|         # with it if we don't have one. | ||||
|         if debug: | ||||
|             try_names = [lib + "_d", lib] | ||||
|         else: | ||||
|             try_names = [lib] | ||||
|         for dir in dirs: | ||||
|             for name in try_names: | ||||
|                 libfile = os.path.join(dir, self.library_filename(name)) | ||||
|                 if os.path.exists(libfile): | ||||
|                     return libfile | ||||
|         else: | ||||
|             # Oops, didn't find it in *any* of 'dirs' | ||||
|             return None | ||||
| 
 | ||||
|     # Helper methods for using the MSVC registry settings | ||||
| 
 | ||||
|     def find_exe(self, exe): | ||||
|         """Return path to an MSVC executable program. | ||||
| 
 | ||||
|         Tries to find the program in several places: first, one of the | ||||
|         MSVC program search paths from the registry; next, the directories | ||||
|         in the PATH environment variable.  If any of those work, return an | ||||
|         absolute path that is known to exist.  If none of them work, just | ||||
|         return the original program name, 'exe'. | ||||
|         """ | ||||
| 
 | ||||
|         for p in self.__paths: | ||||
|             fn = os.path.join(os.path.abspath(p), exe) | ||||
|             if os.path.isfile(fn): | ||||
|                 return fn | ||||
| 
 | ||||
|         # didn't find it; try existing path | ||||
|         for p in os.environ['Path'].split(';'): | ||||
|             fn = os.path.join(os.path.abspath(p), exe) | ||||
|             if os.path.isfile(fn): | ||||
|                 return fn | ||||
| 
 | ||||
|         return exe | ||||
| 
 | ||||
|     def get_msvc_paths(self, path, platform='x86'): | ||||
|         """Get a list of devstudio directories (include, lib or path). | ||||
| 
 | ||||
|         Return a list of strings.  The list will be empty if unable to | ||||
|         access the registry or appropriate registry keys not found. | ||||
|         """ | ||||
| 
 | ||||
|         if not _can_read_reg: | ||||
|             return [] | ||||
| 
 | ||||
|         path = path + " dirs" | ||||
|         if self.__version >= 7: | ||||
|             key = (r"%s\%0.1f\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories" | ||||
|                    % (self.__root, self.__version)) | ||||
|         else: | ||||
|             key = (r"%s\6.0\Build System\Components\Platforms" | ||||
|                    r"\Win32 (%s)\Directories" % (self.__root, platform)) | ||||
| 
 | ||||
|         for base in HKEYS: | ||||
|             d = read_values(base, key) | ||||
|             if d: | ||||
|                 if self.__version >= 7: | ||||
|                     return self.__macros.sub(d[path]).split(";") | ||||
|                 else: | ||||
|                     return d[path].split(";") | ||||
|         # MSVC 6 seems to create the registry entries we need only when | ||||
|         # the GUI is run. | ||||
|         if self.__version == 6: | ||||
|             for base in HKEYS: | ||||
|                 if read_values(base, r"%s\6.0" % self.__root) is not None: | ||||
|                     self.warn("It seems you have Visual Studio 6 installed, " | ||||
|                         "but the expected registry settings are not present.\n" | ||||
|                         "You must at least run the Visual Studio GUI once " | ||||
|                         "so that these entries are created.") | ||||
|                     break | ||||
|         return [] | ||||
| 
 | ||||
|     def set_path_env_var(self, name): | ||||
|         """Set environment variable 'name' to an MSVC path type value. | ||||
| 
 | ||||
|         This is equivalent to a SET command prior to execution of spawned | ||||
|         commands. | ||||
|         """ | ||||
| 
 | ||||
|         if name == "lib": | ||||
|             p = self.get_msvc_paths("library") | ||||
|         else: | ||||
|             p = self.get_msvc_paths(name) | ||||
|         if p: | ||||
|             os.environ[name] = ';'.join(p) | ||||
| 
 | ||||
| 
 | ||||
| if get_build_version() >= 8.0: | ||||
|     logger.debug("importing new compiler from distutils.msvc9compiler") | ||||
|     OldMSVCCompiler = MSVCCompiler | ||||
|     from packaging.compiler.msvc9compiler import MSVCCompiler | ||||
|     # get_build_architecture not really relevant now we support cross-compile | ||||
|     from packaging.compiler.msvc9compiler import MacroExpander | ||||
							
								
								
									
										339
									
								
								Lib/packaging/compiler/unixccompiler.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										339
									
								
								Lib/packaging/compiler/unixccompiler.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,339 @@ | |||
| """CCompiler implementation for Unix compilers. | ||||
| 
 | ||||
| This module contains the UnixCCompiler class, a subclass of CCompiler | ||||
| that handles the "typical" Unix-style command-line C compiler: | ||||
|   * macros defined with -Dname[=value] | ||||
|   * macros undefined with -Uname | ||||
|   * include search directories specified with -Idir | ||||
|   * libraries specified with -lllib | ||||
|   * library search directories specified with -Ldir | ||||
|   * compile handled by 'cc' (or similar) executable with -c option: | ||||
|     compiles .c to .o | ||||
|   * link static library handled by 'ar' command (possibly with 'ranlib') | ||||
|   * link shared library handled by 'cc -shared' | ||||
| """ | ||||
| 
 | ||||
| import os, sys | ||||
| 
 | ||||
| from packaging.util import newer | ||||
| from packaging.compiler.ccompiler import CCompiler | ||||
| from packaging.compiler import gen_preprocess_options, gen_lib_options | ||||
| from packaging.errors import (PackagingExecError, CompileError, | ||||
|                                LibError, LinkError) | ||||
| from packaging import logger | ||||
| import sysconfig | ||||
| 
 | ||||
| 
 | ||||
| # XXX Things not currently handled: | ||||
| #   * optimization/debug/warning flags; we just use whatever's in Python's | ||||
| #     Makefile and live with it.  Is this adequate?  If not, we might | ||||
| #     have to have a bunch of subclasses GNUCCompiler, SGICCompiler, | ||||
| #     SunCCompiler, and I suspect down that road lies madness. | ||||
| #   * even if we don't know a warning flag from an optimization flag, | ||||
| #     we need some way for outsiders to feed preprocessor/compiler/linker | ||||
| #     flags in to us -- eg. a sysadmin might want to mandate certain flags | ||||
| #     via a site config file, or a user might want to set something for | ||||
| #     compiling this module distribution only via the setup.py command | ||||
| #     line, whatever.  As long as these options come from something on the | ||||
| #     current system, they can be as system-dependent as they like, and we | ||||
| #     should just happily stuff them into the preprocessor/compiler/linker | ||||
| #     options and carry on. | ||||
| 
 | ||||
| def _darwin_compiler_fixup(compiler_so, cc_args): | ||||
|     """ | ||||
|     This function will strip '-isysroot PATH' and '-arch ARCH' from the | ||||
|     compile flags if the user has specified one them in extra_compile_flags. | ||||
| 
 | ||||
|     This is needed because '-arch ARCH' adds another architecture to the | ||||
|     build, without a way to remove an architecture. Furthermore GCC will | ||||
|     barf if multiple '-isysroot' arguments are present. | ||||
|     """ | ||||
|     stripArch = stripSysroot = False | ||||
| 
 | ||||
|     compiler_so = list(compiler_so) | ||||
|     kernel_version = os.uname()[2] # 8.4.3 | ||||
|     major_version = int(kernel_version.split('.')[0]) | ||||
| 
 | ||||
|     if major_version < 8: | ||||
|         # OSX before 10.4.0, these don't support -arch and -isysroot at | ||||
|         # all. | ||||
|         stripArch = stripSysroot = True | ||||
|     else: | ||||
|         stripArch = '-arch' in cc_args | ||||
|         stripSysroot = '-isysroot' in cc_args | ||||
| 
 | ||||
|     if stripArch or 'ARCHFLAGS' in os.environ: | ||||
|         while True: | ||||
|             try: | ||||
|                 index = compiler_so.index('-arch') | ||||
|                 # Strip this argument and the next one: | ||||
|                 del compiler_so[index:index+2] | ||||
|             except ValueError: | ||||
|                 break | ||||
| 
 | ||||
|     if 'ARCHFLAGS' in os.environ and not stripArch: | ||||
|         # User specified different -arch flags in the environ, | ||||
|         # see also the sysconfig | ||||
|         compiler_so = compiler_so + os.environ['ARCHFLAGS'].split() | ||||
| 
 | ||||
|     if stripSysroot: | ||||
|         try: | ||||
|             index = compiler_so.index('-isysroot') | ||||
|             # Strip this argument and the next one: | ||||
|             del compiler_so[index:index+2] | ||||
|         except ValueError: | ||||
|             pass | ||||
| 
 | ||||
|     # Check if the SDK that is used during compilation actually exists, | ||||
|     # the universal build requires the usage of a universal SDK and not all | ||||
|     # users have that installed by default. | ||||
|     sysroot = None | ||||
|     if '-isysroot' in cc_args: | ||||
|         idx = cc_args.index('-isysroot') | ||||
|         sysroot = cc_args[idx+1] | ||||
|     elif '-isysroot' in compiler_so: | ||||
|         idx = compiler_so.index('-isysroot') | ||||
|         sysroot = compiler_so[idx+1] | ||||
| 
 | ||||
|     if sysroot and not os.path.isdir(sysroot): | ||||
|         logger.warning( | ||||
|             "compiling with an SDK that doesn't seem to exist: %r;\n" | ||||
|             "please check your Xcode installation", sysroot) | ||||
| 
 | ||||
|     return compiler_so | ||||
| 
 | ||||
| class UnixCCompiler(CCompiler): | ||||
| 
 | ||||
|     name = 'unix' | ||||
|     description = 'Standard UNIX-style compiler' | ||||
| 
 | ||||
|     # These are used by CCompiler in two places: the constructor sets | ||||
|     # instance attributes 'preprocessor', 'compiler', etc. from them, and | ||||
|     # 'set_executable()' allows any of these to be set.  The defaults here | ||||
|     # are pretty generic; they will probably have to be set by an outsider | ||||
|     # (eg. using information discovered by the sysconfig about building | ||||
|     # Python extensions). | ||||
|     executables = {'preprocessor' : None, | ||||
|                    'compiler'     : ["cc"], | ||||
|                    'compiler_so'  : ["cc"], | ||||
|                    'compiler_cxx' : ["cc"], | ||||
|                    'linker_so'    : ["cc", "-shared"], | ||||
|                    'linker_exe'   : ["cc"], | ||||
|                    'archiver'     : ["ar", "-cr"], | ||||
|                    'ranlib'       : None, | ||||
|                   } | ||||
| 
 | ||||
|     if sys.platform[:6] == "darwin": | ||||
|         executables['ranlib'] = ["ranlib"] | ||||
| 
 | ||||
|     # Needed for the filename generation methods provided by the base | ||||
|     # class, CCompiler.  NB. whoever instantiates/uses a particular | ||||
|     # UnixCCompiler instance should set 'shared_lib_ext' -- we set a | ||||
|     # reasonable common default here, but it's not necessarily used on all | ||||
|     # Unices! | ||||
| 
 | ||||
|     src_extensions = [".c",".C",".cc",".cxx",".cpp",".m"] | ||||
|     obj_extension = ".o" | ||||
|     static_lib_extension = ".a" | ||||
|     shared_lib_extension = ".so" | ||||
|     dylib_lib_extension = ".dylib" | ||||
|     static_lib_format = shared_lib_format = dylib_lib_format = "lib%s%s" | ||||
|     if sys.platform == "cygwin": | ||||
|         exe_extension = ".exe" | ||||
| 
 | ||||
|     def preprocess(self, source, | ||||
|                    output_file=None, macros=None, include_dirs=None, | ||||
|                    extra_preargs=None, extra_postargs=None): | ||||
|         ignore, macros, include_dirs = \ | ||||
|             self._fix_compile_args(None, macros, include_dirs) | ||||
|         pp_opts = gen_preprocess_options(macros, include_dirs) | ||||
|         pp_args = self.preprocessor + pp_opts | ||||
|         if output_file: | ||||
|             pp_args.extend(('-o', output_file)) | ||||
|         if extra_preargs: | ||||
|             pp_args[:0] = extra_preargs | ||||
|         if extra_postargs: | ||||
|             pp_args.extend(extra_postargs) | ||||
|         pp_args.append(source) | ||||
| 
 | ||||
|         # We need to preprocess: either we're being forced to, or we're | ||||
|         # generating output to stdout, or there's a target output file and | ||||
|         # the source file is newer than the target (or the target doesn't | ||||
|         # exist). | ||||
|         if self.force or output_file is None or newer(source, output_file): | ||||
|             if output_file: | ||||
|                 self.mkpath(os.path.dirname(output_file)) | ||||
|             try: | ||||
|                 self.spawn(pp_args) | ||||
|             except PackagingExecError as msg: | ||||
|                 raise CompileError(msg) | ||||
| 
 | ||||
|     def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): | ||||
|         compiler_so = self.compiler_so | ||||
|         if sys.platform == 'darwin': | ||||
|             compiler_so = _darwin_compiler_fixup(compiler_so, cc_args + extra_postargs) | ||||
|         try: | ||||
|             self.spawn(compiler_so + cc_args + [src, '-o', obj] + | ||||
|                        extra_postargs) | ||||
|         except PackagingExecError as msg: | ||||
|             raise CompileError(msg) | ||||
| 
 | ||||
|     def create_static_lib(self, objects, output_libname, | ||||
|                           output_dir=None, debug=False, target_lang=None): | ||||
|         objects, output_dir = self._fix_object_args(objects, output_dir) | ||||
| 
 | ||||
|         output_filename = \ | ||||
|             self.library_filename(output_libname, output_dir=output_dir) | ||||
| 
 | ||||
|         if self._need_link(objects, output_filename): | ||||
|             self.mkpath(os.path.dirname(output_filename)) | ||||
|             self.spawn(self.archiver + | ||||
|                        [output_filename] + | ||||
|                        objects + self.objects) | ||||
| 
 | ||||
|             # Not many Unices required ranlib anymore -- SunOS 4.x is, I | ||||
|             # think the only major Unix that does.  Maybe we need some | ||||
|             # platform intelligence here to skip ranlib if it's not | ||||
|             # needed -- or maybe Python's configure script took care of | ||||
|             # it for us, hence the check for leading colon. | ||||
|             if self.ranlib: | ||||
|                 try: | ||||
|                     self.spawn(self.ranlib + [output_filename]) | ||||
|                 except PackagingExecError as msg: | ||||
|                     raise LibError(msg) | ||||
|         else: | ||||
|             logger.debug("skipping %s (up-to-date)", output_filename) | ||||
| 
 | ||||
|     def link(self, target_desc, objects, | ||||
|              output_filename, output_dir=None, libraries=None, | ||||
|              library_dirs=None, runtime_library_dirs=None, | ||||
|              export_symbols=None, debug=False, extra_preargs=None, | ||||
|              extra_postargs=None, build_temp=None, target_lang=None): | ||||
|         objects, output_dir = self._fix_object_args(objects, output_dir) | ||||
|         libraries, library_dirs, runtime_library_dirs = \ | ||||
|             self._fix_lib_args(libraries, library_dirs, runtime_library_dirs) | ||||
| 
 | ||||
|         lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, | ||||
|                                    libraries) | ||||
|         if type(output_dir) not in (str, type(None)): | ||||
|             raise TypeError("'output_dir' must be a string or None") | ||||
|         if output_dir is not None: | ||||
|             output_filename = os.path.join(output_dir, output_filename) | ||||
| 
 | ||||
|         if self._need_link(objects, output_filename): | ||||
|             ld_args = (objects + self.objects + | ||||
|                        lib_opts + ['-o', output_filename]) | ||||
|             if debug: | ||||
|                 ld_args[:0] = ['-g'] | ||||
|             if extra_preargs: | ||||
|                 ld_args[:0] = extra_preargs | ||||
|             if extra_postargs: | ||||
|                 ld_args.extend(extra_postargs) | ||||
|             self.mkpath(os.path.dirname(output_filename)) | ||||
|             try: | ||||
|                 if target_desc == CCompiler.EXECUTABLE: | ||||
|                     linker = self.linker_exe[:] | ||||
|                 else: | ||||
|                     linker = self.linker_so[:] | ||||
|                 if target_lang == "c++" and self.compiler_cxx: | ||||
|                     # skip over environment variable settings if /usr/bin/env | ||||
|                     # is used to set up the linker's environment. | ||||
|                     # This is needed on OSX. Note: this assumes that the | ||||
|                     # normal and C++ compiler have the same environment | ||||
|                     # settings. | ||||
|                     i = 0 | ||||
|                     if os.path.basename(linker[0]) == "env": | ||||
|                         i = 1 | ||||
|                         while '=' in linker[i]: | ||||
|                             i = i + 1 | ||||
| 
 | ||||
|                     linker[i] = self.compiler_cxx[i] | ||||
| 
 | ||||
|                 if sys.platform == 'darwin': | ||||
|                     linker = _darwin_compiler_fixup(linker, ld_args) | ||||
| 
 | ||||
|                 self.spawn(linker + ld_args) | ||||
|             except PackagingExecError as msg: | ||||
|                 raise LinkError(msg) | ||||
|         else: | ||||
|             logger.debug("skipping %s (up-to-date)", output_filename) | ||||
| 
 | ||||
|     # -- Miscellaneous methods ----------------------------------------- | ||||
|     # These are all used by the 'gen_lib_options() function, in | ||||
|     # ccompiler.py. | ||||
| 
 | ||||
|     def library_dir_option(self, dir): | ||||
|         return "-L" + dir | ||||
| 
 | ||||
|     def _is_gcc(self, compiler_name): | ||||
|         return "gcc" in compiler_name or "g++" in compiler_name | ||||
| 
 | ||||
|     def runtime_library_dir_option(self, dir): | ||||
|         # XXX Hackish, at the very least.  See Python bug #445902: | ||||
|         # http://sourceforge.net/tracker/index.php | ||||
|         #   ?func=detail&aid=445902&group_id=5470&atid=105470 | ||||
|         # Linkers on different platforms need different options to | ||||
|         # specify that directories need to be added to the list of | ||||
|         # directories searched for dependencies when a dynamic library | ||||
|         # is sought.  GCC on GNU systems (Linux, FreeBSD, ...) has to | ||||
|         # be told to pass the -R option through to the linker, whereas | ||||
|         # other compilers and gcc on other systems just know this. | ||||
|         # Other compilers may need something slightly different.  At | ||||
|         # this time, there's no way to determine this information from | ||||
|         # the configuration data stored in the Python installation, so | ||||
|         # we use this hack. | ||||
| 
 | ||||
|         compiler = os.path.basename(sysconfig.get_config_var("CC")) | ||||
|         if sys.platform[:6] == "darwin": | ||||
|             # MacOSX's linker doesn't understand the -R flag at all | ||||
|             return "-L" + dir | ||||
|         elif sys.platform[:5] == "hp-ux": | ||||
|             if self._is_gcc(compiler): | ||||
|                 return ["-Wl,+s", "-L" + dir] | ||||
|             return ["+s", "-L" + dir] | ||||
|         elif sys.platform[:7] == "irix646" or sys.platform[:6] == "osf1V5": | ||||
|             return ["-rpath", dir] | ||||
|         elif self._is_gcc(compiler): | ||||
|             # gcc on non-GNU systems does not need -Wl, but can | ||||
|             # use it anyway.  Since distutils has always passed in | ||||
|             # -Wl whenever gcc was used in the past it is probably | ||||
|             # safest to keep doing so. | ||||
|             if sysconfig.get_config_var("GNULD") == "yes": | ||||
|                 # GNU ld needs an extra option to get a RUNPATH | ||||
|                 # instead of just an RPATH. | ||||
|                 return "-Wl,--enable-new-dtags,-R" + dir | ||||
|             else: | ||||
|                 return "-Wl,-R" + dir | ||||
|         elif sys.platform[:3] == "aix": | ||||
|             return "-blibpath:" + dir | ||||
|         else: | ||||
|             # No idea how --enable-new-dtags would be passed on to | ||||
|             # ld if this system was using GNU ld.  Don't know if a | ||||
|             # system like this even exists. | ||||
|             return "-R" + dir | ||||
| 
 | ||||
|     def library_option(self, lib): | ||||
|         return "-l" + lib | ||||
| 
 | ||||
|     def find_library_file(self, dirs, lib, debug=False): | ||||
|         shared_f = self.library_filename(lib, lib_type='shared') | ||||
|         dylib_f = self.library_filename(lib, lib_type='dylib') | ||||
|         static_f = self.library_filename(lib, lib_type='static') | ||||
| 
 | ||||
|         for dir in dirs: | ||||
|             shared = os.path.join(dir, shared_f) | ||||
|             dylib = os.path.join(dir, dylib_f) | ||||
|             static = os.path.join(dir, static_f) | ||||
|             # We're second-guessing the linker here, with not much hard | ||||
|             # data to go on: GCC seems to prefer the shared library, so I'm | ||||
|             # assuming that *all* Unix C compilers do.  And of course I'm | ||||
|             # ignoring even GCC's "-static" option.  So sue me. | ||||
|             if os.path.exists(dylib): | ||||
|                 return dylib | ||||
|             elif os.path.exists(shared): | ||||
|                 return shared | ||||
|             elif os.path.exists(static): | ||||
|                 return static | ||||
| 
 | ||||
|         # Oops, didn't find it in *any* of 'dirs' | ||||
|         return None | ||||
							
								
								
									
										357
									
								
								Lib/packaging/config.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										357
									
								
								Lib/packaging/config.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,357 @@ | |||
| """Utilities to find and read config files used by packaging.""" | ||||
| 
 | ||||
| import os | ||||
| import sys | ||||
| import logging | ||||
| 
 | ||||
| from shlex import split | ||||
| from configparser import RawConfigParser | ||||
| from packaging import logger | ||||
| from packaging.errors import PackagingOptionError | ||||
| from packaging.compiler.extension import Extension | ||||
| from packaging.util import check_environ, iglob, resolve_name, strtobool | ||||
| from packaging.compiler import set_compiler | ||||
| from packaging.command import set_command | ||||
| from packaging.markers import interpret | ||||
| 
 | ||||
| 
 | ||||
| def _pop_values(values_dct, key): | ||||
|     """Remove values from the dictionary and convert them as a list""" | ||||
|     vals_str = values_dct.pop(key, '') | ||||
|     if not vals_str: | ||||
|         return | ||||
|     fields = [] | ||||
|     for field in vals_str.split(os.linesep): | ||||
|         tmp_vals = field.split('--') | ||||
|         if len(tmp_vals) == 2 and not interpret(tmp_vals[1]): | ||||
|             continue | ||||
|         fields.append(tmp_vals[0]) | ||||
|     # Get bash options like `gcc -print-file-name=libgcc.a` XXX bash options? | ||||
|     vals = split(' '.join(fields)) | ||||
|     if vals: | ||||
|         return vals | ||||
| 
 | ||||
| 
 | ||||
| def _rel_path(base, path): | ||||
|     assert path.startswith(base) | ||||
|     return path[len(base):].lstrip('/') | ||||
| 
 | ||||
| 
 | ||||
| def get_resources_dests(resources_root, rules): | ||||
|     """Find destinations for resources files""" | ||||
|     destinations = {} | ||||
|     for base, suffix, dest in rules: | ||||
|         prefix = os.path.join(resources_root, base) | ||||
|         for abs_base in iglob(prefix): | ||||
|             abs_glob = os.path.join(abs_base, suffix) | ||||
|             for abs_path in iglob(abs_glob): | ||||
|                 resource_file = _rel_path(resources_root, abs_path) | ||||
|                 if dest is None:  # remove the entry if it was here | ||||
|                     destinations.pop(resource_file, None) | ||||
|                 else: | ||||
|                     rel_path = _rel_path(abs_base, abs_path) | ||||
|                     destinations[resource_file] = os.path.join(dest, rel_path) | ||||
|     return destinations | ||||
| 
 | ||||
| 
 | ||||
| class Config: | ||||
|     """Reads configuration files and work with the Distribution instance | ||||
|     """ | ||||
|     def __init__(self, dist): | ||||
|         self.dist = dist | ||||
|         self.setup_hook = None | ||||
| 
 | ||||
|     def run_hook(self, config): | ||||
|         if self.setup_hook is None: | ||||
|             return | ||||
|         # the hook gets only the config | ||||
|         self.setup_hook(config) | ||||
| 
 | ||||
|     def find_config_files(self): | ||||
|         """Find as many configuration files as should be processed for this | ||||
|         platform, and return a list of filenames in the order in which they | ||||
|         should be parsed.  The filenames returned are guaranteed to exist | ||||
|         (modulo nasty race conditions). | ||||
| 
 | ||||
|         There are three possible config files: packaging.cfg in the | ||||
|         Packaging installation directory (ie. where the top-level | ||||
|         Packaging __inst__.py file lives), a file in the user's home | ||||
|         directory named .pydistutils.cfg on Unix and pydistutils.cfg | ||||
|         on Windows/Mac; and setup.cfg in the current directory. | ||||
| 
 | ||||
|         The file in the user's home directory can be disabled with the | ||||
|         --no-user-cfg option. | ||||
|         """ | ||||
|         files = [] | ||||
|         check_environ() | ||||
| 
 | ||||
|         # Where to look for the system-wide Packaging config file | ||||
|         sys_dir = os.path.dirname(sys.modules['packaging'].__file__) | ||||
| 
 | ||||
|         # Look for the system config file | ||||
|         sys_file = os.path.join(sys_dir, "packaging.cfg") | ||||
|         if os.path.isfile(sys_file): | ||||
|             files.append(sys_file) | ||||
| 
 | ||||
|         # What to call the per-user config file | ||||
|         if os.name == 'posix': | ||||
|             user_filename = ".pydistutils.cfg" | ||||
|         else: | ||||
|             user_filename = "pydistutils.cfg" | ||||
| 
 | ||||
|         # And look for the user config file | ||||
|         if self.dist.want_user_cfg: | ||||
|             user_file = os.path.join(os.path.expanduser('~'), user_filename) | ||||
|             if os.path.isfile(user_file): | ||||
|                 files.append(user_file) | ||||
| 
 | ||||
|         # All platforms support local setup.cfg | ||||
|         local_file = "setup.cfg" | ||||
|         if os.path.isfile(local_file): | ||||
|             files.append(local_file) | ||||
| 
 | ||||
|         if logger.isEnabledFor(logging.DEBUG): | ||||
|             logger.debug("using config files: %s", ', '.join(files)) | ||||
|         return files | ||||
| 
 | ||||
|     def _convert_metadata(self, name, value): | ||||
|         # converts a value found in setup.cfg into a valid metadata | ||||
|         # XXX | ||||
|         return value | ||||
| 
 | ||||
|     def _multiline(self, value): | ||||
|         value = [v for v in | ||||
|                  [v.strip() for v in value.split('\n')] | ||||
|                  if v != ''] | ||||
|         return value | ||||
| 
 | ||||
|     def _read_setup_cfg(self, parser, cfg_filename): | ||||
|         cfg_directory = os.path.dirname(os.path.abspath(cfg_filename)) | ||||
|         content = {} | ||||
|         for section in parser.sections(): | ||||
|             content[section] = dict(parser.items(section)) | ||||
| 
 | ||||
|         # global:setup_hook is called *first* | ||||
|         if 'global' in content: | ||||
|             if 'setup_hook' in content['global']: | ||||
|                 setup_hook = content['global']['setup_hook'] | ||||
|                 try: | ||||
|                     self.setup_hook = resolve_name(setup_hook) | ||||
|                 except ImportError as e: | ||||
|                     logger.warning('could not import setup_hook: %s', | ||||
|                             e.args[0]) | ||||
|                 else: | ||||
|                     self.run_hook(content) | ||||
| 
 | ||||
|         metadata = self.dist.metadata | ||||
| 
 | ||||
|         # setting the metadata values | ||||
|         if 'metadata' in content: | ||||
|             for key, value in content['metadata'].items(): | ||||
|                 key = key.replace('_', '-') | ||||
|                 if metadata.is_multi_field(key): | ||||
|                     value = self._multiline(value) | ||||
| 
 | ||||
|                 if key == 'project-url': | ||||
|                     value = [(label.strip(), url.strip()) | ||||
|                              for label, url in | ||||
|                              [v.split(',') for v in value]] | ||||
| 
 | ||||
|                 if key == 'description-file': | ||||
|                     if 'description' in content['metadata']: | ||||
|                         msg = ("description and description-file' are " | ||||
|                                "mutually exclusive") | ||||
|                         raise PackagingOptionError(msg) | ||||
| 
 | ||||
|                     if isinstance(value, list): | ||||
|                         filenames = value | ||||
|                     else: | ||||
|                         filenames = value.split() | ||||
| 
 | ||||
|                     # concatenate each files | ||||
|                     value = '' | ||||
|                     for filename in filenames: | ||||
|                         # will raise if file not found | ||||
|                         with open(filename) as description_file: | ||||
|                             value += description_file.read().strip() + '\n' | ||||
|                         # add filename as a required file | ||||
|                         if filename not in metadata.requires_files: | ||||
|                             metadata.requires_files.append(filename) | ||||
|                     value = value.strip() | ||||
|                     key = 'description' | ||||
| 
 | ||||
|                 if metadata.is_metadata_field(key): | ||||
|                     metadata[key] = self._convert_metadata(key, value) | ||||
| 
 | ||||
|         if 'files' in content: | ||||
|             files = content['files'] | ||||
|             self.dist.package_dir = files.pop('packages_root', None) | ||||
| 
 | ||||
|             files = dict((key, self._multiline(value)) for key, value in | ||||
|                          files.items()) | ||||
| 
 | ||||
|             self.dist.packages = [] | ||||
| 
 | ||||
|             packages = files.get('packages', []) | ||||
|             if isinstance(packages, str): | ||||
|                 packages = [packages] | ||||
| 
 | ||||
|             for package in packages: | ||||
|                 if ':' in package: | ||||
|                     dir_, package = package.split(':') | ||||
|                     self.dist.package_dir[package] = dir_ | ||||
|                 self.dist.packages.append(package) | ||||
| 
 | ||||
|             self.dist.py_modules = files.get('modules', []) | ||||
|             if isinstance(self.dist.py_modules, str): | ||||
|                 self.dist.py_modules = [self.dist.py_modules] | ||||
|             self.dist.scripts = files.get('scripts', []) | ||||
|             if isinstance(self.dist.scripts, str): | ||||
|                 self.dist.scripts = [self.dist.scripts] | ||||
| 
 | ||||
|             self.dist.package_data = {} | ||||
|             for data in files.get('package_data', []): | ||||
|                 data = data.split('=') | ||||
|                 if len(data) != 2: | ||||
|                     continue  # XXX error should never pass silently | ||||
|                 key, value = data | ||||
|                 self.dist.package_data[key.strip()] = value.strip() | ||||
| 
 | ||||
|             self.dist.data_files = [] | ||||
|             for data in files.get('data_files', []): | ||||
|                 data = data.split('=') | ||||
|                 if len(data) != 2: | ||||
|                     continue | ||||
|                 key, value = data | ||||
|                 values = [v.strip() for v in value.split(',')] | ||||
|                 self.dist.data_files.append((key, values)) | ||||
| 
 | ||||
|             # manifest template | ||||
|             self.dist.extra_files = files.get('extra_files', []) | ||||
| 
 | ||||
|             resources = [] | ||||
|             for rule in files.get('resources', []): | ||||
|                 glob, destination = rule.split('=', 1) | ||||
|                 rich_glob = glob.strip().split(' ', 1) | ||||
|                 if len(rich_glob) == 2: | ||||
|                     prefix, suffix = rich_glob | ||||
|                 else: | ||||
|                     assert len(rich_glob) == 1 | ||||
|                     prefix = '' | ||||
|                     suffix = glob | ||||
|                 if destination == '<exclude>': | ||||
|                     destination = None | ||||
|                 resources.append( | ||||
|                     (prefix.strip(), suffix.strip(), destination.strip())) | ||||
|                 self.dist.data_files = get_resources_dests( | ||||
|                     cfg_directory, resources) | ||||
| 
 | ||||
|         ext_modules = self.dist.ext_modules | ||||
|         for section_key in content: | ||||
|             labels = section_key.split('=') | ||||
|             if len(labels) == 2 and labels[0] == 'extension': | ||||
|                 # labels[1] not used from now but should be implemented | ||||
|                 # for extension build dependency | ||||
|                 values_dct = content[section_key] | ||||
|                 ext_modules.append(Extension( | ||||
|                     values_dct.pop('name'), | ||||
|                     _pop_values(values_dct, 'sources'), | ||||
|                     _pop_values(values_dct, 'include_dirs'), | ||||
|                     _pop_values(values_dct, 'define_macros'), | ||||
|                     _pop_values(values_dct, 'undef_macros'), | ||||
|                     _pop_values(values_dct, 'library_dirs'), | ||||
|                     _pop_values(values_dct, 'libraries'), | ||||
|                     _pop_values(values_dct, 'runtime_library_dirs'), | ||||
|                     _pop_values(values_dct, 'extra_objects'), | ||||
|                     _pop_values(values_dct, 'extra_compile_args'), | ||||
|                     _pop_values(values_dct, 'extra_link_args'), | ||||
|                     _pop_values(values_dct, 'export_symbols'), | ||||
|                     _pop_values(values_dct, 'swig_opts'), | ||||
|                     _pop_values(values_dct, 'depends'), | ||||
|                     values_dct.pop('language', None), | ||||
|                     values_dct.pop('optional', None), | ||||
|                     **values_dct)) | ||||
| 
 | ||||
|     def parse_config_files(self, filenames=None): | ||||
|         if filenames is None: | ||||
|             filenames = self.find_config_files() | ||||
| 
 | ||||
|         logger.debug("Distribution.parse_config_files():") | ||||
| 
 | ||||
|         parser = RawConfigParser() | ||||
| 
 | ||||
|         for filename in filenames: | ||||
|             logger.debug("  reading %s", filename) | ||||
|             parser.read(filename) | ||||
| 
 | ||||
|             if os.path.split(filename)[-1] == 'setup.cfg': | ||||
|                 self._read_setup_cfg(parser, filename) | ||||
| 
 | ||||
|             for section in parser.sections(): | ||||
|                 if section == 'global': | ||||
|                     if parser.has_option('global', 'compilers'): | ||||
|                         self._load_compilers(parser.get('global', 'compilers')) | ||||
| 
 | ||||
|                     if parser.has_option('global', 'commands'): | ||||
|                         self._load_commands(parser.get('global', 'commands')) | ||||
| 
 | ||||
|                 options = parser.options(section) | ||||
|                 opt_dict = self.dist.get_option_dict(section) | ||||
| 
 | ||||
|                 for opt in options: | ||||
|                     if opt == '__name__': | ||||
|                         continue | ||||
|                     val = parser.get(section, opt) | ||||
|                     opt = opt.replace('-', '_') | ||||
| 
 | ||||
|                     if opt == 'sub_commands': | ||||
|                         val = self._multiline(val) | ||||
|                         if isinstance(val, str): | ||||
|                             val = [val] | ||||
| 
 | ||||
|                     # Hooks use a suffix system to prevent being overriden | ||||
|                     # by a config file processed later (i.e. a hook set in | ||||
|                     # the user config file cannot be replaced by a hook | ||||
|                     # set in a project config file, unless they have the | ||||
|                     # same suffix). | ||||
|                     if (opt.startswith("pre_hook.") or | ||||
|                         opt.startswith("post_hook.")): | ||||
|                         hook_type, alias = opt.split(".") | ||||
|                         hook_dict = opt_dict.setdefault( | ||||
|                             hook_type, (filename, {}))[1] | ||||
|                         hook_dict[alias] = val | ||||
|                     else: | ||||
|                         opt_dict[opt] = filename, val | ||||
| 
 | ||||
|             # Make the RawConfigParser forget everything (so we retain | ||||
|             # the original filenames that options come from) | ||||
|             parser.__init__() | ||||
| 
 | ||||
|         # If there was a "global" section in the config file, use it | ||||
|         # to set Distribution options. | ||||
|         if 'global' in self.dist.command_options: | ||||
|             for opt, (src, val) in self.dist.command_options['global'].items(): | ||||
|                 alias = self.dist.negative_opt.get(opt) | ||||
|                 try: | ||||
|                     if alias: | ||||
|                         setattr(self.dist, alias, not strtobool(val)) | ||||
|                     elif opt == 'dry_run':  # FIXME ugh! | ||||
|                         setattr(self.dist, opt, strtobool(val)) | ||||
|                     else: | ||||
|                         setattr(self.dist, opt, val) | ||||
|                 except ValueError as msg: | ||||
|                     raise PackagingOptionError(msg) | ||||
| 
 | ||||
|     def _load_compilers(self, compilers): | ||||
|         compilers = self._multiline(compilers) | ||||
|         if isinstance(compilers, str): | ||||
|             compilers = [compilers] | ||||
|         for compiler in compilers: | ||||
|             set_compiler(compiler.strip()) | ||||
| 
 | ||||
|     def _load_commands(self, commands): | ||||
|         commands = self._multiline(commands) | ||||
|         if isinstance(commands, str): | ||||
|             commands = [commands] | ||||
|         for command in commands: | ||||
|             set_command(command.strip()) | ||||
							
								
								
									
										693
									
								
								Lib/packaging/create.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										693
									
								
								Lib/packaging/create.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,693 @@ | |||
| #!/usr/bin/env python | ||||
| """Interactive helper used to create a setup.cfg file. | ||||
| 
 | ||||
| This script will generate a packaging configuration file by looking at | ||||
| the current directory and asking the user questions.  It is intended to | ||||
| be called as | ||||
| 
 | ||||
|   pysetup create | ||||
| 
 | ||||
| or | ||||
| 
 | ||||
|   python3.3 -m packaging.create | ||||
| """ | ||||
| 
 | ||||
| #  Original code by Sean Reifschneider <jafo@tummy.com> | ||||
| 
 | ||||
| #  Original TODO list: | ||||
| #  Look for a license file and automatically add the category. | ||||
| #  When a .c file is found during the walk, can we add it as an extension? | ||||
| #  Ask if there is a maintainer different that the author | ||||
| #  Ask for the platform (can we detect this via "import win32" or something?) | ||||
| #  Ask for the dependencies. | ||||
| #  Ask for the Requires-Dist | ||||
| #  Ask for the Provides-Dist | ||||
| #  Ask for a description | ||||
| #  Detect scripts (not sure how.  #! outside of package?) | ||||
| 
 | ||||
| import os | ||||
| import imp | ||||
| import sys | ||||
| import glob | ||||
| import re | ||||
| import shutil | ||||
| import sysconfig | ||||
| from configparser import RawConfigParser | ||||
| from textwrap import dedent | ||||
| from hashlib import md5 | ||||
| from functools import cmp_to_key | ||||
| # importing this with an underscore as it should be replaced by the | ||||
| # dict form or another structures for all purposes | ||||
| from packaging._trove import all_classifiers as _CLASSIFIERS_LIST | ||||
| from packaging.version import is_valid_version | ||||
| 
 | ||||
| _FILENAME = 'setup.cfg' | ||||
| _DEFAULT_CFG = '.pypkgcreate' | ||||
| 
 | ||||
| _helptext = { | ||||
|     'name': ''' | ||||
| The name of the program to be packaged, usually a single word composed | ||||
| of lower-case characters such as "python", "sqlalchemy", or "CherryPy". | ||||
| ''', | ||||
|     'version': ''' | ||||
| Version number of the software, typically 2 or 3 numbers separated by dots | ||||
| such as "1.00", "0.6", or "3.02.01".  "0.1.0" is recommended for initial | ||||
| development. | ||||
| ''', | ||||
|     'summary': ''' | ||||
| A one-line summary of what this project is or does, typically a sentence 80 | ||||
| characters or less in length. | ||||
| ''', | ||||
|     'author': ''' | ||||
| The full name of the author (typically you). | ||||
| ''', | ||||
|     'author_email': ''' | ||||
| E-mail address of the project author (typically you). | ||||
| ''', | ||||
|     'do_classifier': ''' | ||||
| Trove classifiers are optional identifiers that allow you to specify the | ||||
| intended audience by saying things like "Beta software with a text UI | ||||
| for Linux under the PSF license.  However, this can be a somewhat involved | ||||
| process. | ||||
| ''', | ||||
|     'packages': ''' | ||||
| You can provide a package name contained in your project. | ||||
| ''', | ||||
|     'modules': ''' | ||||
| You can provide a python module contained in your project. | ||||
| ''', | ||||
|     'extra_files': ''' | ||||
| You can provide extra files/dirs contained in your project. | ||||
| It has to follow the template syntax. XXX add help here. | ||||
| ''', | ||||
| 
 | ||||
|     'home_page': ''' | ||||
| The home page for the project, typically starting with "http://". | ||||
| ''', | ||||
|     'trove_license': ''' | ||||
| Optionally you can specify a license.  Type a string that identifies a common | ||||
| license, and then you can select a list of license specifiers. | ||||
| ''', | ||||
|     'trove_generic': ''' | ||||
| Optionally, you can set other trove identifiers for things such as the | ||||
| human language, programming language, user interface, etc... | ||||
| ''', | ||||
|     'setup.py found': ''' | ||||
| The setup.py script will be executed to retrieve the metadata. | ||||
| A wizard will be run if you answer "n", | ||||
| ''', | ||||
| } | ||||
| 
 | ||||
| PROJECT_MATURITY = ['Development Status :: 1 - Planning', | ||||
|                     'Development Status :: 2 - Pre-Alpha', | ||||
|                     'Development Status :: 3 - Alpha', | ||||
|                     'Development Status :: 4 - Beta', | ||||
|                     'Development Status :: 5 - Production/Stable', | ||||
|                     'Development Status :: 6 - Mature', | ||||
|                     'Development Status :: 7 - Inactive'] | ||||
| 
 | ||||
| # XXX everything needs docstrings and tests (both low-level tests of various | ||||
| # methods and functional tests of running the script) | ||||
| 
 | ||||
| 
 | ||||
| def load_setup(): | ||||
|     """run the setup script (i.e the setup.py file) | ||||
| 
 | ||||
|     This function load the setup file in all cases (even if it have already | ||||
|     been loaded before, because we are monkey patching its setup function with | ||||
|     a particular one""" | ||||
|     with open("setup.py") as f: | ||||
|         imp.load_module("setup", f, "setup.py", (".py", "r", imp.PY_SOURCE)) | ||||
| 
 | ||||
| 
 | ||||
| def ask_yn(question, default=None, helptext=None): | ||||
|     question += ' (y/n)' | ||||
|     while True: | ||||
|         answer = ask(question, default, helptext, required=True) | ||||
|         if answer and answer[0].lower() in 'yn': | ||||
|             return answer[0].lower() | ||||
| 
 | ||||
|         print('\nERROR: You must select "Y" or "N".\n') | ||||
| 
 | ||||
| 
 | ||||
| def ask(question, default=None, helptext=None, required=True, | ||||
|         lengthy=False, multiline=False): | ||||
|     prompt = '%s: ' % (question,) | ||||
|     if default: | ||||
|         prompt = '%s [%s]: ' % (question, default) | ||||
|         if default and len(question) + len(default) > 70: | ||||
|             prompt = '%s\n    [%s]: ' % (question, default) | ||||
|     if lengthy or multiline: | ||||
|         prompt += '\n   > ' | ||||
| 
 | ||||
|     if not helptext: | ||||
|         helptext = 'No additional help available.' | ||||
| 
 | ||||
|     helptext = helptext.strip("\n") | ||||
| 
 | ||||
|     while True: | ||||
|         sys.stdout.write(prompt) | ||||
|         sys.stdout.flush() | ||||
| 
 | ||||
|         line = sys.stdin.readline().strip() | ||||
|         if line == '?': | ||||
|             print('=' * 70) | ||||
|             print(helptext) | ||||
|             print('=' * 70) | ||||
|             continue | ||||
|         if default and not line: | ||||
|             return default | ||||
|         if not line and required: | ||||
|             print('*' * 70) | ||||
|             print('This value cannot be empty.') | ||||
|             print('===========================') | ||||
|             if helptext: | ||||
|                 print(helptext) | ||||
|             print('*' * 70) | ||||
|             continue | ||||
|         return line | ||||
| 
 | ||||
| 
 | ||||
| def convert_yn_to_bool(yn, yes=True, no=False): | ||||
|     """Convert a y/yes or n/no to a boolean value.""" | ||||
|     if yn.lower().startswith('y'): | ||||
|         return yes | ||||
|     else: | ||||
|         return no | ||||
| 
 | ||||
| 
 | ||||
| def _build_classifiers_dict(classifiers): | ||||
|     d = {} | ||||
|     for key in classifiers: | ||||
|         subDict = d | ||||
|         for subkey in key.split(' :: '): | ||||
|             if not subkey in subDict: | ||||
|                 subDict[subkey] = {} | ||||
|             subDict = subDict[subkey] | ||||
|     return d | ||||
| 
 | ||||
| CLASSIFIERS = _build_classifiers_dict(_CLASSIFIERS_LIST) | ||||
| 
 | ||||
| 
 | ||||
| def _build_licences(classifiers): | ||||
|     res = [] | ||||
|     for index, item in enumerate(classifiers): | ||||
|         if not item.startswith('License :: '): | ||||
|             continue | ||||
|         res.append((index, item.split(' :: ')[-1].lower())) | ||||
|     return res | ||||
| 
 | ||||
| LICENCES = _build_licences(_CLASSIFIERS_LIST) | ||||
| 
 | ||||
| 
 | ||||
| class MainProgram: | ||||
|     """Make a project setup configuration file (setup.cfg).""" | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.configparser = None | ||||
|         self.classifiers = set() | ||||
|         self.data = {'name': '', | ||||
|                      'version': '1.0.0', | ||||
|                      'classifier': self.classifiers, | ||||
|                      'packages': [], | ||||
|                      'modules': [], | ||||
|                      'platform': [], | ||||
|                      'resources': [], | ||||
|                      'extra_files': [], | ||||
|                      'scripts': [], | ||||
|                      } | ||||
|         self._load_defaults() | ||||
| 
 | ||||
|     def __call__(self): | ||||
|         setupcfg_defined = False | ||||
|         if self.has_setup_py() and self._prompt_user_for_conversion(): | ||||
|             setupcfg_defined = self.convert_py_to_cfg() | ||||
|         if not setupcfg_defined: | ||||
|             self.define_cfg_values() | ||||
|         self._write_cfg() | ||||
| 
 | ||||
|     def has_setup_py(self): | ||||
|         """Test for the existance of a setup.py file.""" | ||||
|         return os.path.exists('setup.py') | ||||
| 
 | ||||
|     def define_cfg_values(self): | ||||
|         self.inspect() | ||||
|         self.query_user() | ||||
| 
 | ||||
|     def _lookup_option(self, key): | ||||
|         if not self.configparser.has_option('DEFAULT', key): | ||||
|             return None | ||||
|         return self.configparser.get('DEFAULT', key) | ||||
| 
 | ||||
|     def _load_defaults(self): | ||||
|         # Load default values from a user configuration file | ||||
|         self.configparser = RawConfigParser() | ||||
|         # TODO replace with section in distutils config file | ||||
|         default_cfg = os.path.expanduser(os.path.join('~', _DEFAULT_CFG)) | ||||
|         self.configparser.read(default_cfg) | ||||
|         self.data['author'] = self._lookup_option('author') | ||||
|         self.data['author_email'] = self._lookup_option('author_email') | ||||
| 
 | ||||
|     def _prompt_user_for_conversion(self): | ||||
|         # Prompt the user about whether they would like to use the setup.py | ||||
|         # conversion utility to generate a setup.cfg or generate the setup.cfg | ||||
|         # from scratch | ||||
|         answer = ask_yn(('A legacy setup.py has been found.\n' | ||||
|                          'Would you like to convert it to a setup.cfg?'), | ||||
|                         default="y", | ||||
|                         helptext=_helptext['setup.py found']) | ||||
|         return convert_yn_to_bool(answer) | ||||
| 
 | ||||
|     def _dotted_packages(self, data): | ||||
|         packages = sorted(data) | ||||
|         modified_pkgs = [] | ||||
|         for pkg in packages: | ||||
|             pkg = pkg.lstrip('./') | ||||
|             pkg = pkg.replace('/', '.') | ||||
|             modified_pkgs.append(pkg) | ||||
|         return modified_pkgs | ||||
| 
 | ||||
|     def _write_cfg(self): | ||||
|         if os.path.exists(_FILENAME): | ||||
|             if os.path.exists('%s.old' % _FILENAME): | ||||
|                 print("ERROR: %(name)s.old backup exists, please check that " | ||||
|                       "current %(name)s is correct and remove %(name)s.old" % | ||||
|                       {'name': _FILENAME}) | ||||
|                 return | ||||
|             shutil.move(_FILENAME, '%s.old' % _FILENAME) | ||||
| 
 | ||||
|         with open(_FILENAME, 'w') as fp: | ||||
|             fp.write('[metadata]\n') | ||||
|             # simple string entries | ||||
|             for name in ('name', 'version', 'summary', 'download_url'): | ||||
|                 fp.write('%s = %s\n' % (name, self.data.get(name, 'UNKNOWN'))) | ||||
|             # optional string entries | ||||
|             if 'keywords' in self.data and self.data['keywords']: | ||||
|                 fp.write('keywords = %s\n' % ' '.join(self.data['keywords'])) | ||||
|             for name in ('home_page', 'author', 'author_email', | ||||
|                          'maintainer', 'maintainer_email', 'description-file'): | ||||
|                 if name in self.data and self.data[name]: | ||||
|                     fp.write('%s = %s\n' % (name, self.data[name])) | ||||
|             if 'description' in self.data: | ||||
|                 fp.write( | ||||
|                     'description = %s\n' | ||||
|                     % '\n       |'.join(self.data['description'].split('\n'))) | ||||
|             # multiple use string entries | ||||
|             for name in ('platform', 'supported-platform', 'classifier', | ||||
|                          'requires-dist', 'provides-dist', 'obsoletes-dist', | ||||
|                          'requires-external'): | ||||
|                 if not(name in self.data and self.data[name]): | ||||
|                     continue | ||||
|                 fp.write('%s = ' % name) | ||||
|                 fp.write(''.join('    %s\n' % val | ||||
|                                  for val in self.data[name]).lstrip()) | ||||
|             fp.write('\n[files]\n') | ||||
|             for name in ('packages', 'modules', 'scripts', | ||||
|                          'package_data', 'extra_files'): | ||||
|                 if not(name in self.data and self.data[name]): | ||||
|                     continue | ||||
|                 fp.write('%s = %s\n' | ||||
|                          % (name, '\n    '.join(self.data[name]).strip())) | ||||
|             fp.write('\nresources =\n') | ||||
|             for src, dest in self.data['resources']: | ||||
|                 fp.write('    %s = %s\n' % (src, dest)) | ||||
|             fp.write('\n') | ||||
| 
 | ||||
|         os.chmod(_FILENAME, 0o644) | ||||
|         print('Wrote "%s".' % _FILENAME) | ||||
| 
 | ||||
|     def convert_py_to_cfg(self): | ||||
|         """Generate a setup.cfg from an existing setup.py. | ||||
| 
 | ||||
|         It only exports the distutils metadata (setuptools specific metadata | ||||
|         is not currently supported). | ||||
|         """ | ||||
|         data = self.data | ||||
| 
 | ||||
|         def setup_mock(**attrs): | ||||
|             """Mock the setup(**attrs) in order to retrieve metadata.""" | ||||
|             # use the distutils v1 processings to correctly parse metadata. | ||||
|             #XXX we could also use the setuptools distibution ??? | ||||
|             from distutils.dist import Distribution | ||||
|             dist = Distribution(attrs) | ||||
|             dist.parse_config_files() | ||||
| 
 | ||||
|             # 1. retrieve metadata fields that are quite similar in | ||||
|             # PEP 314 and PEP 345 | ||||
|             labels = (('name',) * 2, | ||||
|                       ('version',) * 2, | ||||
|                       ('author',) * 2, | ||||
|                       ('author_email',) * 2, | ||||
|                       ('maintainer',) * 2, | ||||
|                       ('maintainer_email',) * 2, | ||||
|                       ('description', 'summary'), | ||||
|                       ('long_description', 'description'), | ||||
|                       ('url', 'home_page'), | ||||
|                       ('platforms', 'platform'), | ||||
|                       # backport only for 2.5+ | ||||
|                       ('provides', 'provides-dist'), | ||||
|                       ('obsoletes', 'obsoletes-dist'), | ||||
|                       ('requires', 'requires-dist')) | ||||
| 
 | ||||
|             get = lambda lab: getattr(dist.metadata, lab.replace('-', '_')) | ||||
|             data.update((new, get(old)) for old, new in labels if get(old)) | ||||
| 
 | ||||
|             # 2. retrieve data that requires special processing | ||||
|             data['classifier'].update(dist.get_classifiers() or []) | ||||
|             data['scripts'].extend(dist.scripts or []) | ||||
|             data['packages'].extend(dist.packages or []) | ||||
|             data['modules'].extend(dist.py_modules or []) | ||||
|             # 2.1 data_files -> resources | ||||
|             if dist.data_files: | ||||
|                 if len(dist.data_files) < 2 or \ | ||||
|                    isinstance(dist.data_files[1], str): | ||||
|                     dist.data_files = [('', dist.data_files)] | ||||
|                 # add tokens in the destination paths | ||||
|                 vars = {'distribution.name': data['name']} | ||||
|                 path_tokens = list(sysconfig.get_paths(vars=vars).items()) | ||||
| 
 | ||||
|                 def length_comparison(x, y): | ||||
|                     len_x = len(x[1]) | ||||
|                     len_y = len(y[1]) | ||||
|                     if len_x == len_y: | ||||
|                         return 0 | ||||
|                     elif len_x < len_y: | ||||
|                         return -1 | ||||
|                     else: | ||||
|                         return 1 | ||||
| 
 | ||||
|                 # sort tokens to use the longest one first | ||||
|                 path_tokens.sort(key=cmp_to_key(length_comparison)) | ||||
|                 for dest, srcs in (dist.data_files or []): | ||||
|                     dest = os.path.join(sys.prefix, dest) | ||||
|                     for tok, path in path_tokens: | ||||
|                         if dest.startswith(path): | ||||
|                             dest = ('{%s}' % tok) + dest[len(path):] | ||||
|                             files = [('/ '.join(src.rsplit('/', 1)), dest) | ||||
|                                      for src in srcs] | ||||
|                             data['resources'].extend(files) | ||||
|                             continue | ||||
|             # 2.2 package_data -> extra_files | ||||
|             package_dirs = dist.package_dir or {} | ||||
|             for package, extras in iter(dist.package_data.items()) or []: | ||||
|                 package_dir = package_dirs.get(package, package) | ||||
|                 files = [os.path.join(package_dir, f) for f in extras] | ||||
|                 data['extra_files'].extend(files) | ||||
| 
 | ||||
|             # Use README file if its content is the desciption | ||||
|             if "description" in data: | ||||
|                 ref = md5(re.sub('\s', '', | ||||
|                                  self.data['description']).lower().encode()) | ||||
|                 ref = ref.digest() | ||||
|                 for readme in glob.glob('README*'): | ||||
|                     with open(readme) as fp: | ||||
|                         contents = fp.read() | ||||
|                     val = md5(re.sub('\s', '', | ||||
|                                      contents.lower()).encode()).digest() | ||||
|                     if val == ref: | ||||
|                         del data['description'] | ||||
|                         data['description-file'] = readme | ||||
|                         break | ||||
| 
 | ||||
|         # apply monkey patch to distutils (v1) and setuptools (if needed) | ||||
|         # (abort the feature if distutils v1 has been killed) | ||||
|         try: | ||||
|             from distutils import core | ||||
|             core.setup  # make sure it's not d2 maskerading as d1 | ||||
|         except (ImportError, AttributeError): | ||||
|             return | ||||
|         saved_setups = [(core, core.setup)] | ||||
|         core.setup = setup_mock | ||||
|         try: | ||||
|             import setuptools | ||||
|         except ImportError: | ||||
|             pass | ||||
|         else: | ||||
|             saved_setups.append((setuptools, setuptools.setup)) | ||||
|             setuptools.setup = setup_mock | ||||
|         # get metadata by executing the setup.py with the patched setup(...) | ||||
|         success = False  # for python < 2.4 | ||||
|         try: | ||||
|             load_setup() | ||||
|             success = True | ||||
|         finally:  # revert monkey patches | ||||
|             for patched_module, original_setup in saved_setups: | ||||
|                 patched_module.setup = original_setup | ||||
|         if not self.data: | ||||
|             raise ValueError('Unable to load metadata from setup.py') | ||||
|         return success | ||||
| 
 | ||||
|     def inspect_file(self, path): | ||||
|         with open(path, 'r') as fp: | ||||
|             for _ in range(10): | ||||
|                 line = fp.readline() | ||||
|                 m = re.match(r'^#!.*python((?P<major>\d)(\.\d+)?)?$', line) | ||||
|                 if m: | ||||
|                     if m.group('major') == '3': | ||||
|                         self.classifiers.add( | ||||
|                             'Programming Language :: Python :: 3') | ||||
|                     else: | ||||
|                         self.classifiers.add( | ||||
|                         'Programming Language :: Python :: 2') | ||||
| 
 | ||||
|     def inspect(self): | ||||
|         """Inspect the current working diretory for a name and version. | ||||
| 
 | ||||
|         This information is harvested in where the directory is named | ||||
|         like [name]-[version]. | ||||
|         """ | ||||
|         dir_name = os.path.basename(os.getcwd()) | ||||
|         self.data['name'] = dir_name | ||||
|         match = re.match(r'(.*)-(\d.+)', dir_name) | ||||
|         if match: | ||||
|             self.data['name'] = match.group(1) | ||||
|             self.data['version'] = match.group(2) | ||||
|             # TODO Needs tested! | ||||
|             if not is_valid_version(self.data['version']): | ||||
|                 msg = "Invalid version discovered: %s" % self.data['version'] | ||||
|                 raise RuntimeError(msg) | ||||
| 
 | ||||
|     def query_user(self): | ||||
|         self.data['name'] = ask('Project name', self.data['name'], | ||||
|               _helptext['name']) | ||||
| 
 | ||||
|         self.data['version'] = ask('Current version number', | ||||
|               self.data.get('version'), _helptext['version']) | ||||
|         self.data['summary'] = ask('Package summary', | ||||
|               self.data.get('summary'), _helptext['summary'], | ||||
|               lengthy=True) | ||||
|         self.data['author'] = ask('Author name', | ||||
|               self.data.get('author'), _helptext['author']) | ||||
|         self.data['author_email'] = ask('Author e-mail address', | ||||
|               self.data.get('author_email'), _helptext['author_email']) | ||||
|         self.data['home_page'] = ask('Project Home Page', | ||||
|               self.data.get('home_page'), _helptext['home_page'], | ||||
|               required=False) | ||||
| 
 | ||||
|         if ask_yn('Do you want me to automatically build the file list ' | ||||
|               'with everything I can find in the current directory ? ' | ||||
|               'If you say no, you will have to define them manually.') == 'y': | ||||
|             self._find_files() | ||||
|         else: | ||||
|             while ask_yn('Do you want to add a single module ?' | ||||
|                         ' (you will be able to add full packages next)', | ||||
|                     helptext=_helptext['modules']) == 'y': | ||||
|                 self._set_multi('Module name', 'modules') | ||||
| 
 | ||||
|             while ask_yn('Do you want to add a package ?', | ||||
|                     helptext=_helptext['packages']) == 'y': | ||||
|                 self._set_multi('Package name', 'packages') | ||||
| 
 | ||||
|             while ask_yn('Do you want to add an extra file ?', | ||||
|                         helptext=_helptext['extra_files']) == 'y': | ||||
|                 self._set_multi('Extra file/dir name', 'extra_files') | ||||
| 
 | ||||
|         if ask_yn('Do you want to set Trove classifiers?', | ||||
|                   helptext=_helptext['do_classifier']) == 'y': | ||||
|             self.set_classifier() | ||||
| 
 | ||||
|     def _find_files(self): | ||||
|         # we are looking for python modules and packages, | ||||
|         # other stuff are added as regular files | ||||
|         pkgs = self.data['packages'] | ||||
|         modules = self.data['modules'] | ||||
|         extra_files = self.data['extra_files'] | ||||
| 
 | ||||
|         def is_package(path): | ||||
|             return os.path.exists(os.path.join(path, '__init__.py')) | ||||
| 
 | ||||
|         curdir = os.getcwd() | ||||
|         scanned = [] | ||||
|         _pref = ['lib', 'include', 'dist', 'build', '.', '~'] | ||||
|         _suf = ['.pyc'] | ||||
| 
 | ||||
|         def to_skip(path): | ||||
|             path = relative(path) | ||||
| 
 | ||||
|             for pref in _pref: | ||||
|                 if path.startswith(pref): | ||||
|                     return True | ||||
| 
 | ||||
|             for suf in _suf: | ||||
|                 if path.endswith(suf): | ||||
|                     return True | ||||
| 
 | ||||
|             return False | ||||
| 
 | ||||
|         def relative(path): | ||||
|             return path[len(curdir) + 1:] | ||||
| 
 | ||||
|         def dotted(path): | ||||
|             res = relative(path).replace(os.path.sep, '.') | ||||
|             if res.endswith('.py'): | ||||
|                 res = res[:-len('.py')] | ||||
|             return res | ||||
| 
 | ||||
|         # first pass: packages | ||||
|         for root, dirs, files in os.walk(curdir): | ||||
|             if to_skip(root): | ||||
|                 continue | ||||
|             for dir_ in sorted(dirs): | ||||
|                 if to_skip(dir_): | ||||
|                     continue | ||||
|                 fullpath = os.path.join(root, dir_) | ||||
|                 dotted_name = dotted(fullpath) | ||||
|                 if is_package(fullpath) and dotted_name not in pkgs: | ||||
|                     pkgs.append(dotted_name) | ||||
|                     scanned.append(fullpath) | ||||
| 
 | ||||
|         # modules and extra files | ||||
|         for root, dirs, files in os.walk(curdir): | ||||
|             if to_skip(root): | ||||
|                 continue | ||||
| 
 | ||||
|             if any(root.startswith(path) for path in scanned): | ||||
|                 continue | ||||
| 
 | ||||
|             for file in sorted(files): | ||||
|                 fullpath = os.path.join(root, file) | ||||
|                 if to_skip(fullpath): | ||||
|                     continue | ||||
|                 # single module? | ||||
|                 if os.path.splitext(file)[-1] == '.py': | ||||
|                     modules.append(dotted(fullpath)) | ||||
|                 else: | ||||
|                     extra_files.append(relative(fullpath)) | ||||
| 
 | ||||
|     def _set_multi(self, question, name): | ||||
|         existing_values = self.data[name] | ||||
|         value = ask(question, helptext=_helptext[name]).strip() | ||||
|         if value not in existing_values: | ||||
|             existing_values.append(value) | ||||
| 
 | ||||
|     def set_classifier(self): | ||||
|         self.set_maturity_status(self.classifiers) | ||||
|         self.set_license(self.classifiers) | ||||
|         self.set_other_classifier(self.classifiers) | ||||
| 
 | ||||
|     def set_other_classifier(self, classifiers): | ||||
|         if ask_yn('Do you want to set other trove identifiers', 'n', | ||||
|                   _helptext['trove_generic']) != 'y': | ||||
|             return | ||||
|         self.walk_classifiers(classifiers, [CLASSIFIERS], '') | ||||
| 
 | ||||
|     def walk_classifiers(self, classifiers, trovepath, desc): | ||||
|         trove = trovepath[-1] | ||||
| 
 | ||||
|         if not trove: | ||||
|             return | ||||
| 
 | ||||
|         for key in sorted(trove): | ||||
|             if len(trove[key]) == 0: | ||||
|                 if ask_yn('Add "%s"' % desc[4:] + ' :: ' + key, 'n') == 'y': | ||||
|                     classifiers.add(desc[4:] + ' :: ' + key) | ||||
|                 continue | ||||
| 
 | ||||
|             if ask_yn('Do you want to set items under\n   "%s" (%d sub-items)' | ||||
|                       % (key, len(trove[key])), 'n', | ||||
|                       _helptext['trove_generic']) == 'y': | ||||
|                 self.walk_classifiers(classifiers, trovepath + [trove[key]], | ||||
|                                       desc + ' :: ' + key) | ||||
| 
 | ||||
|     def set_license(self, classifiers): | ||||
|         while True: | ||||
|             license = ask('What license do you use', | ||||
|                           helptext=_helptext['trove_license'], required=False) | ||||
|             if not license: | ||||
|                 return | ||||
| 
 | ||||
|             license_words = license.lower().split(' ') | ||||
|             found_list = [] | ||||
| 
 | ||||
|             for index, licence in LICENCES: | ||||
|                 for word in license_words: | ||||
|                     if word in licence: | ||||
|                         found_list.append(index) | ||||
|                         break | ||||
| 
 | ||||
|             if len(found_list) == 0: | ||||
|                 print('ERROR: Could not find a matching license for "%s"' % | ||||
|                       license) | ||||
|                 continue | ||||
| 
 | ||||
|             question = 'Matching licenses:\n\n' | ||||
| 
 | ||||
|             for index, list_index in enumerate(found_list): | ||||
|                 question += '   %s) %s\n' % (index + 1, | ||||
|                                              _CLASSIFIERS_LIST[list_index]) | ||||
| 
 | ||||
|             question += ('\nType the number of the license you wish to use or ' | ||||
|                          '? to try again:') | ||||
|             choice = ask(question, required=False) | ||||
| 
 | ||||
|             if choice == '?': | ||||
|                 continue | ||||
|             if choice == '': | ||||
|                 return | ||||
| 
 | ||||
|             try: | ||||
|                 index = found_list[int(choice) - 1] | ||||
|             except ValueError: | ||||
|                 print("ERROR: Invalid selection, type a number from the list " | ||||
|                       "above.") | ||||
| 
 | ||||
|             classifiers.add(_CLASSIFIERS_LIST[index]) | ||||
| 
 | ||||
|     def set_maturity_status(self, classifiers): | ||||
|         maturity_name = lambda mat: mat.split('- ')[-1] | ||||
|         maturity_question = '''\ | ||||
|             Please select the project status: | ||||
| 
 | ||||
|             %s | ||||
| 
 | ||||
|             Status''' % '\n'.join('%s - %s' % (i, maturity_name(n)) | ||||
|                                   for i, n in enumerate(PROJECT_MATURITY)) | ||||
|         while True: | ||||
|             choice = ask(dedent(maturity_question), required=False) | ||||
| 
 | ||||
|             if choice: | ||||
|                 try: | ||||
|                     choice = int(choice) - 1 | ||||
|                     key = PROJECT_MATURITY[choice] | ||||
|                     classifiers.add(key) | ||||
|                     return | ||||
|                 except (IndexError, ValueError): | ||||
|                     print("ERROR: Invalid selection, type a single digit " | ||||
|                           "number.") | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|     """Main entry point.""" | ||||
|     program = MainProgram() | ||||
|     # # uncomment when implemented | ||||
|     # if not program.load_existing_setup_script(): | ||||
|     #     program.inspect_directory() | ||||
|     #     program.query_user() | ||||
|     #     program.update_config_file() | ||||
|     # program.write_setup_script() | ||||
|     # packaging.util.cfg_to_args() | ||||
|     program() | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
							
								
								
									
										627
									
								
								Lib/packaging/database.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										627
									
								
								Lib/packaging/database.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,627 @@ | |||
| """PEP 376 implementation.""" | ||||
| 
 | ||||
| import io | ||||
| import os | ||||
| import re | ||||
| import csv | ||||
| import sys | ||||
| import zipimport | ||||
| from hashlib import md5 | ||||
| from packaging import logger | ||||
| from packaging.errors import PackagingError | ||||
| from packaging.version import suggest_normalized_version, VersionPredicate | ||||
| from packaging.metadata import Metadata | ||||
| 
 | ||||
| 
 | ||||
| __all__ = [ | ||||
|     'Distribution', 'EggInfoDistribution', 'distinfo_dirname', | ||||
|     'get_distributions', 'get_distribution', 'get_file_users', | ||||
|     'provides_distribution', 'obsoletes_distribution', | ||||
|     'enable_cache', 'disable_cache', 'clear_cache', | ||||
| ] | ||||
| 
 | ||||
| 
 | ||||
| # TODO update docs | ||||
| 
 | ||||
| DIST_FILES = ('INSTALLER', 'METADATA', 'RECORD', 'REQUESTED', 'RESOURCES') | ||||
| 
 | ||||
| # Cache | ||||
| _cache_name = {}  # maps names to Distribution instances | ||||
| _cache_name_egg = {}  # maps names to EggInfoDistribution instances | ||||
| _cache_path = {}  # maps paths to Distribution instances | ||||
| _cache_path_egg = {}  # maps paths to EggInfoDistribution instances | ||||
| _cache_generated = False  # indicates if .dist-info distributions are cached | ||||
| _cache_generated_egg = False  # indicates if .dist-info and .egg are cached | ||||
| _cache_enabled = True | ||||
| 
 | ||||
| 
 | ||||
| def enable_cache(): | ||||
|     """ | ||||
|     Enables the internal cache. | ||||
| 
 | ||||
|     Note that this function will not clear the cache in any case, for that | ||||
|     functionality see :func:`clear_cache`. | ||||
|     """ | ||||
|     global _cache_enabled | ||||
| 
 | ||||
|     _cache_enabled = True | ||||
| 
 | ||||
| 
 | ||||
| def disable_cache(): | ||||
|     """ | ||||
|     Disables the internal cache. | ||||
| 
 | ||||
|     Note that this function will not clear the cache in any case, for that | ||||
|     functionality see :func:`clear_cache`. | ||||
|     """ | ||||
|     global _cache_enabled | ||||
| 
 | ||||
|     _cache_enabled = False | ||||
| 
 | ||||
| 
 | ||||
| def clear_cache(): | ||||
|     """ Clears the internal cache. """ | ||||
|     global _cache_name, _cache_name_egg, _cache_path, _cache_path_egg, \ | ||||
|         _cache_generated, _cache_generated_egg | ||||
| 
 | ||||
|     _cache_name = {} | ||||
|     _cache_name_egg = {} | ||||
|     _cache_path = {} | ||||
|     _cache_path_egg = {} | ||||
|     _cache_generated = False | ||||
|     _cache_generated_egg = False | ||||
| 
 | ||||
| 
 | ||||
| def _yield_distributions(include_dist, include_egg, paths=sys.path): | ||||
|     """ | ||||
|     Yield .dist-info and .egg(-info) distributions, based on the arguments | ||||
| 
 | ||||
|     :parameter include_dist: yield .dist-info distributions | ||||
|     :parameter include_egg: yield .egg(-info) distributions | ||||
|     """ | ||||
|     for path in paths: | ||||
|         realpath = os.path.realpath(path) | ||||
|         if not os.path.isdir(realpath): | ||||
|             continue | ||||
|         for dir in os.listdir(realpath): | ||||
|             dist_path = os.path.join(realpath, dir) | ||||
|             if include_dist and dir.endswith('.dist-info'): | ||||
|                 yield Distribution(dist_path) | ||||
|             elif include_egg and (dir.endswith('.egg-info') or | ||||
|                                   dir.endswith('.egg')): | ||||
|                 yield EggInfoDistribution(dist_path) | ||||
| 
 | ||||
| 
 | ||||
| def _generate_cache(use_egg_info=False, paths=sys.path): | ||||
|     global _cache_generated, _cache_generated_egg | ||||
| 
 | ||||
|     if _cache_generated_egg or (_cache_generated and not use_egg_info): | ||||
|         return | ||||
|     else: | ||||
|         gen_dist = not _cache_generated | ||||
|         gen_egg = use_egg_info | ||||
| 
 | ||||
|         for dist in _yield_distributions(gen_dist, gen_egg, paths): | ||||
|             if isinstance(dist, Distribution): | ||||
|                 _cache_path[dist.path] = dist | ||||
|                 if not dist.name in _cache_name: | ||||
|                     _cache_name[dist.name] = [] | ||||
|                 _cache_name[dist.name].append(dist) | ||||
|             else: | ||||
|                 _cache_path_egg[dist.path] = dist | ||||
|                 if not dist.name in _cache_name_egg: | ||||
|                     _cache_name_egg[dist.name] = [] | ||||
|                 _cache_name_egg[dist.name].append(dist) | ||||
| 
 | ||||
|         if gen_dist: | ||||
|             _cache_generated = True | ||||
|         if gen_egg: | ||||
|             _cache_generated_egg = True | ||||
| 
 | ||||
| 
 | ||||
| class Distribution: | ||||
|     """Created with the *path* of the ``.dist-info`` directory provided to the | ||||
|     constructor. It reads the metadata contained in ``METADATA`` when it is | ||||
|     instantiated.""" | ||||
| 
 | ||||
|     name = '' | ||||
|     """The name of the distribution.""" | ||||
| 
 | ||||
|     version = '' | ||||
|     """The version of the distribution.""" | ||||
| 
 | ||||
|     metadata = None | ||||
|     """A :class:`packaging.metadata.Metadata` instance loaded with | ||||
|     the distribution's ``METADATA`` file.""" | ||||
| 
 | ||||
|     requested = False | ||||
|     """A boolean that indicates whether the ``REQUESTED`` metadata file is | ||||
|     present (in other words, whether the package was installed by user | ||||
|     request or it was installed as a dependency).""" | ||||
| 
 | ||||
|     def __init__(self, path): | ||||
|         if _cache_enabled and path in _cache_path: | ||||
|             self.metadata = _cache_path[path].metadata | ||||
|         else: | ||||
|             metadata_path = os.path.join(path, 'METADATA') | ||||
|             self.metadata = Metadata(path=metadata_path) | ||||
| 
 | ||||
|         self.name = self.metadata['Name'] | ||||
|         self.version = self.metadata['Version'] | ||||
|         self.path = path | ||||
| 
 | ||||
|         if _cache_enabled and not path in _cache_path: | ||||
|             _cache_path[path] = self | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return '<Distribution %r %s at %r>' % ( | ||||
|             self.name, self.version, self.path) | ||||
| 
 | ||||
|     def _get_records(self, local=False): | ||||
|         with self.get_distinfo_file('RECORD') as record: | ||||
|             record_reader = csv.reader(record, delimiter=',') | ||||
|             # XXX needs an explaining comment | ||||
|             for row in record_reader: | ||||
|                 path, checksum, size = (row[:] + | ||||
|                                         [None for i in range(len(row), 3)]) | ||||
|                 if local: | ||||
|                     path = path.replace('/', os.sep) | ||||
|                     path = os.path.join(sys.prefix, path) | ||||
|                 yield path, checksum, size | ||||
| 
 | ||||
|     def get_resource_path(self, relative_path): | ||||
|         with self.get_distinfo_file('RESOURCES') as resources_file: | ||||
|             resources_reader = csv.reader(resources_file, delimiter=',') | ||||
|             for relative, destination in resources_reader: | ||||
|                 if relative == relative_path: | ||||
|                     return destination | ||||
|         raise KeyError( | ||||
|             'no resource file with relative path %r is installed' % | ||||
|             relative_path) | ||||
| 
 | ||||
|     def list_installed_files(self, local=False): | ||||
|         """ | ||||
|         Iterates over the ``RECORD`` entries and returns a tuple | ||||
|         ``(path, md5, size)`` for each line. If *local* is ``True``, | ||||
|         the returned path is transformed into a local absolute path. | ||||
|         Otherwise the raw value from RECORD is returned. | ||||
| 
 | ||||
|         A local absolute path is an absolute path in which occurrences of | ||||
|         ``'/'`` have been replaced by the system separator given by ``os.sep``. | ||||
| 
 | ||||
|         :parameter local: flag to say if the path should be returned a local | ||||
|                           absolute path | ||||
| 
 | ||||
|         :type local: boolean | ||||
|         :returns: iterator of (path, md5, size) | ||||
|         """ | ||||
|         return self._get_records(local) | ||||
| 
 | ||||
|     def uses(self, path): | ||||
|         """ | ||||
|         Returns ``True`` if path is listed in ``RECORD``. *path* can be a local | ||||
|         absolute path or a relative ``'/'``-separated path. | ||||
| 
 | ||||
|         :rtype: boolean | ||||
|         """ | ||||
|         for p, checksum, size in self._get_records(): | ||||
|             local_absolute = os.path.join(sys.prefix, p) | ||||
|             if path == p or path == local_absolute: | ||||
|                 return True | ||||
|         return False | ||||
| 
 | ||||
|     def get_distinfo_file(self, path, binary=False): | ||||
|         """ | ||||
|         Returns a file located under the ``.dist-info`` directory. Returns a | ||||
|         ``file`` instance for the file pointed by *path*. | ||||
| 
 | ||||
|         :parameter path: a ``'/'``-separated path relative to the | ||||
|                          ``.dist-info`` directory or an absolute path; | ||||
|                          If *path* is an absolute path and doesn't start | ||||
|                          with the ``.dist-info`` directory path, | ||||
|                          a :class:`PackagingError` is raised | ||||
|         :type path: string | ||||
|         :parameter binary: If *binary* is ``True``, opens the file in read-only | ||||
|                            binary mode (``rb``), otherwise opens it in | ||||
|                            read-only mode (``r``). | ||||
|         :rtype: file object | ||||
|         """ | ||||
|         open_flags = 'r' | ||||
|         if binary: | ||||
|             open_flags += 'b' | ||||
| 
 | ||||
|         # Check if it is an absolute path  # XXX use relpath, add tests | ||||
|         if path.find(os.sep) >= 0: | ||||
|             # it's an absolute path? | ||||
|             distinfo_dirname, path = path.split(os.sep)[-2:] | ||||
|             if distinfo_dirname != self.path.split(os.sep)[-1]: | ||||
|                 raise PackagingError( | ||||
|                     'dist-info file %r does not belong to the %r %s ' | ||||
|                     'distribution' % (path, self.name, self.version)) | ||||
| 
 | ||||
|         # The file must be relative | ||||
|         if path not in DIST_FILES: | ||||
|             raise PackagingError('invalid path for a dist-info file: %r' % | ||||
|                                  path) | ||||
| 
 | ||||
|         path = os.path.join(self.path, path) | ||||
|         return open(path, open_flags) | ||||
| 
 | ||||
|     def list_distinfo_files(self, local=False): | ||||
|         """ | ||||
|         Iterates over the ``RECORD`` entries and returns paths for each line if | ||||
|         the path is pointing to a file located in the ``.dist-info`` directory | ||||
|         or one of its subdirectories. | ||||
| 
 | ||||
|         :parameter local: If *local* is ``True``, each returned path is | ||||
|                           transformed into a local absolute path. Otherwise the | ||||
|                           raw value from ``RECORD`` is returned. | ||||
|         :type local: boolean | ||||
|         :returns: iterator of paths | ||||
|         """ | ||||
|         for path, checksum, size in self._get_records(local): | ||||
|             yield path | ||||
| 
 | ||||
|     def __eq__(self, other): | ||||
|         return isinstance(other, Distribution) and self.path == other.path | ||||
| 
 | ||||
|     # See http://docs.python.org/reference/datamodel#object.__hash__ | ||||
|     __hash__ = object.__hash__ | ||||
| 
 | ||||
| 
 | ||||
| class EggInfoDistribution: | ||||
|     """Created with the *path* of the ``.egg-info`` directory or file provided | ||||
|     to the constructor. It reads the metadata contained in the file itself, or | ||||
|     if the given path happens to be a directory, the metadata is read from the | ||||
|     file ``PKG-INFO`` under that directory.""" | ||||
| 
 | ||||
|     name = '' | ||||
|     """The name of the distribution.""" | ||||
| 
 | ||||
|     version = '' | ||||
|     """The version of the distribution.""" | ||||
| 
 | ||||
|     metadata = None | ||||
|     """A :class:`packaging.metadata.Metadata` instance loaded with | ||||
|     the distribution's ``METADATA`` file.""" | ||||
| 
 | ||||
|     _REQUIREMENT = re.compile( | ||||
|         r'(?P<name>[-A-Za-z0-9_.]+)\s*' | ||||
|         r'(?P<first>(?:<|<=|!=|==|>=|>)[-A-Za-z0-9_.]+)?\s*' | ||||
|         r'(?P<rest>(?:\s*,\s*(?:<|<=|!=|==|>=|>)[-A-Za-z0-9_.]+)*)\s*' | ||||
|         r'(?P<extras>\[.*\])?') | ||||
| 
 | ||||
|     def __init__(self, path): | ||||
|         self.path = path | ||||
|         if _cache_enabled and path in _cache_path_egg: | ||||
|             self.metadata = _cache_path_egg[path].metadata | ||||
|             self.name = self.metadata['Name'] | ||||
|             self.version = self.metadata['Version'] | ||||
|             return | ||||
| 
 | ||||
|         # reused from Distribute's pkg_resources | ||||
|         def yield_lines(strs): | ||||
|             """Yield non-empty/non-comment lines of a ``basestring`` | ||||
|             or sequence""" | ||||
|             if isinstance(strs, str): | ||||
|                 for s in strs.splitlines(): | ||||
|                     s = s.strip() | ||||
|                     # skip blank lines/comments | ||||
|                     if s and not s.startswith('#'): | ||||
|                         yield s | ||||
|             else: | ||||
|                 for ss in strs: | ||||
|                     for s in yield_lines(ss): | ||||
|                         yield s | ||||
| 
 | ||||
|         requires = None | ||||
| 
 | ||||
|         if path.endswith('.egg'): | ||||
|             if os.path.isdir(path): | ||||
|                 meta_path = os.path.join(path, 'EGG-INFO', 'PKG-INFO') | ||||
|                 self.metadata = Metadata(path=meta_path) | ||||
|                 try: | ||||
|                     req_path = os.path.join(path, 'EGG-INFO', 'requires.txt') | ||||
|                     with open(req_path, 'r') as fp: | ||||
|                         requires = fp.read() | ||||
|                 except IOError: | ||||
|                     requires = None | ||||
|             else: | ||||
|                 # FIXME handle the case where zipfile is not available | ||||
|                 zipf = zipimport.zipimporter(path) | ||||
|                 fileobj = io.StringIO( | ||||
|                     zipf.get_data('EGG-INFO/PKG-INFO').decode('utf8')) | ||||
|                 self.metadata = Metadata(fileobj=fileobj) | ||||
|                 try: | ||||
|                     requires = zipf.get_data('EGG-INFO/requires.txt') | ||||
|                 except IOError: | ||||
|                     requires = None | ||||
|             self.name = self.metadata['Name'] | ||||
|             self.version = self.metadata['Version'] | ||||
| 
 | ||||
|         elif path.endswith('.egg-info'): | ||||
|             if os.path.isdir(path): | ||||
|                 path = os.path.join(path, 'PKG-INFO') | ||||
|                 try: | ||||
|                     with open(os.path.join(path, 'requires.txt'), 'r') as fp: | ||||
|                         requires = fp.read() | ||||
|                 except IOError: | ||||
|                     requires = None | ||||
|             self.metadata = Metadata(path=path) | ||||
|             self.name = self.metadata['name'] | ||||
|             self.version = self.metadata['Version'] | ||||
| 
 | ||||
|         else: | ||||
|             raise ValueError('path must end with .egg-info or .egg, got %r' % | ||||
|                              path) | ||||
| 
 | ||||
|         if requires is not None: | ||||
|             if self.metadata['Metadata-Version'] == '1.1': | ||||
|                 # we can't have 1.1 metadata *and* Setuptools requires | ||||
|                 for field in ('Obsoletes', 'Requires', 'Provides'): | ||||
|                     del self.metadata[field] | ||||
| 
 | ||||
|         reqs = [] | ||||
| 
 | ||||
|         if requires is not None: | ||||
|             for line in yield_lines(requires): | ||||
|                 if line.startswith('['): | ||||
|                     logger.warning( | ||||
|                         'extensions in requires.txt are not supported ' | ||||
|                         '(used by %r %s)', self.name, self.version) | ||||
|                     break | ||||
|                 else: | ||||
|                     match = self._REQUIREMENT.match(line.strip()) | ||||
|                     if not match: | ||||
|                         # this happens when we encounter extras; since they | ||||
|                         # are written at the end of the file we just exit | ||||
|                         break | ||||
|                     else: | ||||
|                         if match.group('extras'): | ||||
|                             msg = ('extra requirements are not supported ' | ||||
|                                    '(used by %r %s)', self.name, self.version) | ||||
|                             logger.warning(msg, self.name) | ||||
|                         name = match.group('name') | ||||
|                         version = None | ||||
|                         if match.group('first'): | ||||
|                             version = match.group('first') | ||||
|                             if match.group('rest'): | ||||
|                                 version += match.group('rest') | ||||
|                             version = version.replace(' ', '')  # trim spaces | ||||
|                         if version is None: | ||||
|                             reqs.append(name) | ||||
|                         else: | ||||
|                             reqs.append('%s (%s)' % (name, version)) | ||||
| 
 | ||||
|             if len(reqs) > 0: | ||||
|                 self.metadata['Requires-Dist'] += reqs | ||||
| 
 | ||||
|         if _cache_enabled: | ||||
|             _cache_path_egg[self.path] = self | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return '<EggInfoDistribution %r %s at %r>' % ( | ||||
|             self.name, self.version, self.path) | ||||
| 
 | ||||
|     def list_installed_files(self, local=False): | ||||
| 
 | ||||
|         def _md5(path): | ||||
|             with open(path, 'rb') as f: | ||||
|                 content = f.read() | ||||
|             return md5(content).hexdigest() | ||||
| 
 | ||||
|         def _size(path): | ||||
|             return os.stat(path).st_size | ||||
| 
 | ||||
|         path = self.path | ||||
|         if local: | ||||
|             path = path.replace('/', os.sep) | ||||
| 
 | ||||
|         # XXX What about scripts and data files ? | ||||
|         if os.path.isfile(path): | ||||
|             return [(path, _md5(path), _size(path))] | ||||
|         else: | ||||
|             files = [] | ||||
|             for root, dir, files_ in os.walk(path): | ||||
|                 for item in files_: | ||||
|                     item = os.path.join(root, item) | ||||
|                     files.append((item, _md5(item), _size(item))) | ||||
|             return files | ||||
| 
 | ||||
|         return [] | ||||
| 
 | ||||
|     def uses(self, path): | ||||
|         return False | ||||
| 
 | ||||
|     def __eq__(self, other): | ||||
|         return (isinstance(other, EggInfoDistribution) and | ||||
|                 self.path == other.path) | ||||
| 
 | ||||
|     # See http://docs.python.org/reference/datamodel#object.__hash__ | ||||
|     __hash__ = object.__hash__ | ||||
| 
 | ||||
| 
 | ||||
| def distinfo_dirname(name, version): | ||||
|     """ | ||||
|     The *name* and *version* parameters are converted into their | ||||
|     filename-escaped form, i.e. any ``'-'`` characters are replaced | ||||
|     with ``'_'`` other than the one in ``'dist-info'`` and the one | ||||
|     separating the name from the version number. | ||||
| 
 | ||||
|     :parameter name: is converted to a standard distribution name by replacing | ||||
|                      any runs of non- alphanumeric characters with a single | ||||
|                      ``'-'``. | ||||
|     :type name: string | ||||
|     :parameter version: is converted to a standard version string. Spaces | ||||
|                         become dots, and all other non-alphanumeric characters | ||||
|                         (except dots) become dashes, with runs of multiple | ||||
|                         dashes condensed to a single dash. | ||||
|     :type version: string | ||||
|     :returns: directory name | ||||
|     :rtype: string""" | ||||
|     file_extension = '.dist-info' | ||||
|     name = name.replace('-', '_') | ||||
|     normalized_version = suggest_normalized_version(version) | ||||
|     # Because this is a lookup procedure, something will be returned even if | ||||
|     #   it is a version that cannot be normalized | ||||
|     if normalized_version is None: | ||||
|         # Unable to achieve normality? | ||||
|         normalized_version = version | ||||
|     return '-'.join([name, normalized_version]) + file_extension | ||||
| 
 | ||||
| 
 | ||||
| def get_distributions(use_egg_info=False, paths=sys.path): | ||||
|     """ | ||||
|     Provides an iterator that looks for ``.dist-info`` directories in | ||||
|     ``sys.path`` and returns :class:`Distribution` instances for each one of | ||||
|     them. If the parameters *use_egg_info* is ``True``, then the ``.egg-info`` | ||||
|     files and directores are iterated as well. | ||||
| 
 | ||||
|     :rtype: iterator of :class:`Distribution` and :class:`EggInfoDistribution` | ||||
|             instances | ||||
|     """ | ||||
|     if not _cache_enabled: | ||||
|         for dist in _yield_distributions(True, use_egg_info, paths): | ||||
|             yield dist | ||||
|     else: | ||||
|         _generate_cache(use_egg_info, paths) | ||||
| 
 | ||||
|         for dist in _cache_path.values(): | ||||
|             yield dist | ||||
| 
 | ||||
|         if use_egg_info: | ||||
|             for dist in _cache_path_egg.values(): | ||||
|                 yield dist | ||||
| 
 | ||||
| 
 | ||||
| def get_distribution(name, use_egg_info=False, paths=None): | ||||
|     """ | ||||
|     Scans all elements in ``sys.path`` and looks for all directories | ||||
|     ending with ``.dist-info``. Returns a :class:`Distribution` | ||||
|     corresponding to the ``.dist-info`` directory that contains the | ||||
|     ``METADATA`` that matches *name* for the *name* metadata field. | ||||
|     If no distribution exists with the given *name* and the parameter | ||||
|     *use_egg_info* is set to ``True``, then all files and directories ending | ||||
|     with ``.egg-info`` are scanned. A :class:`EggInfoDistribution` instance is | ||||
|     returned if one is found that has metadata that matches *name* for the | ||||
|     *name* metadata field. | ||||
| 
 | ||||
|     This function only returns the first result found, as no more than one | ||||
|     value is expected. If the directory is not found, ``None`` is returned. | ||||
| 
 | ||||
|     :rtype: :class:`Distribution` or :class:`EggInfoDistribution` or None | ||||
|     """ | ||||
|     if paths == None: | ||||
|         paths = sys.path | ||||
| 
 | ||||
|     if not _cache_enabled: | ||||
|         for dist in _yield_distributions(True, use_egg_info, paths): | ||||
|             if dist.name == name: | ||||
|                 return dist | ||||
|     else: | ||||
|         _generate_cache(use_egg_info, paths) | ||||
| 
 | ||||
|         if name in _cache_name: | ||||
|             return _cache_name[name][0] | ||||
|         elif use_egg_info and name in _cache_name_egg: | ||||
|             return _cache_name_egg[name][0] | ||||
|         else: | ||||
|             return None | ||||
| 
 | ||||
| 
 | ||||
| def obsoletes_distribution(name, version=None, use_egg_info=False): | ||||
|     """ | ||||
|     Iterates over all distributions to find which distributions obsolete | ||||
|     *name*. | ||||
| 
 | ||||
|     If a *version* is provided, it will be used to filter the results. | ||||
|     If the argument *use_egg_info* is set to ``True``, then ``.egg-info`` | ||||
|     distributions will be considered as well. | ||||
| 
 | ||||
|     :type name: string | ||||
|     :type version: string | ||||
|     :parameter name: | ||||
|     """ | ||||
|     for dist in get_distributions(use_egg_info): | ||||
|         obsoleted = (dist.metadata['Obsoletes-Dist'] + | ||||
|                      dist.metadata['Obsoletes']) | ||||
|         for obs in obsoleted: | ||||
|             o_components = obs.split(' ', 1) | ||||
|             if len(o_components) == 1 or version is None: | ||||
|                 if name == o_components[0]: | ||||
|                     yield dist | ||||
|                     break | ||||
|             else: | ||||
|                 try: | ||||
|                     predicate = VersionPredicate(obs) | ||||
|                 except ValueError: | ||||
|                     raise PackagingError( | ||||
|                         'distribution %r has ill-formed obsoletes field: ' | ||||
|                         '%r' % (dist.name, obs)) | ||||
|                 if name == o_components[0] and predicate.match(version): | ||||
|                     yield dist | ||||
|                     break | ||||
| 
 | ||||
| 
 | ||||
| def provides_distribution(name, version=None, use_egg_info=False): | ||||
|     """ | ||||
|     Iterates over all distributions to find which distributions provide *name*. | ||||
|     If a *version* is provided, it will be used to filter the results. Scans | ||||
|     all elements in ``sys.path``  and looks for all directories ending with | ||||
|     ``.dist-info``. Returns a :class:`Distribution`  corresponding to the | ||||
|     ``.dist-info`` directory that contains a ``METADATA`` that matches *name* | ||||
|     for the name metadata. If the argument *use_egg_info* is set to ``True``, | ||||
|     then all files and directories ending with ``.egg-info`` are considered | ||||
|     as well and returns an :class:`EggInfoDistribution` instance. | ||||
| 
 | ||||
|     This function only returns the first result found, since no more than | ||||
|     one values are expected. If the directory is not found, returns ``None``. | ||||
| 
 | ||||
|     :parameter version: a version specifier that indicates the version | ||||
|                         required, conforming to the format in ``PEP-345`` | ||||
| 
 | ||||
|     :type name: string | ||||
|     :type version: string | ||||
|     """ | ||||
|     predicate = None | ||||
|     if not version is None: | ||||
|         try: | ||||
|             predicate = VersionPredicate(name + ' (' + version + ')') | ||||
|         except ValueError: | ||||
|             raise PackagingError('invalid name or version: %r, %r' % | ||||
|                                  (name, version)) | ||||
| 
 | ||||
|     for dist in get_distributions(use_egg_info): | ||||
|         provided = dist.metadata['Provides-Dist'] + dist.metadata['Provides'] | ||||
| 
 | ||||
|         for p in provided: | ||||
|             p_components = p.rsplit(' ', 1) | ||||
|             if len(p_components) == 1 or predicate is None: | ||||
|                 if name == p_components[0]: | ||||
|                     yield dist | ||||
|                     break | ||||
|             else: | ||||
|                 p_name, p_ver = p_components | ||||
|                 if len(p_ver) < 2 or p_ver[0] != '(' or p_ver[-1] != ')': | ||||
|                     raise PackagingError( | ||||
|                         'distribution %r has invalid Provides field: %r' % | ||||
|                         (dist.name, p)) | ||||
|                 p_ver = p_ver[1:-1]  # trim off the parenthesis | ||||
|                 if p_name == name and predicate.match(p_ver): | ||||
|                     yield dist | ||||
|                     break | ||||
| 
 | ||||
| 
 | ||||
| def get_file_users(path): | ||||
|     """ | ||||
|     Iterates over all distributions to find out which distributions use | ||||
|     *path*. | ||||
| 
 | ||||
|     :parameter path: can be a local absolute path or a relative | ||||
|                      ``'/'``-separated path. | ||||
|     :type path: string | ||||
|     :rtype: iterator of :class:`Distribution` instances | ||||
|     """ | ||||
|     for dist in get_distributions(): | ||||
|         if dist.uses(path): | ||||
|             yield dist | ||||
							
								
								
									
										270
									
								
								Lib/packaging/depgraph.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										270
									
								
								Lib/packaging/depgraph.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,270 @@ | |||
| """Class and functions dealing with dependencies between distributions. | ||||
| 
 | ||||
| This module provides a DependencyGraph class to represent the | ||||
| dependencies between distributions.  Auxiliary functions can generate a | ||||
| graph, find reverse dependencies, and print a graph in DOT format. | ||||
| """ | ||||
| 
 | ||||
| import sys | ||||
| 
 | ||||
| from io import StringIO | ||||
| from packaging.errors import PackagingError | ||||
| from packaging.version import VersionPredicate, IrrationalVersionError | ||||
| 
 | ||||
| __all__ = ['DependencyGraph', 'generate_graph', 'dependent_dists', | ||||
|            'graph_to_dot'] | ||||
| 
 | ||||
| 
 | ||||
| class DependencyGraph: | ||||
|     """ | ||||
|     Represents a dependency graph between distributions. | ||||
| 
 | ||||
|     The dependency relationships are stored in an ``adjacency_list`` that maps | ||||
|     distributions to a list of ``(other, label)`` tuples where  ``other`` | ||||
|     is a distribution and the edge is labeled with ``label`` (i.e. the version | ||||
|     specifier, if such was provided). Also, for more efficient traversal, for | ||||
|     every distribution ``x``, a list of predecessors is kept in | ||||
|     ``reverse_list[x]``. An edge from distribution ``a`` to | ||||
|     distribution ``b`` means that ``a`` depends on ``b``. If any missing | ||||
|     dependencies are found, they are stored in ``missing``, which is a | ||||
|     dictionary that maps distributions to a list of requirements that were not | ||||
|     provided by any other distributions. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.adjacency_list = {} | ||||
|         self.reverse_list = {} | ||||
|         self.missing = {} | ||||
| 
 | ||||
|     def add_distribution(self, distribution): | ||||
|         """Add the *distribution* to the graph. | ||||
| 
 | ||||
|         :type distribution: :class:`packaging.database.Distribution` or | ||||
|                             :class:`packaging.database.EggInfoDistribution` | ||||
|         """ | ||||
|         self.adjacency_list[distribution] = [] | ||||
|         self.reverse_list[distribution] = [] | ||||
|         self.missing[distribution] = [] | ||||
| 
 | ||||
|     def add_edge(self, x, y, label=None): | ||||
|         """Add an edge from distribution *x* to distribution *y* with the given | ||||
|         *label*. | ||||
| 
 | ||||
|         :type x: :class:`packaging.database.Distribution` or | ||||
|                  :class:`packaging.database.EggInfoDistribution` | ||||
|         :type y: :class:`packaging.database.Distribution` or | ||||
|                  :class:`packaging.database.EggInfoDistribution` | ||||
|         :type label: ``str`` or ``None`` | ||||
|         """ | ||||
|         self.adjacency_list[x].append((y, label)) | ||||
|         # multiple edges are allowed, so be careful | ||||
|         if not x in self.reverse_list[y]: | ||||
|             self.reverse_list[y].append(x) | ||||
| 
 | ||||
|     def add_missing(self, distribution, requirement): | ||||
|         """ | ||||
|         Add a missing *requirement* for the given *distribution*. | ||||
| 
 | ||||
|         :type distribution: :class:`packaging.database.Distribution` or | ||||
|                             :class:`packaging.database.EggInfoDistribution` | ||||
|         :type requirement: ``str`` | ||||
|         """ | ||||
|         self.missing[distribution].append(requirement) | ||||
| 
 | ||||
|     def _repr_dist(self, dist): | ||||
|         return '%s %s' % (dist.name, dist.metadata['Version']) | ||||
| 
 | ||||
|     def repr_node(self, dist, level=1): | ||||
|         """Prints only a subgraph""" | ||||
|         output = [] | ||||
|         output.append(self._repr_dist(dist)) | ||||
|         for other, label in self.adjacency_list[dist]: | ||||
|             dist = self._repr_dist(other) | ||||
|             if label is not None: | ||||
|                 dist = '%s [%s]' % (dist, label) | ||||
|             output.append('    ' * level + str(dist)) | ||||
|             suboutput = self.repr_node(other, level + 1) | ||||
|             subs = suboutput.split('\n') | ||||
|             output.extend(subs[1:]) | ||||
|         return '\n'.join(output) | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         """Representation of the graph""" | ||||
|         output = [] | ||||
|         for dist, adjs in self.adjacency_list.items(): | ||||
|             output.append(self.repr_node(dist)) | ||||
|         return '\n'.join(output) | ||||
| 
 | ||||
| 
 | ||||
| def graph_to_dot(graph, f, skip_disconnected=True): | ||||
|     """Writes a DOT output for the graph to the provided file *f*. | ||||
| 
 | ||||
|     If *skip_disconnected* is set to ``True``, then all distributions | ||||
|     that are not dependent on any other distribution are skipped. | ||||
| 
 | ||||
|     :type f: has to support ``file``-like operations | ||||
|     :type skip_disconnected: ``bool`` | ||||
|     """ | ||||
|     disconnected = [] | ||||
| 
 | ||||
|     f.write("digraph dependencies {\n") | ||||
|     for dist, adjs in graph.adjacency_list.items(): | ||||
|         if len(adjs) == 0 and not skip_disconnected: | ||||
|             disconnected.append(dist) | ||||
|         for other, label in adjs: | ||||
|             if not label is None: | ||||
|                 f.write('"%s" -> "%s" [label="%s"]\n' % | ||||
|                                             (dist.name, other.name, label)) | ||||
|             else: | ||||
|                 f.write('"%s" -> "%s"\n' % (dist.name, other.name)) | ||||
|     if not skip_disconnected and len(disconnected) > 0: | ||||
|         f.write('subgraph disconnected {\n') | ||||
|         f.write('label = "Disconnected"\n') | ||||
|         f.write('bgcolor = red\n') | ||||
| 
 | ||||
|         for dist in disconnected: | ||||
|             f.write('"%s"' % dist.name) | ||||
|             f.write('\n') | ||||
|         f.write('}\n') | ||||
|     f.write('}\n') | ||||
| 
 | ||||
| 
 | ||||
| def generate_graph(dists): | ||||
|     """Generates a dependency graph from the given distributions. | ||||
| 
 | ||||
|     :parameter dists: a list of distributions | ||||
|     :type dists: list of :class:`packaging.database.Distribution` and | ||||
|                  :class:`packaging.database.EggInfoDistribution` instances | ||||
|     :rtype: a :class:`DependencyGraph` instance | ||||
|     """ | ||||
|     graph = DependencyGraph() | ||||
|     provided = {}  # maps names to lists of (version, dist) tuples | ||||
| 
 | ||||
|     # first, build the graph and find out the provides | ||||
|     for dist in dists: | ||||
|         graph.add_distribution(dist) | ||||
|         provides = (dist.metadata['Provides-Dist'] + | ||||
|                     dist.metadata['Provides'] + | ||||
|                     ['%s (%s)' % (dist.name, dist.metadata['Version'])]) | ||||
| 
 | ||||
|         for p in provides: | ||||
|             comps = p.strip().rsplit(" ", 1) | ||||
|             name = comps[0] | ||||
|             version = None | ||||
|             if len(comps) == 2: | ||||
|                 version = comps[1] | ||||
|                 if len(version) < 3 or version[0] != '(' or version[-1] != ')': | ||||
|                     raise PackagingError('Distribution %s has ill formed' \ | ||||
|                                          'provides field: %s' % (dist.name, p)) | ||||
|                 version = version[1:-1]  # trim off parenthesis | ||||
|             if not name in provided: | ||||
|                 provided[name] = [] | ||||
|             provided[name].append((version, dist)) | ||||
| 
 | ||||
|     # now make the edges | ||||
|     for dist in dists: | ||||
|         requires = dist.metadata['Requires-Dist'] + dist.metadata['Requires'] | ||||
|         for req in requires: | ||||
|             try: | ||||
|                 predicate = VersionPredicate(req) | ||||
|             except IrrationalVersionError: | ||||
|                 # XXX compat-mode if cannot read the version | ||||
|                 name = req.split()[0] | ||||
|                 predicate = VersionPredicate(name) | ||||
| 
 | ||||
|             name = predicate.name | ||||
| 
 | ||||
|             if not name in provided: | ||||
|                 graph.add_missing(dist, req) | ||||
|             else: | ||||
|                 matched = False | ||||
|                 for version, provider in provided[name]: | ||||
|                     try: | ||||
|                         match = predicate.match(version) | ||||
|                     except IrrationalVersionError: | ||||
|                         # XXX small compat-mode | ||||
|                         if version.split(' ') == 1: | ||||
|                             match = True | ||||
|                         else: | ||||
|                             match = False | ||||
| 
 | ||||
|                     if match: | ||||
|                         graph.add_edge(dist, provider, req) | ||||
|                         matched = True | ||||
|                         break | ||||
|                 if not matched: | ||||
|                     graph.add_missing(dist, req) | ||||
|     return graph | ||||
| 
 | ||||
| 
 | ||||
| def dependent_dists(dists, dist): | ||||
|     """Recursively generate a list of distributions from *dists* that are | ||||
|     dependent on *dist*. | ||||
| 
 | ||||
|     :param dists: a list of distributions | ||||
|     :param dist: a distribution, member of *dists* for which we are interested | ||||
|     """ | ||||
|     if not dist in dists: | ||||
|         raise ValueError('The given distribution is not a member of the list') | ||||
|     graph = generate_graph(dists) | ||||
| 
 | ||||
|     dep = [dist]  # dependent distributions | ||||
|     fringe = graph.reverse_list[dist]  # list of nodes we should inspect | ||||
| 
 | ||||
|     while not len(fringe) == 0: | ||||
|         node = fringe.pop() | ||||
|         dep.append(node) | ||||
|         for prev in graph.reverse_list[node]: | ||||
|             if not prev in dep: | ||||
|                 fringe.append(prev) | ||||
| 
 | ||||
|     dep.pop(0)  # remove dist from dep, was there to prevent infinite loops | ||||
|     return dep | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|     from packaging.database import get_distributions | ||||
|     tempout = StringIO() | ||||
|     try: | ||||
|         old = sys.stderr | ||||
|         sys.stderr = tempout | ||||
|         try: | ||||
|             dists = list(get_distributions(use_egg_info=True)) | ||||
|             graph = generate_graph(dists) | ||||
|         finally: | ||||
|             sys.stderr = old | ||||
|     except Exception as e: | ||||
|         tempout.seek(0) | ||||
|         tempout = tempout.read() | ||||
|         print('Could not generate the graph\n%s\n%s\n' % (tempout, e)) | ||||
|         sys.exit(1) | ||||
| 
 | ||||
|     for dist, reqs in graph.missing.items(): | ||||
|         if len(reqs) > 0: | ||||
|             print("Warning: Missing dependencies for %s:" % dist.name, | ||||
|                   ", ".join(reqs)) | ||||
|     # XXX replace with argparse | ||||
|     if len(sys.argv) == 1: | ||||
|         print('Dependency graph:') | ||||
|         print('    ' + repr(graph).replace('\n', '\n    ')) | ||||
|         sys.exit(0) | ||||
|     elif len(sys.argv) > 1 and sys.argv[1] in ('-d', '--dot'): | ||||
|         if len(sys.argv) > 2: | ||||
|             filename = sys.argv[2] | ||||
|         else: | ||||
|             filename = 'depgraph.dot' | ||||
| 
 | ||||
|         with open(filename, 'w') as f: | ||||
|             graph_to_dot(graph, f, True) | ||||
|         tempout.seek(0) | ||||
|         tempout = tempout.read() | ||||
|         print(tempout) | ||||
|         print('Dot file written at "%s"' % filename) | ||||
|         sys.exit(0) | ||||
|     else: | ||||
|         print('Supported option: -d [filename]') | ||||
|         sys.exit(1) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
							
								
								
									
										819
									
								
								Lib/packaging/dist.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										819
									
								
								Lib/packaging/dist.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,819 @@ | |||
| """Class representing the distribution being built/installed/etc.""" | ||||
| 
 | ||||
| import os | ||||
| import re | ||||
| 
 | ||||
| from packaging.errors import (PackagingOptionError, PackagingArgError, | ||||
|                               PackagingModuleError, PackagingClassError) | ||||
| from packaging.fancy_getopt import FancyGetopt | ||||
| from packaging.util import strtobool, resolve_name | ||||
| from packaging import logger | ||||
| from packaging.metadata import Metadata | ||||
| from packaging.config import Config | ||||
| from packaging.command import get_command_class, STANDARD_COMMANDS | ||||
| 
 | ||||
| # Regex to define acceptable Packaging command names.  This is not *quite* | ||||
| # the same as a Python NAME -- I don't allow leading underscores.  The fact | ||||
| # that they're very similar is no coincidence; the default naming scheme is | ||||
| # to look for a Python module named after the command. | ||||
| command_re = re.compile(r'^[a-zA-Z]([a-zA-Z0-9_]*)$') | ||||
| 
 | ||||
| USAGE = """\ | ||||
| usage: %(script)s [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...] | ||||
|    or: %(script)s --help [cmd1 cmd2 ...] | ||||
|    or: %(script)s --help-commands | ||||
|    or: %(script)s cmd --help | ||||
| """ | ||||
| 
 | ||||
| 
 | ||||
| def gen_usage(script_name): | ||||
|     script = os.path.basename(script_name) | ||||
|     return USAGE % {'script': script} | ||||
| 
 | ||||
| 
 | ||||
| class Distribution: | ||||
|     """The core of the Packaging.  Most of the work hiding behind 'setup' | ||||
|     is really done within a Distribution instance, which farms the work out | ||||
|     to the Packaging commands specified on the command line. | ||||
| 
 | ||||
|     Setup scripts will almost never instantiate Distribution directly, | ||||
|     unless the 'setup()' function is totally inadequate to their needs. | ||||
|     However, it is conceivable that a setup script might wish to subclass | ||||
|     Distribution for some specialized purpose, and then pass the subclass | ||||
|     to 'setup()' as the 'distclass' keyword argument.  If so, it is | ||||
|     necessary to respect the expectations that 'setup' has of Distribution. | ||||
|     See the code for 'setup()', in run.py, for details. | ||||
|     """ | ||||
| 
 | ||||
|     # 'global_options' describes the command-line options that may be | ||||
|     # supplied to the setup script prior to any actual commands. | ||||
|     # Eg. "./setup.py -n" or "./setup.py --dry-run" both take advantage of | ||||
|     # these global options.  This list should be kept to a bare minimum, | ||||
|     # since every global option is also valid as a command option -- and we | ||||
|     # don't want to pollute the commands with too many options that they | ||||
|     # have minimal control over. | ||||
|     global_options = [ | ||||
|         ('dry-run', 'n', "don't actually do anything"), | ||||
|         ('help', 'h', "show detailed help message"), | ||||
|         ('no-user-cfg', None, 'ignore pydistutils.cfg in your home directory'), | ||||
|     ] | ||||
| 
 | ||||
|     # 'common_usage' is a short (2-3 line) string describing the common | ||||
|     # usage of the setup script. | ||||
|     common_usage = """\ | ||||
| Common commands: (see '--help-commands' for more) | ||||
| 
 | ||||
|   setup.py build      will build the package underneath 'build/' | ||||
|   setup.py install    will install the package | ||||
| """ | ||||
| 
 | ||||
|     # options that are not propagated to the commands | ||||
|     display_options = [ | ||||
|         ('help-commands', None, | ||||
|          "list all available commands"), | ||||
|         ('name', None, | ||||
|          "print package name"), | ||||
|         ('version', 'V', | ||||
|          "print package version"), | ||||
|         ('fullname', None, | ||||
|          "print <package name>-<version>"), | ||||
|         ('author', None, | ||||
|          "print the author's name"), | ||||
|         ('author-email', None, | ||||
|          "print the author's email address"), | ||||
|         ('maintainer', None, | ||||
|          "print the maintainer's name"), | ||||
|         ('maintainer-email', None, | ||||
|          "print the maintainer's email address"), | ||||
|         ('contact', None, | ||||
|          "print the maintainer's name if known, else the author's"), | ||||
|         ('contact-email', None, | ||||
|          "print the maintainer's email address if known, else the author's"), | ||||
|         ('url', None, | ||||
|          "print the URL for this package"), | ||||
|         ('license', None, | ||||
|          "print the license of the package"), | ||||
|         ('licence', None, | ||||
|          "alias for --license"), | ||||
|         ('description', None, | ||||
|          "print the package description"), | ||||
|         ('long-description', None, | ||||
|          "print the long package description"), | ||||
|         ('platforms', None, | ||||
|          "print the list of platforms"), | ||||
|         ('classifier', None, | ||||
|          "print the list of classifiers"), | ||||
|         ('keywords', None, | ||||
|          "print the list of keywords"), | ||||
|         ('provides', None, | ||||
|          "print the list of packages/modules provided"), | ||||
|         ('requires', None, | ||||
|          "print the list of packages/modules required"), | ||||
|         ('obsoletes', None, | ||||
|          "print the list of packages/modules made obsolete"), | ||||
|         ('use-2to3', None, | ||||
|          "use 2to3 to make source python 3.x compatible"), | ||||
|         ('convert-2to3-doctests', None, | ||||
|          "use 2to3 to convert doctests in seperate text files"), | ||||
|         ] | ||||
|     display_option_names = [x[0].replace('-', '_') for x in display_options] | ||||
| 
 | ||||
|     # negative options are options that exclude other options | ||||
|     negative_opt = {} | ||||
| 
 | ||||
|     # -- Creation/initialization methods ------------------------------- | ||||
|     def __init__(self, attrs=None): | ||||
|         """Construct a new Distribution instance: initialize all the | ||||
|         attributes of a Distribution, and then use 'attrs' (a dictionary | ||||
|         mapping attribute names to values) to assign some of those | ||||
|         attributes their "real" values.  (Any attributes not mentioned in | ||||
|         'attrs' will be assigned to some null value: 0, None, an empty list | ||||
|         or dictionary, etc.)  Most importantly, initialize the | ||||
|         'command_obj' attribute to the empty dictionary; this will be | ||||
|         filled in with real command objects by 'parse_command_line()'. | ||||
|         """ | ||||
| 
 | ||||
|         # Default values for our command-line options | ||||
|         self.dry_run = False | ||||
|         self.help = False | ||||
|         for attr in self.display_option_names: | ||||
|             setattr(self, attr, False) | ||||
| 
 | ||||
|         # Store the configuration | ||||
|         self.config = Config(self) | ||||
| 
 | ||||
|         # Store the distribution metadata (name, version, author, and so | ||||
|         # forth) in a separate object -- we're getting to have enough | ||||
|         # information here (and enough command-line options) that it's | ||||
|         # worth it. | ||||
|         self.metadata = Metadata() | ||||
| 
 | ||||
|         # 'cmdclass' maps command names to class objects, so we | ||||
|         # can 1) quickly figure out which class to instantiate when | ||||
|         # we need to create a new command object, and 2) have a way | ||||
|         # for the setup script to override command classes | ||||
|         self.cmdclass = {} | ||||
| 
 | ||||
|         # 'script_name' and 'script_args' are usually set to sys.argv[0] | ||||
|         # and sys.argv[1:], but they can be overridden when the caller is | ||||
|         # not necessarily a setup script run from the command line. | ||||
|         self.script_name = None | ||||
|         self.script_args = None | ||||
| 
 | ||||
|         # 'command_options' is where we store command options between | ||||
|         # parsing them (from config files, the command line, etc.) and when | ||||
|         # they are actually needed -- ie. when the command in question is | ||||
|         # instantiated.  It is a dictionary of dictionaries of 2-tuples: | ||||
|         #   command_options = { command_name : { option : (source, value) } } | ||||
|         self.command_options = {} | ||||
| 
 | ||||
|         # 'dist_files' is the list of (command, pyversion, file) that | ||||
|         # have been created by any dist commands run so far. This is | ||||
|         # filled regardless of whether the run is dry or not. pyversion | ||||
|         # gives sysconfig.get_python_version() if the dist file is | ||||
|         # specific to a Python version, 'any' if it is good for all | ||||
|         # Python versions on the target platform, and '' for a source | ||||
|         # file. pyversion should not be used to specify minimum or | ||||
|         # maximum required Python versions; use the metainfo for that | ||||
|         # instead. | ||||
|         self.dist_files = [] | ||||
| 
 | ||||
|         # These options are really the business of various commands, rather | ||||
|         # than of the Distribution itself.  We provide aliases for them in | ||||
|         # Distribution as a convenience to the developer. | ||||
|         self.packages = [] | ||||
|         self.package_data = {} | ||||
|         self.package_dir = None | ||||
|         self.py_modules = [] | ||||
|         self.libraries = [] | ||||
|         self.headers = [] | ||||
|         self.ext_modules = [] | ||||
|         self.ext_package = None | ||||
|         self.include_dirs = [] | ||||
|         self.extra_path = None | ||||
|         self.scripts = [] | ||||
|         self.data_files = {} | ||||
|         self.password = '' | ||||
|         self.use_2to3 = False | ||||
|         self.convert_2to3_doctests = [] | ||||
|         self.extra_files = [] | ||||
| 
 | ||||
|         # And now initialize bookkeeping stuff that can't be supplied by | ||||
|         # the caller at all.  'command_obj' maps command names to | ||||
|         # Command instances -- that's how we enforce that every command | ||||
|         # class is a singleton. | ||||
|         self.command_obj = {} | ||||
| 
 | ||||
|         # 'have_run' maps command names to boolean values; it keeps track | ||||
|         # of whether we have actually run a particular command, to make it | ||||
|         # cheap to "run" a command whenever we think we might need to -- if | ||||
|         # it's already been done, no need for expensive filesystem | ||||
|         # operations, we just check the 'have_run' dictionary and carry on. | ||||
|         # It's only safe to query 'have_run' for a command class that has | ||||
|         # been instantiated -- a false value will be inserted when the | ||||
|         # command object is created, and replaced with a true value when | ||||
|         # the command is successfully run.  Thus it's probably best to use | ||||
|         # '.get()' rather than a straight lookup. | ||||
|         self.have_run = {} | ||||
| 
 | ||||
|         # Now we'll use the attrs dictionary (ultimately, keyword args from | ||||
|         # the setup script) to possibly override any or all of these | ||||
|         # distribution options. | ||||
| 
 | ||||
|         if attrs is not None: | ||||
|             # Pull out the set of command options and work on them | ||||
|             # specifically.  Note that this order guarantees that aliased | ||||
|             # command options will override any supplied redundantly | ||||
|             # through the general options dictionary. | ||||
|             options = attrs.get('options') | ||||
|             if options is not None: | ||||
|                 del attrs['options'] | ||||
|                 for command, cmd_options in options.items(): | ||||
|                     opt_dict = self.get_option_dict(command) | ||||
|                     for opt, val in cmd_options.items(): | ||||
|                         opt_dict[opt] = ("setup script", val) | ||||
| 
 | ||||
|             # Now work on the rest of the attributes.  Any attribute that's | ||||
|             # not already defined is invalid! | ||||
|             for key, val in attrs.items(): | ||||
|                 if self.metadata.is_metadata_field(key): | ||||
|                     self.metadata[key] = val | ||||
|                 elif hasattr(self, key): | ||||
|                     setattr(self, key, val) | ||||
|                 else: | ||||
|                     logger.warning( | ||||
|                         'unknown argument given to Distribution: %r', key) | ||||
| 
 | ||||
|         # no-user-cfg is handled before other command line args | ||||
|         # because other args override the config files, and this | ||||
|         # one is needed before we can load the config files. | ||||
|         # If attrs['script_args'] wasn't passed, assume false. | ||||
|         # | ||||
|         # This also make sure we just look at the global options | ||||
|         self.want_user_cfg = True | ||||
| 
 | ||||
|         if self.script_args is not None: | ||||
|             for arg in self.script_args: | ||||
|                 if not arg.startswith('-'): | ||||
|                     break | ||||
|                 if arg == '--no-user-cfg': | ||||
|                     self.want_user_cfg = False | ||||
|                     break | ||||
| 
 | ||||
|         self.finalize_options() | ||||
| 
 | ||||
|     def get_option_dict(self, command): | ||||
|         """Get the option dictionary for a given command.  If that | ||||
|         command's option dictionary hasn't been created yet, then create it | ||||
|         and return the new dictionary; otherwise, return the existing | ||||
|         option dictionary. | ||||
|         """ | ||||
|         d = self.command_options.get(command) | ||||
|         if d is None: | ||||
|             d = self.command_options[command] = {} | ||||
|         return d | ||||
| 
 | ||||
|     def get_fullname(self): | ||||
|         return self.metadata.get_fullname() | ||||
| 
 | ||||
|     def dump_option_dicts(self, header=None, commands=None, indent=""): | ||||
|         from pprint import pformat | ||||
| 
 | ||||
|         if commands is None:             # dump all command option dicts | ||||
|             commands = sorted(self.command_options) | ||||
| 
 | ||||
|         if header is not None: | ||||
|             logger.info(indent + header) | ||||
|             indent = indent + "  " | ||||
| 
 | ||||
|         if not commands: | ||||
|             logger.info(indent + "no commands known yet") | ||||
|             return | ||||
| 
 | ||||
|         for cmd_name in commands: | ||||
|             opt_dict = self.command_options.get(cmd_name) | ||||
|             if opt_dict is None: | ||||
|                 logger.info(indent + "no option dict for %r command", | ||||
|                             cmd_name) | ||||
|             else: | ||||
|                 logger.info(indent + "option dict for %r command:", cmd_name) | ||||
|                 out = pformat(opt_dict) | ||||
|                 for line in out.split('\n'): | ||||
|                     logger.info(indent + "  " + line) | ||||
| 
 | ||||
|     # -- Config file finding/parsing methods --------------------------- | ||||
|     # XXX to be removed | ||||
|     def parse_config_files(self, filenames=None): | ||||
|         return self.config.parse_config_files(filenames) | ||||
| 
 | ||||
|     def find_config_files(self): | ||||
|         return self.config.find_config_files() | ||||
| 
 | ||||
|     # -- Command-line parsing methods ---------------------------------- | ||||
| 
 | ||||
|     def parse_command_line(self): | ||||
|         """Parse the setup script's command line, taken from the | ||||
|         'script_args' instance attribute (which defaults to 'sys.argv[1:]' | ||||
|         -- see 'setup()' in run.py).  This list is first processed for | ||||
|         "global options" -- options that set attributes of the Distribution | ||||
|         instance.  Then, it is alternately scanned for Packaging commands | ||||
|         and options for that command.  Each new command terminates the | ||||
|         options for the previous command.  The allowed options for a | ||||
|         command are determined by the 'user_options' attribute of the | ||||
|         command class -- thus, we have to be able to load command classes | ||||
|         in order to parse the command line.  Any error in that 'options' | ||||
|         attribute raises PackagingGetoptError; any error on the | ||||
|         command line raises PackagingArgError.  If no Packaging commands | ||||
|         were found on the command line, raises PackagingArgError.  Return | ||||
|         true if command line was successfully parsed and we should carry | ||||
|         on with executing commands; false if no errors but we shouldn't | ||||
|         execute commands (currently, this only happens if user asks for | ||||
|         help). | ||||
|         """ | ||||
|         # | ||||
|         # We now have enough information to show the Macintosh dialog | ||||
|         # that allows the user to interactively specify the "command line". | ||||
|         # | ||||
|         toplevel_options = self._get_toplevel_options() | ||||
| 
 | ||||
|         # We have to parse the command line a bit at a time -- global | ||||
|         # options, then the first command, then its options, and so on -- | ||||
|         # because each command will be handled by a different class, and | ||||
|         # the options that are valid for a particular class aren't known | ||||
|         # until we have loaded the command class, which doesn't happen | ||||
|         # until we know what the command is. | ||||
| 
 | ||||
|         self.commands = [] | ||||
|         parser = FancyGetopt(toplevel_options + self.display_options) | ||||
|         parser.set_negative_aliases(self.negative_opt) | ||||
|         parser.set_aliases({'licence': 'license'}) | ||||
|         args = parser.getopt(args=self.script_args, object=self) | ||||
|         option_order = parser.get_option_order() | ||||
| 
 | ||||
|         # for display options we return immediately | ||||
|         if self.handle_display_options(option_order): | ||||
|             return | ||||
| 
 | ||||
|         while args: | ||||
|             args = self._parse_command_opts(parser, args) | ||||
|             if args is None:            # user asked for help (and got it) | ||||
|                 return | ||||
| 
 | ||||
|         # Handle the cases of --help as a "global" option, ie. | ||||
|         # "setup.py --help" and "setup.py --help command ...".  For the | ||||
|         # former, we show global options (--dry-run, etc.) | ||||
|         # and display-only options (--name, --version, etc.); for the | ||||
|         # latter, we omit the display-only options and show help for | ||||
|         # each command listed on the command line. | ||||
|         if self.help: | ||||
|             self._show_help(parser, | ||||
|                             display_options=len(self.commands) == 0, | ||||
|                             commands=self.commands) | ||||
|             return | ||||
| 
 | ||||
|         return 1 | ||||
| 
 | ||||
|     def _get_toplevel_options(self): | ||||
|         """Return the non-display options recognized at the top level. | ||||
| 
 | ||||
|         This includes options that are recognized *only* at the top | ||||
|         level as well as options recognized for commands. | ||||
|         """ | ||||
|         return self.global_options | ||||
| 
 | ||||
|     def _parse_command_opts(self, parser, args): | ||||
|         """Parse the command-line options for a single command. | ||||
|         'parser' must be a FancyGetopt instance; 'args' must be the list | ||||
|         of arguments, starting with the current command (whose options | ||||
|         we are about to parse).  Returns a new version of 'args' with | ||||
|         the next command at the front of the list; will be the empty | ||||
|         list if there are no more commands on the command line.  Returns | ||||
|         None if the user asked for help on this command. | ||||
|         """ | ||||
|         # Pull the current command from the head of the command line | ||||
|         command = args[0] | ||||
|         if not command_re.match(command): | ||||
|             raise SystemExit("invalid command name %r" % command) | ||||
|         self.commands.append(command) | ||||
| 
 | ||||
|         # Dig up the command class that implements this command, so we | ||||
|         # 1) know that it's a valid command, and 2) know which options | ||||
|         # it takes. | ||||
|         try: | ||||
|             cmd_class = get_command_class(command) | ||||
|         except PackagingModuleError as msg: | ||||
|             raise PackagingArgError(msg) | ||||
| 
 | ||||
|         # XXX We want to push this in packaging.command | ||||
|         # | ||||
|         # Require that the command class be derived from Command -- want | ||||
|         # to be sure that the basic "command" interface is implemented. | ||||
|         for meth in ('initialize_options', 'finalize_options', 'run'): | ||||
|             if hasattr(cmd_class, meth): | ||||
|                 continue | ||||
|             raise PackagingClassError( | ||||
|                 'command %r must implement %r' % (cmd_class, meth)) | ||||
| 
 | ||||
|         # Also make sure that the command object provides a list of its | ||||
|         # known options. | ||||
|         if not (hasattr(cmd_class, 'user_options') and | ||||
|                 isinstance(cmd_class.user_options, list)): | ||||
|             raise PackagingClassError( | ||||
|                 "command class %s must provide " | ||||
|                 "'user_options' attribute (a list of tuples)" % cmd_class) | ||||
| 
 | ||||
|         # If the command class has a list of negative alias options, | ||||
|         # merge it in with the global negative aliases. | ||||
|         negative_opt = self.negative_opt | ||||
|         if hasattr(cmd_class, 'negative_opt'): | ||||
|             negative_opt = negative_opt.copy() | ||||
|             negative_opt.update(cmd_class.negative_opt) | ||||
| 
 | ||||
|         # Check for help_options in command class.  They have a different | ||||
|         # format (tuple of four) so we need to preprocess them here. | ||||
|         if (hasattr(cmd_class, 'help_options') and | ||||
|             isinstance(cmd_class.help_options, list)): | ||||
|             help_options = cmd_class.help_options[:] | ||||
|         else: | ||||
|             help_options = [] | ||||
| 
 | ||||
|         # All commands support the global options too, just by adding | ||||
|         # in 'global_options'. | ||||
|         parser.set_option_table(self.global_options + | ||||
|                                 cmd_class.user_options + | ||||
|                                 help_options) | ||||
|         parser.set_negative_aliases(negative_opt) | ||||
|         args, opts = parser.getopt(args[1:]) | ||||
|         if hasattr(opts, 'help') and opts.help: | ||||
|             self._show_help(parser, display_options=False, | ||||
|                             commands=[cmd_class]) | ||||
|             return | ||||
| 
 | ||||
|         if (hasattr(cmd_class, 'help_options') and | ||||
|             isinstance(cmd_class.help_options, list)): | ||||
|             help_option_found = False | ||||
|             for help_option, short, desc, func in cmd_class.help_options: | ||||
|                 if hasattr(opts, help_option.replace('-', '_')): | ||||
|                     help_option_found = True | ||||
|                     if hasattr(func, '__call__'): | ||||
|                         func() | ||||
|                     else: | ||||
|                         raise PackagingClassError( | ||||
|                             "invalid help function %r for help option %r: " | ||||
|                             "must be a callable object (function, etc.)" | ||||
|                             % (func, help_option)) | ||||
| 
 | ||||
|             if help_option_found: | ||||
|                 return | ||||
| 
 | ||||
|         # Put the options from the command line into their official | ||||
|         # holding pen, the 'command_options' dictionary. | ||||
|         opt_dict = self.get_option_dict(command) | ||||
|         for name, value in vars(opts).items(): | ||||
|             opt_dict[name] = ("command line", value) | ||||
| 
 | ||||
|         return args | ||||
| 
 | ||||
|     def finalize_options(self): | ||||
|         """Set final values for all the options on the Distribution | ||||
|         instance, analogous to the .finalize_options() method of Command | ||||
|         objects. | ||||
|         """ | ||||
|         if getattr(self, 'convert_2to3_doctests', None): | ||||
|             self.convert_2to3_doctests = [os.path.join(p) | ||||
|                                 for p in self.convert_2to3_doctests] | ||||
|         else: | ||||
|             self.convert_2to3_doctests = [] | ||||
| 
 | ||||
|     def _show_help(self, parser, global_options=True, display_options=True, | ||||
|                    commands=[]): | ||||
|         """Show help for the setup script command line in the form of | ||||
|         several lists of command-line options.  'parser' should be a | ||||
|         FancyGetopt instance; do not expect it to be returned in the | ||||
|         same state, as its option table will be reset to make it | ||||
|         generate the correct help text. | ||||
| 
 | ||||
|         If 'global_options' is true, lists the global options: | ||||
|         --dry-run, etc.  If 'display_options' is true, lists | ||||
|         the "display-only" options: --name, --version, etc.  Finally, | ||||
|         lists per-command help for every command name or command class | ||||
|         in 'commands'. | ||||
|         """ | ||||
|         # late import because of mutual dependence between these modules | ||||
|         from packaging.command.cmd import Command | ||||
| 
 | ||||
|         if global_options: | ||||
|             if display_options: | ||||
|                 options = self._get_toplevel_options() | ||||
|             else: | ||||
|                 options = self.global_options | ||||
|             parser.set_option_table(options) | ||||
|             parser.print_help(self.common_usage + "\nGlobal options:") | ||||
|             print('') | ||||
| 
 | ||||
|         if display_options: | ||||
|             parser.set_option_table(self.display_options) | ||||
|             parser.print_help( | ||||
|                 "Information display options (just display " + | ||||
|                 "information, ignore any commands)") | ||||
|             print('') | ||||
| 
 | ||||
|         for command in self.commands: | ||||
|             if isinstance(command, type) and issubclass(command, Command): | ||||
|                 cls = command | ||||
|             else: | ||||
|                 cls = get_command_class(command) | ||||
|             if (hasattr(cls, 'help_options') and | ||||
|                 isinstance(cls.help_options, list)): | ||||
|                 parser.set_option_table(cls.user_options + cls.help_options) | ||||
|             else: | ||||
|                 parser.set_option_table(cls.user_options) | ||||
|             parser.print_help("Options for %r command:" % cls.__name__) | ||||
|             print('') | ||||
| 
 | ||||
|         print(gen_usage(self.script_name)) | ||||
| 
 | ||||
|     def handle_display_options(self, option_order): | ||||
|         """If there were any non-global "display-only" options | ||||
|         (--help-commands or the metadata display options) on the command | ||||
|         line, display the requested info and return true; else return | ||||
|         false. | ||||
|         """ | ||||
|         # User just wants a list of commands -- we'll print it out and stop | ||||
|         # processing now (ie. if they ran "setup --help-commands foo bar", | ||||
|         # we ignore "foo bar"). | ||||
|         if self.help_commands: | ||||
|             self.print_commands() | ||||
|             print('') | ||||
|             print(gen_usage(self.script_name)) | ||||
|             return 1 | ||||
| 
 | ||||
|         # If user supplied any of the "display metadata" options, then | ||||
|         # display that metadata in the order in which the user supplied the | ||||
|         # metadata options. | ||||
|         any_display_options = False | ||||
|         is_display_option = set() | ||||
|         for option in self.display_options: | ||||
|             is_display_option.add(option[0]) | ||||
| 
 | ||||
|         for opt, val in option_order: | ||||
|             if val and opt in is_display_option: | ||||
|                 opt = opt.replace('-', '_') | ||||
|                 value = self.metadata[opt] | ||||
|                 if opt in ('keywords', 'platform'): | ||||
|                     print(','.join(value)) | ||||
|                 elif opt in ('classifier', 'provides', 'requires', | ||||
|                              'obsoletes'): | ||||
|                     print('\n'.join(value)) | ||||
|                 else: | ||||
|                     print(value) | ||||
|                 any_display_options = True | ||||
| 
 | ||||
|         return any_display_options | ||||
| 
 | ||||
|     def print_command_list(self, commands, header, max_length): | ||||
|         """Print a subset of the list of all commands -- used by | ||||
|         'print_commands()'. | ||||
|         """ | ||||
|         print(header + ":") | ||||
| 
 | ||||
|         for cmd in commands: | ||||
|             cls = self.cmdclass.get(cmd) or get_command_class(cmd) | ||||
|             description = getattr(cls, 'description', | ||||
|                                   '(no description available)') | ||||
| 
 | ||||
|             print("  %-*s  %s" % (max_length, cmd, description)) | ||||
| 
 | ||||
|     def _get_command_groups(self): | ||||
|         """Helper function to retrieve all the command class names divided | ||||
|         into standard commands (listed in | ||||
|         packaging2.command.STANDARD_COMMANDS) and extra commands (given in | ||||
|         self.cmdclass and not standard commands). | ||||
|         """ | ||||
|         extra_commands = [cmd for cmd in self.cmdclass | ||||
|                           if cmd not in STANDARD_COMMANDS] | ||||
|         return STANDARD_COMMANDS, extra_commands | ||||
| 
 | ||||
|     def print_commands(self): | ||||
|         """Print out a help message listing all available commands with a | ||||
|         description of each.  The list is divided into standard commands | ||||
|         (listed in packaging2.command.STANDARD_COMMANDS) and extra commands | ||||
|         (given in self.cmdclass and not standard commands).  The | ||||
|         descriptions come from the command class attribute | ||||
|         'description'. | ||||
|         """ | ||||
|         std_commands, extra_commands = self._get_command_groups() | ||||
|         max_length = 0 | ||||
|         for cmd in (std_commands + extra_commands): | ||||
|             if len(cmd) > max_length: | ||||
|                 max_length = len(cmd) | ||||
| 
 | ||||
|         self.print_command_list(std_commands, | ||||
|                                 "Standard commands", | ||||
|                                 max_length) | ||||
|         if extra_commands: | ||||
|             print() | ||||
|             self.print_command_list(extra_commands, | ||||
|                                     "Extra commands", | ||||
|                                     max_length) | ||||
| 
 | ||||
|     # -- Command class/object methods ---------------------------------- | ||||
| 
 | ||||
|     def get_command_obj(self, command, create=True): | ||||
|         """Return the command object for 'command'.  Normally this object | ||||
|         is cached on a previous call to 'get_command_obj()'; if no command | ||||
|         object for 'command' is in the cache, then we either create and | ||||
|         return it (if 'create' is true) or return None. | ||||
|         """ | ||||
|         cmd_obj = self.command_obj.get(command) | ||||
|         if not cmd_obj and create: | ||||
|             logger.debug("Distribution.get_command_obj(): " \ | ||||
|                          "creating %r command object", command) | ||||
| 
 | ||||
|             cls = get_command_class(command) | ||||
|             cmd_obj = self.command_obj[command] = cls(self) | ||||
|             self.have_run[command] = 0 | ||||
| 
 | ||||
|             # Set any options that were supplied in config files | ||||
|             # or on the command line.  (NB. support for error | ||||
|             # reporting is lame here: any errors aren't reported | ||||
|             # until 'finalize_options()' is called, which means | ||||
|             # we won't report the source of the error.) | ||||
|             options = self.command_options.get(command) | ||||
|             if options: | ||||
|                 self._set_command_options(cmd_obj, options) | ||||
| 
 | ||||
|         return cmd_obj | ||||
| 
 | ||||
|     def _set_command_options(self, command_obj, option_dict=None): | ||||
|         """Set the options for 'command_obj' from 'option_dict'.  Basically | ||||
|         this means copying elements of a dictionary ('option_dict') to | ||||
|         attributes of an instance ('command'). | ||||
| 
 | ||||
|         'command_obj' must be a Command instance.  If 'option_dict' is not | ||||
|         supplied, uses the standard option dictionary for this command | ||||
|         (from 'self.command_options'). | ||||
|         """ | ||||
|         command_name = command_obj.get_command_name() | ||||
|         if option_dict is None: | ||||
|             option_dict = self.get_option_dict(command_name) | ||||
| 
 | ||||
|         logger.debug("  setting options for %r command:", command_name) | ||||
| 
 | ||||
|         for option, (source, value) in option_dict.items(): | ||||
|             logger.debug("    %s = %s (from %s)", option, value, source) | ||||
|             try: | ||||
|                 bool_opts = [x.replace('-', '_') | ||||
|                              for x in command_obj.boolean_options] | ||||
|             except AttributeError: | ||||
|                 bool_opts = [] | ||||
|             try: | ||||
|                 neg_opt = command_obj.negative_opt | ||||
|             except AttributeError: | ||||
|                 neg_opt = {} | ||||
| 
 | ||||
|             try: | ||||
|                 is_string = isinstance(value, str) | ||||
|                 if option in neg_opt and is_string: | ||||
|                     setattr(command_obj, neg_opt[option], not strtobool(value)) | ||||
|                 elif option in bool_opts and is_string: | ||||
|                     setattr(command_obj, option, strtobool(value)) | ||||
|                 elif hasattr(command_obj, option): | ||||
|                     setattr(command_obj, option, value) | ||||
|                 else: | ||||
|                     raise PackagingOptionError( | ||||
|                         "error in %s: command %r has no such option %r" % | ||||
|                         (source, command_name, option)) | ||||
|             except ValueError as msg: | ||||
|                 raise PackagingOptionError(msg) | ||||
| 
 | ||||
|     def get_reinitialized_command(self, command, reinit_subcommands=False): | ||||
|         """Reinitializes a command to the state it was in when first | ||||
|         returned by 'get_command_obj()': ie., initialized but not yet | ||||
|         finalized.  This provides the opportunity to sneak option | ||||
|         values in programmatically, overriding or supplementing | ||||
|         user-supplied values from the config files and command line. | ||||
|         You'll have to re-finalize the command object (by calling | ||||
|         'finalize_options()' or 'ensure_finalized()') before using it for | ||||
|         real. | ||||
| 
 | ||||
|         'command' should be a command name (string) or command object.  If | ||||
|         'reinit_subcommands' is true, also reinitializes the command's | ||||
|         sub-commands, as declared by the 'sub_commands' class attribute (if | ||||
|         it has one).  See the "install_dist" command for an example.  Only | ||||
|         reinitializes the sub-commands that actually matter, ie. those | ||||
|         whose test predicates return true. | ||||
| 
 | ||||
|         Returns the reinitialized command object. | ||||
|         """ | ||||
|         from packaging.command.cmd import Command | ||||
|         if not isinstance(command, Command): | ||||
|             command_name = command | ||||
|             command = self.get_command_obj(command_name) | ||||
|         else: | ||||
|             command_name = command.get_command_name() | ||||
| 
 | ||||
|         if not command.finalized: | ||||
|             return command | ||||
|         command.initialize_options() | ||||
|         self.have_run[command_name] = 0 | ||||
|         command.finalized = False | ||||
|         self._set_command_options(command) | ||||
| 
 | ||||
|         if reinit_subcommands: | ||||
|             for sub in command.get_sub_commands(): | ||||
|                 self.get_reinitialized_command(sub, reinit_subcommands) | ||||
| 
 | ||||
|         return command | ||||
| 
 | ||||
|     # -- Methods that operate on the Distribution ---------------------- | ||||
| 
 | ||||
|     def run_commands(self): | ||||
|         """Run each command that was seen on the setup script command line. | ||||
|         Uses the list of commands found and cache of command objects | ||||
|         created by 'get_command_obj()'. | ||||
|         """ | ||||
|         for cmd in self.commands: | ||||
|             self.run_command(cmd) | ||||
| 
 | ||||
|     # -- Methods that operate on its Commands -------------------------- | ||||
| 
 | ||||
|     def run_command(self, command, options=None): | ||||
|         """Do whatever it takes to run a command (including nothing at all, | ||||
|         if the command has already been run).  Specifically: if we have | ||||
|         already created and run the command named by 'command', return | ||||
|         silently without doing anything.  If the command named by 'command' | ||||
|         doesn't even have a command object yet, create one.  Then invoke | ||||
|         'run()' on that command object (or an existing one). | ||||
|         """ | ||||
|         # Already been here, done that? then return silently. | ||||
|         if self.have_run.get(command): | ||||
|             return | ||||
| 
 | ||||
|         if options is not None: | ||||
|             self.command_options[command] = options | ||||
| 
 | ||||
|         cmd_obj = self.get_command_obj(command) | ||||
|         cmd_obj.ensure_finalized() | ||||
|         self.run_command_hooks(cmd_obj, 'pre_hook') | ||||
|         logger.info("running %s", command) | ||||
|         cmd_obj.run() | ||||
|         self.run_command_hooks(cmd_obj, 'post_hook') | ||||
|         self.have_run[command] = 1 | ||||
| 
 | ||||
|     def run_command_hooks(self, cmd_obj, hook_kind): | ||||
|         """Run hooks registered for that command and phase. | ||||
| 
 | ||||
|         *cmd_obj* is a finalized command object; *hook_kind* is either | ||||
|         'pre_hook' or 'post_hook'. | ||||
|         """ | ||||
|         if hook_kind not in ('pre_hook', 'post_hook'): | ||||
|             raise ValueError('invalid hook kind: %r' % hook_kind) | ||||
| 
 | ||||
|         hooks = getattr(cmd_obj, hook_kind, None) | ||||
| 
 | ||||
|         if hooks is None: | ||||
|             return | ||||
| 
 | ||||
|         for hook in hooks.values(): | ||||
|             if isinstance(hook, str): | ||||
|                 try: | ||||
|                     hook_obj = resolve_name(hook) | ||||
|                 except ImportError as e: | ||||
|                     raise PackagingModuleError(e) | ||||
|             else: | ||||
|                 hook_obj = hook | ||||
| 
 | ||||
|             if not hasattr(hook_obj, '__call__'): | ||||
|                 raise PackagingOptionError('hook %r is not callable' % hook) | ||||
| 
 | ||||
|             logger.info('running %s %s for command %s', | ||||
|                         hook_kind, hook, cmd_obj.get_command_name()) | ||||
|             hook_obj(cmd_obj) | ||||
| 
 | ||||
|     # -- Distribution query methods ------------------------------------ | ||||
|     def has_pure_modules(self): | ||||
|         return len(self.packages or self.py_modules or []) > 0 | ||||
| 
 | ||||
|     def has_ext_modules(self): | ||||
|         return self.ext_modules and len(self.ext_modules) > 0 | ||||
| 
 | ||||
|     def has_c_libraries(self): | ||||
|         return self.libraries and len(self.libraries) > 0 | ||||
| 
 | ||||
|     def has_modules(self): | ||||
|         return self.has_pure_modules() or self.has_ext_modules() | ||||
| 
 | ||||
|     def has_headers(self): | ||||
|         return self.headers and len(self.headers) > 0 | ||||
| 
 | ||||
|     def has_scripts(self): | ||||
|         return self.scripts and len(self.scripts) > 0 | ||||
| 
 | ||||
|     def has_data_files(self): | ||||
|         return self.data_files and len(self.data_files) > 0 | ||||
| 
 | ||||
|     def is_pure(self): | ||||
|         return (self.has_pure_modules() and | ||||
|                 not self.has_ext_modules() and | ||||
|                 not self.has_c_libraries()) | ||||
							
								
								
									
										142
									
								
								Lib/packaging/errors.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								Lib/packaging/errors.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,142 @@ | |||
| """Exceptions used throughout the package. | ||||
| 
 | ||||
| Submodules of packaging may raise exceptions defined in this module as | ||||
| well as standard exceptions; in particular, SystemExit is usually raised | ||||
| for errors that are obviously the end-user's fault (e.g. bad | ||||
| command-line arguments). | ||||
| """ | ||||
| 
 | ||||
| 
 | ||||
| class PackagingError(Exception): | ||||
|     """The root of all Packaging evil.""" | ||||
| 
 | ||||
| 
 | ||||
| class PackagingModuleError(PackagingError): | ||||
|     """Unable to load an expected module, or to find an expected class | ||||
|     within some module (in particular, command modules and classes).""" | ||||
| 
 | ||||
| 
 | ||||
| class PackagingClassError(PackagingError): | ||||
|     """Some command class (or possibly distribution class, if anyone | ||||
|     feels a need to subclass Distribution) is found not to be holding | ||||
|     up its end of the bargain, ie. implementing some part of the | ||||
|     "command "interface.""" | ||||
| 
 | ||||
| 
 | ||||
| class PackagingGetoptError(PackagingError): | ||||
|     """The option table provided to 'fancy_getopt()' is bogus.""" | ||||
| 
 | ||||
| 
 | ||||
| class PackagingArgError(PackagingError): | ||||
|     """Raised by fancy_getopt in response to getopt.error -- ie. an | ||||
|     error in the command line usage.""" | ||||
| 
 | ||||
| 
 | ||||
| class PackagingFileError(PackagingError): | ||||
|     """Any problems in the filesystem: expected file not found, etc. | ||||
|     Typically this is for problems that we detect before IOError or | ||||
|     OSError could be raised.""" | ||||
| 
 | ||||
| 
 | ||||
| class PackagingOptionError(PackagingError): | ||||
|     """Syntactic/semantic errors in command options, such as use of | ||||
|     mutually conflicting options, or inconsistent options, | ||||
|     badly-spelled values, etc.  No distinction is made between option | ||||
|     values originating in the setup script, the command line, config | ||||
|     files, or what-have-you -- but if we *know* something originated in | ||||
|     the setup script, we'll raise PackagingSetupError instead.""" | ||||
| 
 | ||||
| 
 | ||||
| class PackagingSetupError(PackagingError): | ||||
|     """For errors that can be definitely blamed on the setup script, | ||||
|     such as invalid keyword arguments to 'setup()'.""" | ||||
| 
 | ||||
| 
 | ||||
| class PackagingPlatformError(PackagingError): | ||||
|     """We don't know how to do something on the current platform (but | ||||
|     we do know how to do it on some platform) -- eg. trying to compile | ||||
|     C files on a platform not supported by a CCompiler subclass.""" | ||||
| 
 | ||||
| 
 | ||||
| class PackagingExecError(PackagingError): | ||||
|     """Any problems executing an external program (such as the C | ||||
|     compiler, when compiling C files).""" | ||||
| 
 | ||||
| 
 | ||||
| class PackagingInternalError(PackagingError): | ||||
|     """Internal inconsistencies or impossibilities (obviously, this | ||||
|     should never be seen if the code is working!).""" | ||||
| 
 | ||||
| 
 | ||||
| class PackagingTemplateError(PackagingError): | ||||
|     """Syntax error in a file list template.""" | ||||
| 
 | ||||
| 
 | ||||
| class PackagingByteCompileError(PackagingError): | ||||
|     """Byte compile error.""" | ||||
| 
 | ||||
| 
 | ||||
| class PackagingPyPIError(PackagingError): | ||||
|     """Any problem occuring during using the indexes.""" | ||||
| 
 | ||||
| 
 | ||||
| # Exception classes used by the CCompiler implementation classes | ||||
| class CCompilerError(Exception): | ||||
|     """Some compile/link operation failed.""" | ||||
| 
 | ||||
| 
 | ||||
| class PreprocessError(CCompilerError): | ||||
|     """Failure to preprocess one or more C/C++ files.""" | ||||
| 
 | ||||
| 
 | ||||
| class CompileError(CCompilerError): | ||||
|     """Failure to compile one or more C/C++ source files.""" | ||||
| 
 | ||||
| 
 | ||||
| class LibError(CCompilerError): | ||||
|     """Failure to create a static library from one or more C/C++ object | ||||
|     files.""" | ||||
| 
 | ||||
| 
 | ||||
| class LinkError(CCompilerError): | ||||
|     """Failure to link one or more C/C++ object files into an executable | ||||
|     or shared library file.""" | ||||
| 
 | ||||
| 
 | ||||
| class UnknownFileError(CCompilerError): | ||||
|     """Attempt to process an unknown file type.""" | ||||
| 
 | ||||
| 
 | ||||
| class MetadataMissingError(PackagingError): | ||||
|     """A required metadata is missing""" | ||||
| 
 | ||||
| 
 | ||||
| class MetadataConflictError(PackagingError): | ||||
|     """Attempt to read or write metadata fields that are conflictual.""" | ||||
| 
 | ||||
| 
 | ||||
| class MetadataUnrecognizedVersionError(PackagingError): | ||||
|     """Unknown metadata version number.""" | ||||
| 
 | ||||
| 
 | ||||
| class IrrationalVersionError(Exception): | ||||
|     """This is an irrational version.""" | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class HugeMajorVersionNumError(IrrationalVersionError): | ||||
|     """An irrational version because the major version number is huge | ||||
|     (often because a year or date was used). | ||||
| 
 | ||||
|     See `error_on_huge_major_num` option in `NormalizedVersion` for details. | ||||
|     This guard can be disabled by setting that option False. | ||||
|     """ | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class InstallationException(Exception): | ||||
|     """Base exception for installation scripts""" | ||||
| 
 | ||||
| 
 | ||||
| class InstallationConflict(InstallationException): | ||||
|     """Raised when a conflict is detected""" | ||||
							
								
								
									
										451
									
								
								Lib/packaging/fancy_getopt.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										451
									
								
								Lib/packaging/fancy_getopt.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,451 @@ | |||
| """Command line parsing machinery. | ||||
| 
 | ||||
| The FancyGetopt class is a Wrapper around the getopt module that | ||||
| provides the following additional features: | ||||
|   * short and long options are tied together | ||||
|   * options have help strings, so fancy_getopt could potentially | ||||
|     create a complete usage summary | ||||
|   * options set attributes of a passed-in object. | ||||
| 
 | ||||
| It is used under the hood by the command classes.  Do not use directly. | ||||
| """ | ||||
| 
 | ||||
| import getopt | ||||
| import re | ||||
| import sys | ||||
| import string | ||||
| import textwrap | ||||
| 
 | ||||
| from packaging.errors import PackagingGetoptError, PackagingArgError | ||||
| 
 | ||||
| # Much like command_re in packaging.core, this is close to but not quite | ||||
| # the same as a Python NAME -- except, in the spirit of most GNU | ||||
| # utilities, we use '-' in place of '_'.  (The spirit of LISP lives on!) | ||||
| # The similarities to NAME are again not a coincidence... | ||||
| longopt_pat = r'[a-zA-Z](?:[a-zA-Z0-9-]*)' | ||||
| longopt_re = re.compile(r'^%s$' % longopt_pat) | ||||
| 
 | ||||
| # For recognizing "negative alias" options, eg. "quiet=!verbose" | ||||
| neg_alias_re = re.compile("^(%s)=!(%s)$" % (longopt_pat, longopt_pat)) | ||||
| 
 | ||||
| 
 | ||||
| class FancyGetopt: | ||||
|     """Wrapper around the standard 'getopt()' module that provides some | ||||
|     handy extra functionality: | ||||
|       * short and long options are tied together | ||||
|       * options have help strings, and help text can be assembled | ||||
|         from them | ||||
|       * options set attributes of a passed-in object | ||||
|       * boolean options can have "negative aliases" -- eg. if | ||||
|         --quiet is the "negative alias" of --verbose, then "--quiet" | ||||
|         on the command line sets 'verbose' to false | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, option_table=None): | ||||
| 
 | ||||
|         # The option table is (currently) a list of tuples.  The | ||||
|         # tuples may have 3 or four values: | ||||
|         #   (long_option, short_option, help_string [, repeatable]) | ||||
|         # if an option takes an argument, its long_option should have '=' | ||||
|         # appended; short_option should just be a single character, no ':' | ||||
|         # in any case.  If a long_option doesn't have a corresponding | ||||
|         # short_option, short_option should be None.  All option tuples | ||||
|         # must have long options. | ||||
|         self.option_table = option_table | ||||
| 
 | ||||
|         # 'option_index' maps long option names to entries in the option | ||||
|         # table (ie. those 3-tuples). | ||||
|         self.option_index = {} | ||||
|         if self.option_table: | ||||
|             self._build_index() | ||||
| 
 | ||||
|         # 'alias' records (duh) alias options; {'foo': 'bar'} means | ||||
|         # --foo is an alias for --bar | ||||
|         self.alias = {} | ||||
| 
 | ||||
|         # 'negative_alias' keeps track of options that are the boolean | ||||
|         # opposite of some other option | ||||
|         self.negative_alias = {} | ||||
| 
 | ||||
|         # These keep track of the information in the option table.  We | ||||
|         # don't actually populate these structures until we're ready to | ||||
|         # parse the command line, since the 'option_table' passed in here | ||||
|         # isn't necessarily the final word. | ||||
|         self.short_opts = [] | ||||
|         self.long_opts = [] | ||||
|         self.short2long = {} | ||||
|         self.attr_name = {} | ||||
|         self.takes_arg = {} | ||||
| 
 | ||||
|         # And 'option_order' is filled up in 'getopt()'; it records the | ||||
|         # original order of options (and their values) on the command line, | ||||
|         # but expands short options, converts aliases, etc. | ||||
|         self.option_order = [] | ||||
| 
 | ||||
|     def _build_index(self): | ||||
|         self.option_index.clear() | ||||
|         for option in self.option_table: | ||||
|             self.option_index[option[0]] = option | ||||
| 
 | ||||
|     def set_option_table(self, option_table): | ||||
|         self.option_table = option_table | ||||
|         self._build_index() | ||||
| 
 | ||||
|     def add_option(self, long_option, short_option=None, help_string=None): | ||||
|         if long_option in self.option_index: | ||||
|             raise PackagingGetoptError( | ||||
|                   "option conflict: already an option '%s'" % long_option) | ||||
|         else: | ||||
|             option = (long_option, short_option, help_string) | ||||
|             self.option_table.append(option) | ||||
|             self.option_index[long_option] = option | ||||
| 
 | ||||
|     def has_option(self, long_option): | ||||
|         """Return true if the option table for this parser has an | ||||
|         option with long name 'long_option'.""" | ||||
|         return long_option in self.option_index | ||||
| 
 | ||||
|     def _check_alias_dict(self, aliases, what): | ||||
|         assert isinstance(aliases, dict) | ||||
|         for alias, opt in aliases.items(): | ||||
|             if alias not in self.option_index: | ||||
|                 raise PackagingGetoptError( | ||||
|                       ("invalid %s '%s': " | ||||
|                        "option '%s' not defined") % (what, alias, alias)) | ||||
|             if opt not in self.option_index: | ||||
|                 raise PackagingGetoptError( | ||||
|                       ("invalid %s '%s': " | ||||
|                        "aliased option '%s' not defined") % (what, alias, opt)) | ||||
| 
 | ||||
|     def set_aliases(self, alias): | ||||
|         """Set the aliases for this option parser.""" | ||||
|         self._check_alias_dict(alias, "alias") | ||||
|         self.alias = alias | ||||
| 
 | ||||
|     def set_negative_aliases(self, negative_alias): | ||||
|         """Set the negative aliases for this option parser. | ||||
|         'negative_alias' should be a dictionary mapping option names to | ||||
|         option names, both the key and value must already be defined | ||||
|         in the option table.""" | ||||
|         self._check_alias_dict(negative_alias, "negative alias") | ||||
|         self.negative_alias = negative_alias | ||||
| 
 | ||||
|     def _grok_option_table(self): | ||||
|         """Populate the various data structures that keep tabs on the | ||||
|         option table.  Called by 'getopt()' before it can do anything | ||||
|         worthwhile. | ||||
|         """ | ||||
|         self.long_opts = [] | ||||
|         self.short_opts = [] | ||||
|         self.short2long.clear() | ||||
|         self.repeat = {} | ||||
| 
 | ||||
|         for option in self.option_table: | ||||
|             if len(option) == 3: | ||||
|                 integer, short, help = option | ||||
|                 repeat = 0 | ||||
|             elif len(option) == 4: | ||||
|                 integer, short, help, repeat = option | ||||
|             else: | ||||
|                 # the option table is part of the code, so simply | ||||
|                 # assert that it is correct | ||||
|                 raise ValueError("invalid option tuple: %r" % option) | ||||
| 
 | ||||
|             # Type- and value-check the option names | ||||
|             if not isinstance(integer, str) or len(integer) < 2: | ||||
|                 raise PackagingGetoptError( | ||||
|                       ("invalid long option '%s': " | ||||
|                        "must be a string of length >= 2") % integer) | ||||
| 
 | ||||
|             if (not ((short is None) or | ||||
|                      (isinstance(short, str) and len(short) == 1))): | ||||
|                 raise PackagingGetoptError( | ||||
|                       ("invalid short option '%s': " | ||||
|                        "must be a single character or None") % short) | ||||
| 
 | ||||
|             self.repeat[integer] = repeat | ||||
|             self.long_opts.append(integer) | ||||
| 
 | ||||
|             if integer[-1] == '=':             # option takes an argument? | ||||
|                 if short: | ||||
|                     short = short + ':' | ||||
|                 integer = integer[0:-1] | ||||
|                 self.takes_arg[integer] = 1 | ||||
|             else: | ||||
| 
 | ||||
|                 # Is option is a "negative alias" for some other option (eg. | ||||
|                 # "quiet" == "!verbose")? | ||||
|                 alias_to = self.negative_alias.get(integer) | ||||
|                 if alias_to is not None: | ||||
|                     if self.takes_arg[alias_to]: | ||||
|                         raise PackagingGetoptError( | ||||
|                               ("invalid negative alias '%s': " | ||||
|                                "aliased option '%s' takes a value") % \ | ||||
|                                (integer, alias_to)) | ||||
| 
 | ||||
|                     self.long_opts[-1] = integer   # XXX redundant?! | ||||
|                     self.takes_arg[integer] = 0 | ||||
| 
 | ||||
|                 else: | ||||
|                     self.takes_arg[integer] = 0 | ||||
| 
 | ||||
|             # If this is an alias option, make sure its "takes arg" flag is | ||||
|             # the same as the option it's aliased to. | ||||
|             alias_to = self.alias.get(integer) | ||||
|             if alias_to is not None: | ||||
|                 if self.takes_arg[integer] != self.takes_arg[alias_to]: | ||||
|                     raise PackagingGetoptError( | ||||
|                           ("invalid alias '%s': inconsistent with " | ||||
|                            "aliased option '%s' (one of them takes a value, " | ||||
|                            "the other doesn't") % (integer, alias_to)) | ||||
| 
 | ||||
|             # Now enforce some bondage on the long option name, so we can | ||||
|             # later translate it to an attribute name on some object.  Have | ||||
|             # to do this a bit late to make sure we've removed any trailing | ||||
|             # '='. | ||||
|             if not longopt_re.match(integer): | ||||
|                 raise PackagingGetoptError( | ||||
|                       ("invalid long option name '%s' " + | ||||
|                        "(must be letters, numbers, hyphens only") % integer) | ||||
| 
 | ||||
|             self.attr_name[integer] = integer.replace('-', '_') | ||||
|             if short: | ||||
|                 self.short_opts.append(short) | ||||
|                 self.short2long[short[0]] = integer | ||||
| 
 | ||||
|     def getopt(self, args=None, object=None): | ||||
|         """Parse command-line options in args. Store as attributes on object. | ||||
| 
 | ||||
|         If 'args' is None or not supplied, uses 'sys.argv[1:]'.  If | ||||
|         'object' is None or not supplied, creates a new OptionDummy | ||||
|         object, stores option values there, and returns a tuple (args, | ||||
|         object).  If 'object' is supplied, it is modified in place and | ||||
|         'getopt()' just returns 'args'; in both cases, the returned | ||||
|         'args' is a modified copy of the passed-in 'args' list, which | ||||
|         is left untouched. | ||||
|         """ | ||||
|         if args is None: | ||||
|             args = sys.argv[1:] | ||||
|         if object is None: | ||||
|             object = OptionDummy() | ||||
|             created_object = 1 | ||||
|         else: | ||||
|             created_object = 0 | ||||
| 
 | ||||
|         self._grok_option_table() | ||||
| 
 | ||||
|         short_opts = ' '.join(self.short_opts) | ||||
| 
 | ||||
|         try: | ||||
|             opts, args = getopt.getopt(args, short_opts, self.long_opts) | ||||
|         except getopt.error as msg: | ||||
|             raise PackagingArgError(msg) | ||||
| 
 | ||||
|         for opt, val in opts: | ||||
|             if len(opt) == 2 and opt[0] == '-':   # it's a short option | ||||
|                 opt = self.short2long[opt[1]] | ||||
|             else: | ||||
|                 assert len(opt) > 2 and opt[:2] == '--' | ||||
|                 opt = opt[2:] | ||||
| 
 | ||||
|             alias = self.alias.get(opt) | ||||
|             if alias: | ||||
|                 opt = alias | ||||
| 
 | ||||
|             if not self.takes_arg[opt]:     # boolean option? | ||||
|                 assert val == '', "boolean option can't have value" | ||||
|                 alias = self.negative_alias.get(opt) | ||||
|                 if alias: | ||||
|                     opt = alias | ||||
|                     val = 0 | ||||
|                 else: | ||||
|                     val = 1 | ||||
| 
 | ||||
|             attr = self.attr_name[opt] | ||||
|             # The only repeating option at the moment is 'verbose'. | ||||
|             # It has a negative option -q quiet, which should set verbose = 0. | ||||
|             if val and self.repeat.get(attr) is not None: | ||||
|                 val = getattr(object, attr, 0) + 1 | ||||
|             setattr(object, attr, val) | ||||
|             self.option_order.append((opt, val)) | ||||
| 
 | ||||
|         # for opts | ||||
|         if created_object: | ||||
|             return args, object | ||||
|         else: | ||||
|             return args | ||||
| 
 | ||||
|     def get_option_order(self): | ||||
|         """Returns the list of (option, value) tuples processed by the | ||||
|         previous run of 'getopt()'.  Raises RuntimeError if | ||||
|         'getopt()' hasn't been called yet. | ||||
|         """ | ||||
|         if self.option_order is None: | ||||
|             raise RuntimeError("'getopt()' hasn't been called yet") | ||||
|         else: | ||||
|             return self.option_order | ||||
| 
 | ||||
|         return self.option_order | ||||
| 
 | ||||
|     def generate_help(self, header=None): | ||||
|         """Generate help text (a list of strings, one per suggested line of | ||||
|         output) from the option table for this FancyGetopt object. | ||||
|         """ | ||||
|         # Blithely assume the option table is good: probably wouldn't call | ||||
|         # 'generate_help()' unless you've already called 'getopt()'. | ||||
| 
 | ||||
|         # First pass: determine maximum length of long option names | ||||
|         max_opt = 0 | ||||
|         for option in self.option_table: | ||||
|             integer = option[0] | ||||
|             short = option[1] | ||||
|             l = len(integer) | ||||
|             if integer[-1] == '=': | ||||
|                 l = l - 1 | ||||
|             if short is not None: | ||||
|                 l = l + 5                   # " (-x)" where short == 'x' | ||||
|             if l > max_opt: | ||||
|                 max_opt = l | ||||
| 
 | ||||
|         opt_width = max_opt + 2 + 2 + 2     # room for indent + dashes + gutter | ||||
| 
 | ||||
|         # Typical help block looks like this: | ||||
|         #   --foo       controls foonabulation | ||||
|         # Help block for longest option looks like this: | ||||
|         #   --flimflam  set the flim-flam level | ||||
|         # and with wrapped text: | ||||
|         #   --flimflam  set the flim-flam level (must be between | ||||
|         #               0 and 100, except on Tuesdays) | ||||
|         # Options with short names will have the short name shown (but | ||||
|         # it doesn't contribute to max_opt): | ||||
|         #   --foo (-f)  controls foonabulation | ||||
|         # If adding the short option would make the left column too wide, | ||||
|         # we push the explanation off to the next line | ||||
|         #   --flimflam (-l) | ||||
|         #               set the flim-flam level | ||||
|         # Important parameters: | ||||
|         #   - 2 spaces before option block start lines | ||||
|         #   - 2 dashes for each long option name | ||||
|         #   - min. 2 spaces between option and explanation (gutter) | ||||
|         #   - 5 characters (incl. space) for short option name | ||||
| 
 | ||||
|         # Now generate lines of help text.  (If 80 columns were good enough | ||||
|         # for Jesus, then 78 columns are good enough for me!) | ||||
|         line_width = 78 | ||||
|         text_width = line_width - opt_width | ||||
|         big_indent = ' ' * opt_width | ||||
|         if header: | ||||
|             lines = [header] | ||||
|         else: | ||||
|             lines = ['Option summary:'] | ||||
| 
 | ||||
|         for option in self.option_table: | ||||
|             integer, short, help = option[:3] | ||||
|             text = textwrap.wrap(help, text_width) | ||||
| 
 | ||||
|             # Case 1: no short option at all (makes life easy) | ||||
|             if short is None: | ||||
|                 if text: | ||||
|                     lines.append("  --%-*s  %s" % (max_opt, integer, text[0])) | ||||
|                 else: | ||||
|                     lines.append("  --%-*s  " % (max_opt, integer)) | ||||
| 
 | ||||
|             # Case 2: we have a short option, so we have to include it | ||||
|             # just after the long option | ||||
|             else: | ||||
|                 opt_names = "%s (-%s)" % (integer, short) | ||||
|                 if text: | ||||
|                     lines.append("  --%-*s  %s" % | ||||
|                                  (max_opt, opt_names, text[0])) | ||||
|                 else: | ||||
|                     lines.append("  --%-*s" % opt_names) | ||||
| 
 | ||||
|             for l in text[1:]: | ||||
|                 lines.append(big_indent + l) | ||||
| 
 | ||||
|         return lines | ||||
| 
 | ||||
|     def print_help(self, header=None, file=None): | ||||
|         if file is None: | ||||
|             file = sys.stdout | ||||
|         for line in self.generate_help(header): | ||||
|             file.write(line + "\n") | ||||
| 
 | ||||
| 
 | ||||
| def fancy_getopt(options, negative_opt, object, args): | ||||
|     parser = FancyGetopt(options) | ||||
|     parser.set_negative_aliases(negative_opt) | ||||
|     return parser.getopt(args, object) | ||||
| 
 | ||||
| 
 | ||||
| WS_TRANS = str.maketrans(string.whitespace, ' ' * len(string.whitespace)) | ||||
| 
 | ||||
| 
 | ||||
| def wrap_text(text, width): | ||||
|     """Split *text* into lines of no more than *width* characters each. | ||||
| 
 | ||||
|     *text* is a str and *width* an int.  Returns a list of str. | ||||
|     """ | ||||
| 
 | ||||
|     if text is None: | ||||
|         return [] | ||||
|     if len(text) <= width: | ||||
|         return [text] | ||||
| 
 | ||||
|     text = text.expandtabs() | ||||
|     text = text.translate(WS_TRANS) | ||||
| 
 | ||||
|     chunks = re.split(r'( +|-+)', text) | ||||
|     chunks = [_f for _f in chunks if _f]      # ' - ' results in empty strings | ||||
|     lines = [] | ||||
| 
 | ||||
|     while chunks: | ||||
| 
 | ||||
|         cur_line = []                   # list of chunks (to-be-joined) | ||||
|         cur_len = 0                     # length of current line | ||||
| 
 | ||||
|         while chunks: | ||||
|             l = len(chunks[0]) | ||||
|             if cur_len + l <= width:    # can squeeze (at least) this chunk in | ||||
|                 cur_line.append(chunks[0]) | ||||
|                 del chunks[0] | ||||
|                 cur_len = cur_len + l | ||||
|             else:                       # this line is full | ||||
|                 # drop last chunk if all space | ||||
|                 if cur_line and cur_line[-1][0] == ' ': | ||||
|                     del cur_line[-1] | ||||
|                 break | ||||
| 
 | ||||
|         if chunks:                      # any chunks left to process? | ||||
| 
 | ||||
|             # if the current line is still empty, then we had a single | ||||
|             # chunk that's too big too fit on a line -- so we break | ||||
|             # down and break it up at the line width | ||||
|             if cur_len == 0: | ||||
|                 cur_line.append(chunks[0][0:width]) | ||||
|                 chunks[0] = chunks[0][width:] | ||||
| 
 | ||||
|             # all-whitespace chunks at the end of a line can be discarded | ||||
|             # (and we know from the re.split above that if a chunk has | ||||
|             # *any* whitespace, it is *all* whitespace) | ||||
|             if chunks[0][0] == ' ': | ||||
|                 del chunks[0] | ||||
| 
 | ||||
|         # and store this line in the list-of-all-lines -- as a single | ||||
|         # string, of course! | ||||
|         lines.append(''.join(cur_line)) | ||||
| 
 | ||||
|     # while chunks | ||||
| 
 | ||||
|     return lines | ||||
| 
 | ||||
| 
 | ||||
| class OptionDummy: | ||||
|     """Dummy class just used as a place to hold command-line option | ||||
|     values as instance attributes.""" | ||||
| 
 | ||||
|     def __init__(self, options=[]): | ||||
|         """Create a new OptionDummy instance.  The attributes listed in | ||||
|         'options' will be initialized to None.""" | ||||
|         for opt in options: | ||||
|             setattr(self, opt, None) | ||||
							
								
								
									
										483
									
								
								Lib/packaging/install.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										483
									
								
								Lib/packaging/install.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,483 @@ | |||
| """Building blocks for installers. | ||||
| 
 | ||||
| When used as a script, this module installs a release thanks to info | ||||
| obtained from an index (e.g. PyPI), with dependencies. | ||||
| 
 | ||||
| This is a higher-level module built on packaging.database and | ||||
| packaging.pypi. | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| import sys | ||||
| import stat | ||||
| import errno | ||||
| import shutil | ||||
| import logging | ||||
| import tempfile | ||||
| from sysconfig import get_config_var | ||||
| 
 | ||||
| from packaging import logger | ||||
| from packaging.dist import Distribution | ||||
| from packaging.util import (_is_archive_file, ask, get_install_method, | ||||
|                             egginfo_to_distinfo) | ||||
| from packaging.pypi import wrapper | ||||
| from packaging.version import get_version_predicate | ||||
| from packaging.database import get_distributions, get_distribution | ||||
| from packaging.depgraph import generate_graph | ||||
| 
 | ||||
| from packaging.errors import (PackagingError, InstallationException, | ||||
|                               InstallationConflict, CCompilerError) | ||||
| from packaging.pypi.errors import ProjectNotFound, ReleaseNotFound | ||||
| 
 | ||||
| __all__ = ['install_dists', 'install_from_infos', 'get_infos', 'remove', | ||||
|            'install', 'install_local_project'] | ||||
| 
 | ||||
| 
 | ||||
| def _move_files(files, destination): | ||||
|     """Move the list of files in the destination folder, keeping the same | ||||
|     structure. | ||||
| 
 | ||||
|     Return a list of tuple (old, new) emplacement of files | ||||
| 
 | ||||
|     :param files: a list of files to move. | ||||
|     :param destination: the destination directory to put on the files. | ||||
|                         if not defined, create a new one, using mkdtemp | ||||
|     """ | ||||
|     if not destination: | ||||
|         destination = tempfile.mkdtemp() | ||||
| 
 | ||||
|     for old in files: | ||||
|         # not using os.path.join() because basename() might not be | ||||
|         # unique in destination | ||||
|         new = "%s%s" % (destination, old) | ||||
| 
 | ||||
|         # try to make the paths. | ||||
|         try: | ||||
|             os.makedirs(os.path.dirname(new)) | ||||
|         except OSError as e: | ||||
|             if e.errno == errno.EEXIST: | ||||
|                 pass | ||||
|             else: | ||||
|                 raise e | ||||
|         os.rename(old, new) | ||||
|         yield old, new | ||||
| 
 | ||||
| 
 | ||||
| def _run_distutils_install(path): | ||||
|     # backward compat: using setuptools or plain-distutils | ||||
|     cmd = '%s setup.py install --record=%s' | ||||
|     record_file = os.path.join(path, 'RECORD') | ||||
|     os.system(cmd % (sys.executable, record_file)) | ||||
|     if not os.path.exists(record_file): | ||||
|         raise ValueError('failed to install') | ||||
|     else: | ||||
|         egginfo_to_distinfo(record_file, remove_egginfo=True) | ||||
| 
 | ||||
| 
 | ||||
| def _run_setuptools_install(path): | ||||
|     cmd = '%s setup.py install --record=%s --single-version-externally-managed' | ||||
|     record_file = os.path.join(path, 'RECORD') | ||||
|     os.system(cmd % (sys.executable, record_file)) | ||||
|     if not os.path.exists(record_file): | ||||
|         raise ValueError('failed to install') | ||||
|     else: | ||||
|         egginfo_to_distinfo(record_file, remove_egginfo=True) | ||||
| 
 | ||||
| 
 | ||||
| def _run_packaging_install(path): | ||||
|     # XXX check for a valid setup.cfg? | ||||
|     dist = Distribution() | ||||
|     dist.parse_config_files() | ||||
|     try: | ||||
|         dist.run_command('install_dist') | ||||
|     except (IOError, os.error, PackagingError, CCompilerError) as msg: | ||||
|         raise SystemExit("error: " + str(msg)) | ||||
| 
 | ||||
| 
 | ||||
| def _install_dist(dist, path): | ||||
|     """Install a distribution into a path. | ||||
| 
 | ||||
|     This: | ||||
| 
 | ||||
|     * unpack the distribution | ||||
|     * copy the files in "path" | ||||
|     * determine if the distribution is packaging or distutils1. | ||||
|     """ | ||||
|     where = dist.unpack() | ||||
| 
 | ||||
|     if where is None: | ||||
|         raise ValueError('Cannot locate the unpacked archive') | ||||
| 
 | ||||
|     return _run_install_from_archive(where) | ||||
| 
 | ||||
| 
 | ||||
| def install_local_project(path): | ||||
|     """Install a distribution from a source directory. | ||||
| 
 | ||||
|     If the source directory contains a setup.py install using distutils1. | ||||
|     If a setup.cfg is found, install using the install_dist command. | ||||
| 
 | ||||
|     """ | ||||
|     path = os.path.abspath(path) | ||||
|     if os.path.isdir(path): | ||||
|         logger.info('installing from source directory: %s', path) | ||||
|         _run_install_from_dir(path) | ||||
|     elif _is_archive_file(path): | ||||
|         logger.info('installing from archive: %s', path) | ||||
|         _unpacked_dir = tempfile.mkdtemp() | ||||
|         shutil.unpack_archive(path, _unpacked_dir) | ||||
|         _run_install_from_archive(_unpacked_dir) | ||||
|     else: | ||||
|         logger.warning('no projects to install') | ||||
| 
 | ||||
| 
 | ||||
| def _run_install_from_archive(source_dir): | ||||
|     # XXX need a better way | ||||
|     for item in os.listdir(source_dir): | ||||
|         fullpath = os.path.join(source_dir, item) | ||||
|         if os.path.isdir(fullpath): | ||||
|             source_dir = fullpath | ||||
|             break | ||||
|     return _run_install_from_dir(source_dir) | ||||
| 
 | ||||
| 
 | ||||
| install_methods = { | ||||
|     'packaging': _run_packaging_install, | ||||
|     'setuptools': _run_setuptools_install, | ||||
|     'distutils': _run_distutils_install} | ||||
| 
 | ||||
| 
 | ||||
| def _run_install_from_dir(source_dir): | ||||
|     old_dir = os.getcwd() | ||||
|     os.chdir(source_dir) | ||||
|     install_method = get_install_method(source_dir) | ||||
|     func = install_methods[install_method] | ||||
|     try: | ||||
|         func = install_methods[install_method] | ||||
|         return func(source_dir) | ||||
|     finally: | ||||
|         os.chdir(old_dir) | ||||
| 
 | ||||
| 
 | ||||
| def install_dists(dists, path, paths=sys.path): | ||||
|     """Install all distributions provided in dists, with the given prefix. | ||||
| 
 | ||||
|     If an error occurs while installing one of the distributions, uninstall all | ||||
|     the installed distribution (in the context if this function). | ||||
| 
 | ||||
|     Return a list of installed dists. | ||||
| 
 | ||||
|     :param dists: distributions to install | ||||
|     :param path: base path to install distribution in | ||||
|     :param paths: list of paths (defaults to sys.path) to look for info | ||||
|     """ | ||||
|     if not path: | ||||
|         path = tempfile.mkdtemp() | ||||
| 
 | ||||
|     installed_dists = [] | ||||
|     for dist in dists: | ||||
|         logger.info('installing %s %s', dist.name, dist.version) | ||||
|         try: | ||||
|             _install_dist(dist, path) | ||||
|             installed_dists.append(dist) | ||||
|         except Exception as e: | ||||
|             logger.info('failed: %s', e) | ||||
| 
 | ||||
|             # reverting | ||||
|             for installed_dist in installed_dists: | ||||
|                 logger.info('reverting %s', installed_dist) | ||||
|                 _remove_dist(installed_dist, paths) | ||||
|             raise e | ||||
|     return installed_dists | ||||
| 
 | ||||
| 
 | ||||
| def install_from_infos(install_path=None, install=[], remove=[], conflicts=[], | ||||
|                        paths=sys.path): | ||||
|     """Install and remove the given distributions. | ||||
| 
 | ||||
|     The function signature is made to be compatible with the one of get_infos. | ||||
|     The aim of this script is to povide a way to install/remove what's asked, | ||||
|     and to rollback if needed. | ||||
| 
 | ||||
|     So, it's not possible to be in an inconsistant state, it could be either | ||||
|     installed, either uninstalled, not half-installed. | ||||
| 
 | ||||
|     The process follow those steps: | ||||
| 
 | ||||
|         1. Move all distributions that will be removed in a temporary location | ||||
|         2. Install all the distributions that will be installed in a temp. loc. | ||||
|         3. If the installation fails, rollback (eg. move back) those | ||||
|            distributions, or remove what have been installed. | ||||
|         4. Else, move the distributions to the right locations, and remove for | ||||
|            real the distributions thats need to be removed. | ||||
| 
 | ||||
|     :param install_path: the installation path where we want to install the | ||||
|                          distributions. | ||||
|     :param install: list of distributions that will be installed; install_path | ||||
|                     must be provided if this list is not empty. | ||||
|     :param remove: list of distributions that will be removed. | ||||
|     :param conflicts: list of conflicting distributions, eg. that will be in | ||||
|                       conflict once the install and remove distribution will be | ||||
|                       processed. | ||||
|     :param paths: list of paths (defaults to sys.path) to look for info | ||||
|     """ | ||||
|     # first of all, if we have conflicts, stop here. | ||||
|     if conflicts: | ||||
|         raise InstallationConflict(conflicts) | ||||
| 
 | ||||
|     if install and not install_path: | ||||
|         raise ValueError("Distributions are to be installed but `install_path`" | ||||
|                          " is not provided.") | ||||
| 
 | ||||
|     # before removing the files, we will start by moving them away | ||||
|     # then, if any error occurs, we could replace them in the good place. | ||||
|     temp_files = {}  # contains lists of {dist: (old, new)} paths | ||||
|     temp_dir = None | ||||
|     if remove: | ||||
|         temp_dir = tempfile.mkdtemp() | ||||
|         for dist in remove: | ||||
|             files = dist.list_installed_files() | ||||
|             temp_files[dist] = _move_files(files, temp_dir) | ||||
|     try: | ||||
|         if install: | ||||
|             install_dists(install, install_path, paths) | ||||
|     except: | ||||
|         # if an error occurs, put back the files in the right place. | ||||
|         for files in temp_files.values(): | ||||
|             for old, new in files: | ||||
|                 shutil.move(new, old) | ||||
|         if temp_dir: | ||||
|             shutil.rmtree(temp_dir) | ||||
|         # now re-raising | ||||
|         raise | ||||
| 
 | ||||
|     # we can remove them for good | ||||
|     for files in temp_files.values(): | ||||
|         for old, new in files: | ||||
|             os.remove(new) | ||||
|     if temp_dir: | ||||
|         shutil.rmtree(temp_dir) | ||||
| 
 | ||||
| 
 | ||||
| def _get_setuptools_deps(release): | ||||
|     # NotImplementedError | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| def get_infos(requirements, index=None, installed=None, prefer_final=True): | ||||
|     """Return the informations on what's going to be installed and upgraded. | ||||
| 
 | ||||
|     :param requirements: is a *string* containing the requirements for this | ||||
|                          project (for instance "FooBar 1.1" or "BarBaz (<1.2)") | ||||
|     :param index: If an index is specified, use this one, otherwise, use | ||||
|                   :class index.ClientWrapper: to get project metadatas. | ||||
|     :param installed: a list of already installed distributions. | ||||
|     :param prefer_final: when picking up the releases, prefer a "final" one | ||||
|                          over a beta/alpha/etc one. | ||||
| 
 | ||||
|     The results are returned in a dict, containing all the operations | ||||
|     needed to install the given requirements:: | ||||
| 
 | ||||
|         >>> get_install_info("FooBar (<=1.2)") | ||||
|         {'install': [<FooBar 1.1>], 'remove': [], 'conflict': []} | ||||
| 
 | ||||
|     Conflict contains all the conflicting distributions, if there is a | ||||
|     conflict. | ||||
|     """ | ||||
|     # this function does several things: | ||||
|     # 1. get a release specified by the requirements | ||||
|     # 2. gather its metadata, using setuptools compatibility if needed | ||||
|     # 3. compare this tree with what is currently installed on the system, | ||||
|     #    return the requirements of what is missing | ||||
|     # 4. do that recursively and merge back the results | ||||
|     # 5. return a dict containing information about what is needed to install | ||||
|     #    or remove | ||||
| 
 | ||||
|     if not installed: | ||||
|         logger.info('reading installed distributions') | ||||
|         installed = list(get_distributions(use_egg_info=True)) | ||||
| 
 | ||||
|     infos = {'install': [], 'remove': [], 'conflict': []} | ||||
|     # Is a compatible version of the project already installed ? | ||||
|     predicate = get_version_predicate(requirements) | ||||
|     found = False | ||||
| 
 | ||||
|     # check that the project isn't already installed | ||||
|     for installed_project in installed: | ||||
|         # is it a compatible project ? | ||||
|         if predicate.name.lower() != installed_project.name.lower(): | ||||
|             continue | ||||
|         found = True | ||||
|         logger.info('found %s %s', installed_project.name, | ||||
|                     installed_project.metadata['version']) | ||||
| 
 | ||||
|         # if we already have something installed, check it matches the | ||||
|         # requirements | ||||
|         if predicate.match(installed_project.metadata['version']): | ||||
|             return infos | ||||
|         break | ||||
| 
 | ||||
|     if not found: | ||||
|         logger.info('project not installed') | ||||
| 
 | ||||
|     if not index: | ||||
|         index = wrapper.ClientWrapper() | ||||
| 
 | ||||
|     if not installed: | ||||
|         installed = get_distributions(use_egg_info=True) | ||||
| 
 | ||||
|     # Get all the releases that match the requirements | ||||
|     try: | ||||
|         release = index.get_release(requirements) | ||||
|     except (ReleaseNotFound, ProjectNotFound): | ||||
|         raise InstallationException('Release not found: "%s"' % requirements) | ||||
| 
 | ||||
|     if release is None: | ||||
|         logger.info('could not find a matching project') | ||||
|         return infos | ||||
| 
 | ||||
|     metadata = release.fetch_metadata() | ||||
| 
 | ||||
|     # we need to build setuptools deps if any | ||||
|     if 'requires_dist' not in metadata: | ||||
|         metadata['requires_dist'] = _get_setuptools_deps(release) | ||||
| 
 | ||||
|     # build the dependency graph with local and required dependencies | ||||
|     dists = list(installed) | ||||
|     dists.append(release) | ||||
|     depgraph = generate_graph(dists) | ||||
| 
 | ||||
|     # Get what the missing deps are | ||||
|     dists = depgraph.missing[release] | ||||
|     if dists: | ||||
|         logger.info("missing dependencies found, retrieving metadata") | ||||
|         # we have missing deps | ||||
|         for dist in dists: | ||||
|             _update_infos(infos, get_infos(dist, index, installed)) | ||||
| 
 | ||||
|     # Fill in the infos | ||||
|     existing = [d for d in installed if d.name == release.name] | ||||
|     if existing: | ||||
|         infos['remove'].append(existing[0]) | ||||
|         infos['conflict'].extend(depgraph.reverse_list[existing[0]]) | ||||
|     infos['install'].append(release) | ||||
|     return infos | ||||
| 
 | ||||
| 
 | ||||
| def _update_infos(infos, new_infos): | ||||
|     """extends the lists contained in the `info` dict with those contained | ||||
|     in the `new_info` one | ||||
|     """ | ||||
|     for key, value in infos.items(): | ||||
|         if key in new_infos: | ||||
|             infos[key].extend(new_infos[key]) | ||||
| 
 | ||||
| 
 | ||||
| def _remove_dist(dist, paths=sys.path): | ||||
|     remove(dist.name, paths) | ||||
| 
 | ||||
| 
 | ||||
| def remove(project_name, paths=sys.path, auto_confirm=True): | ||||
|     """Removes a single project from the installation""" | ||||
|     dist = get_distribution(project_name, use_egg_info=True, paths=paths) | ||||
|     if dist is None: | ||||
|         raise PackagingError('Distribution "%s" not found' % project_name) | ||||
|     files = dist.list_installed_files(local=True) | ||||
|     rmdirs = [] | ||||
|     rmfiles = [] | ||||
|     tmp = tempfile.mkdtemp(prefix=project_name + '-uninstall') | ||||
|     try: | ||||
|         for file_, md5, size in files: | ||||
|             if os.path.isfile(file_): | ||||
|                 dirname, filename = os.path.split(file_) | ||||
|                 tmpfile = os.path.join(tmp, filename) | ||||
|                 try: | ||||
|                     os.rename(file_, tmpfile) | ||||
|                 finally: | ||||
|                     if not os.path.isfile(file_): | ||||
|                         os.rename(tmpfile, file_) | ||||
|                 if file_ not in rmfiles: | ||||
|                     rmfiles.append(file_) | ||||
|                 if dirname not in rmdirs: | ||||
|                     rmdirs.append(dirname) | ||||
|     finally: | ||||
|         shutil.rmtree(tmp) | ||||
| 
 | ||||
|     logger.info('removing %r: ', project_name) | ||||
| 
 | ||||
|     for file_ in rmfiles: | ||||
|         logger.info('  %s', file_) | ||||
| 
 | ||||
|     # Taken from the pip project | ||||
|     if auto_confirm: | ||||
|         response = 'y' | ||||
|     else: | ||||
|         response = ask('Proceed (y/n)? ', ('y', 'n')) | ||||
| 
 | ||||
|     if response == 'y': | ||||
|         file_count = 0 | ||||
|         for file_ in rmfiles: | ||||
|             os.remove(file_) | ||||
|             file_count += 1 | ||||
| 
 | ||||
|         dir_count = 0 | ||||
|         for dirname in rmdirs: | ||||
|             if not os.path.exists(dirname): | ||||
|                 # could | ||||
|                 continue | ||||
| 
 | ||||
|             files_count = 0 | ||||
|             for root, dir, files in os.walk(dirname): | ||||
|                 files_count += len(files) | ||||
| 
 | ||||
|             if files_count > 0: | ||||
|                 # XXX Warning | ||||
|                 continue | ||||
| 
 | ||||
|             # empty dirs with only empty dirs | ||||
|             if os.stat(dirname).st_mode & stat.S_IWUSR: | ||||
|                 # XXX Add a callable in shutil.rmtree to count | ||||
|                 # the number of deleted elements | ||||
|                 shutil.rmtree(dirname) | ||||
|                 dir_count += 1 | ||||
| 
 | ||||
|         # removing the top path | ||||
|         # XXX count it ? | ||||
|         if os.path.exists(dist.path): | ||||
|             shutil.rmtree(dist.path) | ||||
| 
 | ||||
|         logger.info('success: removed %d files and %d dirs', | ||||
|                     file_count, dir_count) | ||||
| 
 | ||||
| 
 | ||||
| def install(project): | ||||
|     logger.info('getting information about %r', project) | ||||
|     try: | ||||
|         info = get_infos(project) | ||||
|     except InstallationException: | ||||
|         logger.info('cound not find %r', project) | ||||
|         return | ||||
| 
 | ||||
|     if info['install'] == []: | ||||
|         logger.info('nothing to install') | ||||
|         return | ||||
| 
 | ||||
|     install_path = get_config_var('base') | ||||
|     try: | ||||
|         install_from_infos(install_path, | ||||
|                            info['install'], info['remove'], info['conflict']) | ||||
| 
 | ||||
|     except InstallationConflict as e: | ||||
|         if logger.isEnabledFor(logging.INFO): | ||||
|             projects = ['%s %s' % (p.name, p.version) for p in e.args[0]] | ||||
|             logger.info('%r conflicts with %s', project, ','.join(projects)) | ||||
| 
 | ||||
| 
 | ||||
| def _main(**attrs): | ||||
|     if 'script_args' not in attrs: | ||||
|         import sys | ||||
|         attrs['requirements'] = sys.argv[1] | ||||
|     get_infos(**attrs) | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     _main() | ||||
							
								
								
									
										372
									
								
								Lib/packaging/manifest.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										372
									
								
								Lib/packaging/manifest.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,372 @@ | |||
| """Class representing the list of files in a distribution. | ||||
| 
 | ||||
| The Manifest class can be used to: | ||||
| 
 | ||||
|  - read or write a MANIFEST file | ||||
|  - read a template file and find out the file list | ||||
| """ | ||||
| # XXX todo: document + add tests | ||||
| import re | ||||
| import os | ||||
| import fnmatch | ||||
| 
 | ||||
| from packaging import logger | ||||
| from packaging.util import write_file, convert_path | ||||
| from packaging.errors import (PackagingTemplateError, | ||||
|                               PackagingInternalError) | ||||
| 
 | ||||
| __all__ = ['Manifest'] | ||||
| 
 | ||||
| # a \ followed by some spaces + EOL | ||||
| _COLLAPSE_PATTERN = re.compile('\\\w*\n', re.M) | ||||
| _COMMENTED_LINE = re.compile('#.*?(?=\n)|\n(?=$)', re.M | re.S) | ||||
| 
 | ||||
| 
 | ||||
| class Manifest(object): | ||||
|     """A list of files built by on exploring the filesystem and filtered by | ||||
|     applying various patterns to what we find there. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.allfiles = None | ||||
|         self.files = [] | ||||
| 
 | ||||
|     # | ||||
|     # Public API | ||||
|     # | ||||
| 
 | ||||
|     def findall(self, dir=os.curdir): | ||||
|         self.allfiles = _findall(dir) | ||||
| 
 | ||||
|     def append(self, item): | ||||
|         self.files.append(item) | ||||
| 
 | ||||
|     def extend(self, items): | ||||
|         self.files.extend(items) | ||||
| 
 | ||||
|     def sort(self): | ||||
|         # Not a strict lexical sort! | ||||
|         self.files = [os.path.join(*path_tuple) for path_tuple in | ||||
|                       sorted(os.path.split(path) for path in self.files)] | ||||
| 
 | ||||
|     def clear(self): | ||||
|         """Clear all collected files.""" | ||||
|         self.files = [] | ||||
|         if self.allfiles is not None: | ||||
|             self.allfiles = [] | ||||
| 
 | ||||
|     def remove_duplicates(self): | ||||
|         # Assumes list has been sorted! | ||||
|         for i in range(len(self.files) - 1, 0, -1): | ||||
|             if self.files[i] == self.files[i - 1]: | ||||
|                 del self.files[i] | ||||
| 
 | ||||
|     def read_template(self, path_or_file): | ||||
|         """Read and parse a manifest template file. | ||||
|         'path' can be a path or a file-like object. | ||||
| 
 | ||||
|         Updates the list accordingly. | ||||
|         """ | ||||
|         if isinstance(path_or_file, str): | ||||
|             f = open(path_or_file) | ||||
|         else: | ||||
|             f = path_or_file | ||||
| 
 | ||||
|         try: | ||||
|             content = f.read() | ||||
|             # first, let's unwrap collapsed lines | ||||
|             content = _COLLAPSE_PATTERN.sub('', content) | ||||
|             # next, let's remove commented lines and empty lines | ||||
|             content = _COMMENTED_LINE.sub('', content) | ||||
| 
 | ||||
|             # now we have our cleaned up lines | ||||
|             lines = [line.strip() for line in content.split('\n')] | ||||
|         finally: | ||||
|             f.close() | ||||
| 
 | ||||
|         for line in lines: | ||||
|             if line == '': | ||||
|                 continue | ||||
|             try: | ||||
|                 self._process_template_line(line) | ||||
|             except PackagingTemplateError as msg: | ||||
|                 logger.warning("%s, %s", path_or_file, msg) | ||||
| 
 | ||||
|     def write(self, path): | ||||
|         """Write the file list in 'self.filelist' (presumably as filled in | ||||
|         by 'add_defaults()' and 'read_template()') to the manifest file | ||||
|         named by 'self.manifest'. | ||||
|         """ | ||||
|         if os.path.isfile(path): | ||||
|             with open(path) as fp: | ||||
|                 first_line = fp.readline() | ||||
| 
 | ||||
|             if first_line != '# file GENERATED by packaging, do NOT edit\n': | ||||
|                 logger.info("not writing to manually maintained " | ||||
|                             "manifest file %r", path) | ||||
|                 return | ||||
| 
 | ||||
|         self.sort() | ||||
|         self.remove_duplicates() | ||||
|         content = self.files[:] | ||||
|         content.insert(0, '# file GENERATED by packaging, do NOT edit') | ||||
|         logger.info("writing manifest file %r", path) | ||||
|         write_file(path, content) | ||||
| 
 | ||||
|     def read(self, path): | ||||
|         """Read the manifest file (named by 'self.manifest') and use it to | ||||
|         fill in 'self.filelist', the list of files to include in the source | ||||
|         distribution. | ||||
|         """ | ||||
|         logger.info("reading manifest file %r", path) | ||||
|         with open(path) as manifest: | ||||
|             for line in manifest.readlines(): | ||||
|                 self.append(line) | ||||
| 
 | ||||
|     def exclude_pattern(self, pattern, anchor=True, prefix=None, | ||||
|                         is_regex=False): | ||||
|         """Remove strings (presumably filenames) from 'files' that match | ||||
|         'pattern'. | ||||
| 
 | ||||
|         Other parameters are the same as for 'include_pattern()', above. | ||||
|         The list 'self.files' is modified in place. Return True if files are | ||||
|         found. | ||||
|         """ | ||||
|         files_found = False | ||||
|         pattern_re = _translate_pattern(pattern, anchor, prefix, is_regex) | ||||
|         for i in range(len(self.files) - 1, -1, -1): | ||||
|             if pattern_re.search(self.files[i]): | ||||
|                 del self.files[i] | ||||
|                 files_found = True | ||||
| 
 | ||||
|         return files_found | ||||
| 
 | ||||
|     # | ||||
|     # Private API | ||||
|     # | ||||
| 
 | ||||
|     def _parse_template_line(self, line): | ||||
|         words = line.split() | ||||
|         if len(words) == 1: | ||||
|             # no action given, let's use the default 'include' | ||||
|             words.insert(0, 'include') | ||||
| 
 | ||||
|         action = words[0] | ||||
|         patterns = dir = dir_pattern = None | ||||
| 
 | ||||
|         if action in ('include', 'exclude', | ||||
|                       'global-include', 'global-exclude'): | ||||
|             if len(words) < 2: | ||||
|                 raise PackagingTemplateError( | ||||
|                       "%r expects <pattern1> <pattern2> ..." % action) | ||||
| 
 | ||||
|             patterns = [convert_path(word) for word in words[1:]] | ||||
| 
 | ||||
|         elif action in ('recursive-include', 'recursive-exclude'): | ||||
|             if len(words) < 3: | ||||
|                 raise PackagingTemplateError( | ||||
|                       "%r expects <dir> <pattern1> <pattern2> ..." % action) | ||||
| 
 | ||||
|             dir = convert_path(words[1]) | ||||
|             patterns = [convert_path(word) for word in words[2:]] | ||||
| 
 | ||||
|         elif action in ('graft', 'prune'): | ||||
|             if len(words) != 2: | ||||
|                 raise PackagingTemplateError( | ||||
|                      "%r expects a single <dir_pattern>" % action) | ||||
| 
 | ||||
|             dir_pattern = convert_path(words[1]) | ||||
| 
 | ||||
|         else: | ||||
|             raise PackagingTemplateError("unknown action %r" % action) | ||||
| 
 | ||||
|         return action, patterns, dir, dir_pattern | ||||
| 
 | ||||
|     def _process_template_line(self, line): | ||||
|         # Parse the line: split it up, make sure the right number of words | ||||
|         # is there, and return the relevant words.  'action' is always | ||||
|         # defined: it's the first word of the line.  Which of the other | ||||
|         # three are defined depends on the action; it'll be either | ||||
|         # patterns, (dir and patterns), or (dir_pattern). | ||||
|         action, patterns, dir, dir_pattern = self._parse_template_line(line) | ||||
| 
 | ||||
|         # OK, now we know that the action is valid and we have the | ||||
|         # right number of words on the line for that action -- so we | ||||
|         # can proceed with minimal error-checking. | ||||
|         if action == 'include': | ||||
|             for pattern in patterns: | ||||
|                 if not self._include_pattern(pattern, anchor=True): | ||||
|                     logger.warning("no files found matching %r", pattern) | ||||
| 
 | ||||
|         elif action == 'exclude': | ||||
|             for pattern in patterns: | ||||
|                 if not self.exclude_pattern(pattern, anchor=True): | ||||
|                     logger.warning("no previously-included files " | ||||
|                                    "found matching %r", pattern) | ||||
| 
 | ||||
|         elif action == 'global-include': | ||||
|             for pattern in patterns: | ||||
|                 if not self._include_pattern(pattern, anchor=False): | ||||
|                     logger.warning("no files found matching %r " | ||||
|                                    "anywhere in distribution", pattern) | ||||
| 
 | ||||
|         elif action == 'global-exclude': | ||||
|             for pattern in patterns: | ||||
|                 if not self.exclude_pattern(pattern, anchor=False): | ||||
|                     logger.warning("no previously-included files " | ||||
|                                    "matching %r found anywhere in " | ||||
|                                    "distribution", pattern) | ||||
| 
 | ||||
|         elif action == 'recursive-include': | ||||
|             for pattern in patterns: | ||||
|                 if not self._include_pattern(pattern, prefix=dir): | ||||
|                     logger.warning("no files found matching %r " | ||||
|                                    "under directory %r", pattern, dir) | ||||
| 
 | ||||
|         elif action == 'recursive-exclude': | ||||
|             for pattern in patterns: | ||||
|                 if not self.exclude_pattern(pattern, prefix=dir): | ||||
|                     logger.warning("no previously-included files " | ||||
|                                    "matching %r found under directory %r", | ||||
|                                    pattern, dir) | ||||
| 
 | ||||
|         elif action == 'graft': | ||||
|             if not self._include_pattern(None, prefix=dir_pattern): | ||||
|                 logger.warning("no directories found matching %r", | ||||
|                                dir_pattern) | ||||
| 
 | ||||
|         elif action == 'prune': | ||||
|             if not self.exclude_pattern(None, prefix=dir_pattern): | ||||
|                 logger.warning("no previously-included directories found " | ||||
|                                "matching %r", dir_pattern) | ||||
|         else: | ||||
|             raise PackagingInternalError( | ||||
|                 "this cannot happen: invalid action %r" % action) | ||||
| 
 | ||||
|     def _include_pattern(self, pattern, anchor=True, prefix=None, | ||||
|                          is_regex=False): | ||||
|         """Select strings (presumably filenames) from 'self.files' that | ||||
|         match 'pattern', a Unix-style wildcard (glob) pattern. | ||||
| 
 | ||||
|         Patterns are not quite the same as implemented by the 'fnmatch' | ||||
|         module: '*' and '?'  match non-special characters, where "special" | ||||
|         is platform-dependent: slash on Unix; colon, slash, and backslash on | ||||
|         DOS/Windows; and colon on Mac OS. | ||||
| 
 | ||||
|         If 'anchor' is true (the default), then the pattern match is more | ||||
|         stringent: "*.py" will match "foo.py" but not "foo/bar.py".  If | ||||
|         'anchor' is false, both of these will match. | ||||
| 
 | ||||
|         If 'prefix' is supplied, then only filenames starting with 'prefix' | ||||
|         (itself a pattern) and ending with 'pattern', with anything in between | ||||
|         them, will match.  'anchor' is ignored in this case. | ||||
| 
 | ||||
|         If 'is_regex' is true, 'anchor' and 'prefix' are ignored, and | ||||
|         'pattern' is assumed to be either a string containing a regex or a | ||||
|         regex object -- no translation is done, the regex is just compiled | ||||
|         and used as-is. | ||||
| 
 | ||||
|         Selected strings will be added to self.files. | ||||
| 
 | ||||
|         Return True if files are found. | ||||
|         """ | ||||
|         files_found = False | ||||
|         pattern_re = _translate_pattern(pattern, anchor, prefix, is_regex) | ||||
| 
 | ||||
|         # delayed loading of allfiles list | ||||
|         if self.allfiles is None: | ||||
|             self.findall() | ||||
| 
 | ||||
|         for name in self.allfiles: | ||||
|             if pattern_re.search(name): | ||||
|                 self.files.append(name) | ||||
|                 files_found = True | ||||
| 
 | ||||
|         return files_found | ||||
| 
 | ||||
| 
 | ||||
| # | ||||
| # Utility functions | ||||
| # | ||||
| def _findall(dir=os.curdir): | ||||
|     """Find all files under 'dir' and return the list of full filenames | ||||
|     (relative to 'dir'). | ||||
|     """ | ||||
|     from stat import S_ISREG, S_ISDIR, S_ISLNK | ||||
| 
 | ||||
|     list = [] | ||||
|     stack = [dir] | ||||
|     pop = stack.pop | ||||
|     push = stack.append | ||||
| 
 | ||||
|     while stack: | ||||
|         dir = pop() | ||||
|         names = os.listdir(dir) | ||||
| 
 | ||||
|         for name in names: | ||||
|             if dir != os.curdir:        # avoid the dreaded "./" syndrome | ||||
|                 fullname = os.path.join(dir, name) | ||||
|             else: | ||||
|                 fullname = name | ||||
| 
 | ||||
|             # Avoid excess stat calls -- just one will do, thank you! | ||||
|             stat = os.stat(fullname) | ||||
|             mode = stat.st_mode | ||||
|             if S_ISREG(mode): | ||||
|                 list.append(fullname) | ||||
|             elif S_ISDIR(mode) and not S_ISLNK(mode): | ||||
|                 push(fullname) | ||||
| 
 | ||||
|     return list | ||||
| 
 | ||||
| 
 | ||||
| def _glob_to_re(pattern): | ||||
|     """Translate a shell-like glob pattern to a regular expression. | ||||
| 
 | ||||
|     Return a string containing the regex.  Differs from | ||||
|     'fnmatch.translate()' in that '*' does not match "special characters" | ||||
|     (which are platform-specific). | ||||
|     """ | ||||
|     pattern_re = fnmatch.translate(pattern) | ||||
| 
 | ||||
|     # '?' and '*' in the glob pattern become '.' and '.*' in the RE, which | ||||
|     # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix, | ||||
|     # and by extension they shouldn't match such "special characters" under | ||||
|     # any OS.  So change all non-escaped dots in the RE to match any | ||||
|     # character except the special characters. | ||||
|     # XXX currently the "special characters" are just slash -- i.e. this is | ||||
|     # Unix-only. | ||||
|     pattern_re = re.sub(r'((?<!\\)(\\\\)*)\.', r'\1[^/]', pattern_re) | ||||
| 
 | ||||
|     return pattern_re | ||||
| 
 | ||||
| 
 | ||||
| def _translate_pattern(pattern, anchor=True, prefix=None, is_regex=False): | ||||
|     """Translate a shell-like wildcard pattern to a compiled regular | ||||
|     expression. | ||||
| 
 | ||||
|     Return the compiled regex.  If 'is_regex' true, | ||||
|     then 'pattern' is directly compiled to a regex (if it's a string) | ||||
|     or just returned as-is (assumes it's a regex object). | ||||
|     """ | ||||
|     if is_regex: | ||||
|         if isinstance(pattern, str): | ||||
|             return re.compile(pattern) | ||||
|         else: | ||||
|             return pattern | ||||
| 
 | ||||
|     if pattern: | ||||
|         pattern_re = _glob_to_re(pattern) | ||||
|     else: | ||||
|         pattern_re = '' | ||||
| 
 | ||||
|     if prefix is not None: | ||||
|         # ditch end of pattern character | ||||
|         empty_pattern = _glob_to_re('') | ||||
|         prefix_re = _glob_to_re(prefix)[:-len(empty_pattern)] | ||||
|         pattern_re = "^" + os.path.join(prefix_re, ".*" + pattern_re) | ||||
|     else:                               # no prefix -- respect anchor flag | ||||
|         if anchor: | ||||
|             pattern_re = "^" + pattern_re | ||||
| 
 | ||||
|     return re.compile(pattern_re) | ||||
							
								
								
									
										187
									
								
								Lib/packaging/markers.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								Lib/packaging/markers.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,187 @@ | |||
| """Parser for the environment markers micro-language defined in PEP 345.""" | ||||
| 
 | ||||
| import sys | ||||
| import platform | ||||
| import os | ||||
| 
 | ||||
| from tokenize import tokenize, NAME, OP, STRING, ENDMARKER, ENCODING | ||||
| from io import BytesIO | ||||
| 
 | ||||
| __all__ = ['interpret'] | ||||
| 
 | ||||
| 
 | ||||
| # allowed operators | ||||
| _OPERATORS = {'==': lambda x, y: x == y, | ||||
|               '!=': lambda x, y: x != y, | ||||
|               '>': lambda x, y: x > y, | ||||
|               '>=': lambda x, y: x >= y, | ||||
|               '<': lambda x, y: x < y, | ||||
|               '<=': lambda x, y: x <= y, | ||||
|               'in': lambda x, y: x in y, | ||||
|               'not in': lambda x, y: x not in y} | ||||
| 
 | ||||
| 
 | ||||
| def _operate(operation, x, y): | ||||
|     return _OPERATORS[operation](x, y) | ||||
| 
 | ||||
| 
 | ||||
| # restricted set of variables | ||||
| _VARS = {'sys.platform': sys.platform, | ||||
|          'python_version': sys.version[:3], | ||||
|          'python_full_version': sys.version.split(' ', 1)[0], | ||||
|          'os.name': os.name, | ||||
|          'platform.version': platform.version(), | ||||
|          'platform.machine': platform.machine(), | ||||
|          'platform.python_implementation': platform.python_implementation()} | ||||
| 
 | ||||
| 
 | ||||
| class _Operation: | ||||
| 
 | ||||
|     def __init__(self, execution_context=None): | ||||
|         self.left = None | ||||
|         self.op = None | ||||
|         self.right = None | ||||
|         if execution_context is None: | ||||
|             execution_context = {} | ||||
|         self.execution_context = execution_context | ||||
| 
 | ||||
|     def _get_var(self, name): | ||||
|         if name in self.execution_context: | ||||
|             return self.execution_context[name] | ||||
|         return _VARS[name] | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return '%s %s %s' % (self.left, self.op, self.right) | ||||
| 
 | ||||
|     def _is_string(self, value): | ||||
|         if value is None or len(value) < 2: | ||||
|             return False | ||||
|         for delimiter in '"\'': | ||||
|             if value[0] == value[-1] == delimiter: | ||||
|                 return True | ||||
|         return False | ||||
| 
 | ||||
|     def _is_name(self, value): | ||||
|         return value in _VARS | ||||
| 
 | ||||
|     def _convert(self, value): | ||||
|         if value in _VARS: | ||||
|             return self._get_var(value) | ||||
|         return value.strip('"\'') | ||||
| 
 | ||||
|     def _check_name(self, value): | ||||
|         if value not in _VARS: | ||||
|             raise NameError(value) | ||||
| 
 | ||||
|     def _nonsense_op(self): | ||||
|         msg = 'This operation is not supported : "%s"' % self | ||||
|         raise SyntaxError(msg) | ||||
| 
 | ||||
|     def __call__(self): | ||||
|         # make sure we do something useful | ||||
|         if self._is_string(self.left): | ||||
|             if self._is_string(self.right): | ||||
|                 self._nonsense_op() | ||||
|             self._check_name(self.right) | ||||
|         else: | ||||
|             if not self._is_string(self.right): | ||||
|                 self._nonsense_op() | ||||
|             self._check_name(self.left) | ||||
| 
 | ||||
|         if self.op not in _OPERATORS: | ||||
|             raise TypeError('Operator not supported "%s"' % self.op) | ||||
| 
 | ||||
|         left = self._convert(self.left) | ||||
|         right = self._convert(self.right) | ||||
|         return _operate(self.op, left, right) | ||||
| 
 | ||||
| 
 | ||||
| class _OR: | ||||
|     def __init__(self, left, right=None): | ||||
|         self.left = left | ||||
|         self.right = right | ||||
| 
 | ||||
|     def filled(self): | ||||
|         return self.right is not None | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return 'OR(%r, %r)' % (self.left, self.right) | ||||
| 
 | ||||
|     def __call__(self): | ||||
|         return self.left() or self.right() | ||||
| 
 | ||||
| 
 | ||||
| class _AND: | ||||
|     def __init__(self, left, right=None): | ||||
|         self.left = left | ||||
|         self.right = right | ||||
| 
 | ||||
|     def filled(self): | ||||
|         return self.right is not None | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return 'AND(%r, %r)' % (self.left, self.right) | ||||
| 
 | ||||
|     def __call__(self): | ||||
|         return self.left() and self.right() | ||||
| 
 | ||||
| 
 | ||||
| def interpret(marker, execution_context=None): | ||||
|     """Interpret a marker and return a result depending on environment.""" | ||||
|     marker = marker.strip().encode() | ||||
|     ops = [] | ||||
|     op_starting = True | ||||
|     for token in tokenize(BytesIO(marker).readline): | ||||
|         # Unpack token | ||||
|         toktype, tokval, rowcol, line, logical_line = token | ||||
|         if toktype not in (NAME, OP, STRING, ENDMARKER, ENCODING): | ||||
|             raise SyntaxError('Type not supported "%s"' % tokval) | ||||
| 
 | ||||
|         if op_starting: | ||||
|             op = _Operation(execution_context) | ||||
|             if len(ops) > 0: | ||||
|                 last = ops[-1] | ||||
|                 if isinstance(last, (_OR, _AND)) and not last.filled(): | ||||
|                     last.right = op | ||||
|                 else: | ||||
|                     ops.append(op) | ||||
|             else: | ||||
|                 ops.append(op) | ||||
|             op_starting = False | ||||
|         else: | ||||
|             op = ops[-1] | ||||
| 
 | ||||
|         if (toktype == ENDMARKER or | ||||
|             (toktype == NAME and tokval in ('and', 'or'))): | ||||
|             if toktype == NAME and tokval == 'and': | ||||
|                 ops.append(_AND(ops.pop())) | ||||
|             elif toktype == NAME and tokval == 'or': | ||||
|                 ops.append(_OR(ops.pop())) | ||||
|             op_starting = True | ||||
|             continue | ||||
| 
 | ||||
|         if isinstance(op, (_OR, _AND)) and op.right is not None: | ||||
|             op = op.right | ||||
| 
 | ||||
|         if ((toktype in (NAME, STRING) and tokval not in ('in', 'not')) | ||||
|             or (toktype == OP and tokval == '.')): | ||||
|             if op.op is None: | ||||
|                 if op.left is None: | ||||
|                     op.left = tokval | ||||
|                 else: | ||||
|                     op.left += tokval | ||||
|             else: | ||||
|                 if op.right is None: | ||||
|                     op.right = tokval | ||||
|                 else: | ||||
|                     op.right += tokval | ||||
|         elif toktype == OP or tokval in ('in', 'not'): | ||||
|             if tokval == 'in' and op.op == 'not': | ||||
|                 op.op = 'not in' | ||||
|             else: | ||||
|                 op.op = tokval | ||||
| 
 | ||||
|     for op in ops: | ||||
|         if not op(): | ||||
|             return False | ||||
|     return True | ||||
							
								
								
									
										552
									
								
								Lib/packaging/metadata.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										552
									
								
								Lib/packaging/metadata.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,552 @@ | |||
| """Implementation of the Metadata for Python packages PEPs. | ||||
| 
 | ||||
| Supports all metadata formats (1.0, 1.1, 1.2). | ||||
| """ | ||||
| 
 | ||||
| import re | ||||
| import logging | ||||
| 
 | ||||
| from io import StringIO | ||||
| from email import message_from_file | ||||
| from packaging import logger | ||||
| from packaging.markers import interpret | ||||
| from packaging.version import (is_valid_predicate, is_valid_version, | ||||
|                                is_valid_versions) | ||||
| from packaging.errors import (MetadataMissingError, | ||||
|                               MetadataConflictError, | ||||
|                               MetadataUnrecognizedVersionError) | ||||
| 
 | ||||
| try: | ||||
|     # docutils is installed | ||||
|     from docutils.utils import Reporter | ||||
|     from docutils.parsers.rst import Parser | ||||
|     from docutils import frontend | ||||
|     from docutils import nodes | ||||
| 
 | ||||
|     class SilentReporter(Reporter): | ||||
| 
 | ||||
|         def __init__(self, source, report_level, halt_level, stream=None, | ||||
|                      debug=0, encoding='ascii', error_handler='replace'): | ||||
|             self.messages = [] | ||||
|             Reporter.__init__(self, source, report_level, halt_level, stream, | ||||
|                               debug, encoding, error_handler) | ||||
| 
 | ||||
|         def system_message(self, level, message, *children, **kwargs): | ||||
|             self.messages.append((level, message, children, kwargs)) | ||||
| 
 | ||||
|     _HAS_DOCUTILS = True | ||||
| except ImportError: | ||||
|     # docutils is not installed | ||||
|     _HAS_DOCUTILS = False | ||||
| 
 | ||||
| # public API of this module | ||||
| __all__ = ['Metadata', 'PKG_INFO_ENCODING', 'PKG_INFO_PREFERRED_VERSION'] | ||||
| 
 | ||||
| # Encoding used for the PKG-INFO files | ||||
| PKG_INFO_ENCODING = 'utf-8' | ||||
| 
 | ||||
| # preferred version. Hopefully will be changed | ||||
| # to 1.2 once PEP 345 is supported everywhere | ||||
| PKG_INFO_PREFERRED_VERSION = '1.0' | ||||
| 
 | ||||
| _LINE_PREFIX = re.compile('\n       \|') | ||||
| _241_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', | ||||
|                'Summary', 'Description', | ||||
|                'Keywords', 'Home-page', 'Author', 'Author-email', | ||||
|                'License') | ||||
| 
 | ||||
| _314_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', | ||||
|                'Supported-Platform', 'Summary', 'Description', | ||||
|                'Keywords', 'Home-page', 'Author', 'Author-email', | ||||
|                'License', 'Classifier', 'Download-URL', 'Obsoletes', | ||||
|                'Provides', 'Requires') | ||||
| 
 | ||||
| _314_MARKERS = ('Obsoletes', 'Provides', 'Requires') | ||||
| 
 | ||||
| _345_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', | ||||
|                'Supported-Platform', 'Summary', 'Description', | ||||
|                'Keywords', 'Home-page', 'Author', 'Author-email', | ||||
|                'Maintainer', 'Maintainer-email', 'License', | ||||
|                'Classifier', 'Download-URL', 'Obsoletes-Dist', | ||||
|                'Project-URL', 'Provides-Dist', 'Requires-Dist', | ||||
|                'Requires-Python', 'Requires-External') | ||||
| 
 | ||||
| _345_MARKERS = ('Provides-Dist', 'Requires-Dist', 'Requires-Python', | ||||
|                 'Obsoletes-Dist', 'Requires-External', 'Maintainer', | ||||
|                 'Maintainer-email', 'Project-URL') | ||||
| 
 | ||||
| _ALL_FIELDS = set() | ||||
| _ALL_FIELDS.update(_241_FIELDS) | ||||
| _ALL_FIELDS.update(_314_FIELDS) | ||||
| _ALL_FIELDS.update(_345_FIELDS) | ||||
| 
 | ||||
| 
 | ||||
| def _version2fieldlist(version): | ||||
|     if version == '1.0': | ||||
|         return _241_FIELDS | ||||
|     elif version == '1.1': | ||||
|         return _314_FIELDS | ||||
|     elif version == '1.2': | ||||
|         return _345_FIELDS | ||||
|     raise MetadataUnrecognizedVersionError(version) | ||||
| 
 | ||||
| 
 | ||||
| def _best_version(fields): | ||||
|     """Detect the best version depending on the fields used.""" | ||||
|     def _has_marker(keys, markers): | ||||
|         for marker in markers: | ||||
|             if marker in keys: | ||||
|                 return True | ||||
|         return False | ||||
| 
 | ||||
|     keys = list(fields) | ||||
|     possible_versions = ['1.0', '1.1', '1.2'] | ||||
| 
 | ||||
|     # first let's try to see if a field is not part of one of the version | ||||
|     for key in keys: | ||||
|         if key not in _241_FIELDS and '1.0' in possible_versions: | ||||
|             possible_versions.remove('1.0') | ||||
|         if key not in _314_FIELDS and '1.1' in possible_versions: | ||||
|             possible_versions.remove('1.1') | ||||
|         if key not in _345_FIELDS and '1.2' in possible_versions: | ||||
|             possible_versions.remove('1.2') | ||||
| 
 | ||||
|     # possible_version contains qualified versions | ||||
|     if len(possible_versions) == 1: | ||||
|         return possible_versions[0]   # found ! | ||||
|     elif len(possible_versions) == 0: | ||||
|         raise MetadataConflictError('Unknown metadata set') | ||||
| 
 | ||||
|     # let's see if one unique marker is found | ||||
|     is_1_1 = '1.1' in possible_versions and _has_marker(keys, _314_MARKERS) | ||||
|     is_1_2 = '1.2' in possible_versions and _has_marker(keys, _345_MARKERS) | ||||
|     if is_1_1 and is_1_2: | ||||
|         raise MetadataConflictError('You used incompatible 1.1 and 1.2 fields') | ||||
| 
 | ||||
|     # we have the choice, either 1.0, or 1.2 | ||||
|     #   - 1.0 has a broken Summary field but works with all tools | ||||
|     #   - 1.1 is to avoid | ||||
|     #   - 1.2 fixes Summary but is not widespread yet | ||||
|     if not is_1_1 and not is_1_2: | ||||
|         # we couldn't find any specific marker | ||||
|         if PKG_INFO_PREFERRED_VERSION in possible_versions: | ||||
|             return PKG_INFO_PREFERRED_VERSION | ||||
|     if is_1_1: | ||||
|         return '1.1' | ||||
| 
 | ||||
|     # default marker when 1.0 is disqualified | ||||
|     return '1.2' | ||||
| 
 | ||||
| 
 | ||||
| _ATTR2FIELD = { | ||||
|     'metadata_version': 'Metadata-Version', | ||||
|     'name': 'Name', | ||||
|     'version': 'Version', | ||||
|     'platform': 'Platform', | ||||
|     'supported_platform': 'Supported-Platform', | ||||
|     'summary': 'Summary', | ||||
|     'description': 'Description', | ||||
|     'keywords': 'Keywords', | ||||
|     'home_page': 'Home-page', | ||||
|     'author': 'Author', | ||||
|     'author_email': 'Author-email', | ||||
|     'maintainer': 'Maintainer', | ||||
|     'maintainer_email': 'Maintainer-email', | ||||
|     'license': 'License', | ||||
|     'classifier': 'Classifier', | ||||
|     'download_url': 'Download-URL', | ||||
|     'obsoletes_dist': 'Obsoletes-Dist', | ||||
|     'provides_dist': 'Provides-Dist', | ||||
|     'requires_dist': 'Requires-Dist', | ||||
|     'requires_python': 'Requires-Python', | ||||
|     'requires_external': 'Requires-External', | ||||
|     'requires': 'Requires', | ||||
|     'provides': 'Provides', | ||||
|     'obsoletes': 'Obsoletes', | ||||
|     'project_url': 'Project-URL', | ||||
| } | ||||
| 
 | ||||
| _PREDICATE_FIELDS = ('Requires-Dist', 'Obsoletes-Dist', 'Provides-Dist') | ||||
| _VERSIONS_FIELDS = ('Requires-Python',) | ||||
| _VERSION_FIELDS = ('Version',) | ||||
| _LISTFIELDS = ('Platform', 'Classifier', 'Obsoletes', | ||||
|                'Requires', 'Provides', 'Obsoletes-Dist', | ||||
|                'Provides-Dist', 'Requires-Dist', 'Requires-External', | ||||
|                'Project-URL', 'Supported-Platform') | ||||
| _LISTTUPLEFIELDS = ('Project-URL',) | ||||
| 
 | ||||
| _ELEMENTSFIELD = ('Keywords',) | ||||
| 
 | ||||
| _UNICODEFIELDS = ('Author', 'Maintainer', 'Summary', 'Description') | ||||
| 
 | ||||
| _MISSING = object() | ||||
| 
 | ||||
| 
 | ||||
| class NoDefault: | ||||
|     """Marker object used for clean representation""" | ||||
|     def __repr__(self): | ||||
|         return '<NoDefault>' | ||||
| 
 | ||||
| _MISSING = NoDefault() | ||||
| 
 | ||||
| 
 | ||||
| class Metadata: | ||||
|     """The metadata of a release. | ||||
| 
 | ||||
|     Supports versions 1.0, 1.1 and 1.2 (auto-detected). You can | ||||
|     instantiate the class with one of these arguments (or none): | ||||
|     - *path*, the path to a METADATA file | ||||
|     - *fileobj* give a file-like object with METADATA as content | ||||
|     - *mapping* is a dict-like object | ||||
|     """ | ||||
|     # TODO document that execution_context and platform_dependent are used | ||||
|     # to filter on query, not when setting a key | ||||
|     # also document the mapping API and UNKNOWN default key | ||||
| 
 | ||||
|     def __init__(self, path=None, platform_dependent=False, | ||||
|                  execution_context=None, fileobj=None, mapping=None): | ||||
|         self._fields = {} | ||||
|         self.requires_files = [] | ||||
|         self.docutils_support = _HAS_DOCUTILS | ||||
|         self.platform_dependent = platform_dependent | ||||
|         self.execution_context = execution_context | ||||
|         if [path, fileobj, mapping].count(None) < 2: | ||||
|             raise TypeError('path, fileobj and mapping are exclusive') | ||||
|         if path is not None: | ||||
|             self.read(path) | ||||
|         elif fileobj is not None: | ||||
|             self.read_file(fileobj) | ||||
|         elif mapping is not None: | ||||
|             self.update(mapping) | ||||
| 
 | ||||
|     def _set_best_version(self): | ||||
|         self._fields['Metadata-Version'] = _best_version(self._fields) | ||||
| 
 | ||||
|     def _write_field(self, file, name, value): | ||||
|         file.write('%s: %s\n' % (name, value)) | ||||
| 
 | ||||
|     def __getitem__(self, name): | ||||
|         return self.get(name) | ||||
| 
 | ||||
|     def __setitem__(self, name, value): | ||||
|         return self.set(name, value) | ||||
| 
 | ||||
|     def __delitem__(self, name): | ||||
|         field_name = self._convert_name(name) | ||||
|         try: | ||||
|             del self._fields[field_name] | ||||
|         except KeyError: | ||||
|             raise KeyError(name) | ||||
|         self._set_best_version() | ||||
| 
 | ||||
|     def __contains__(self, name): | ||||
|         return (name in self._fields or | ||||
|                 self._convert_name(name) in self._fields) | ||||
| 
 | ||||
|     def _convert_name(self, name): | ||||
|         if name in _ALL_FIELDS: | ||||
|             return name | ||||
|         name = name.replace('-', '_').lower() | ||||
|         return _ATTR2FIELD.get(name, name) | ||||
| 
 | ||||
|     def _default_value(self, name): | ||||
|         if name in _LISTFIELDS or name in _ELEMENTSFIELD: | ||||
|             return [] | ||||
|         return 'UNKNOWN' | ||||
| 
 | ||||
|     def _check_rst_data(self, data): | ||||
|         """Return warnings when the provided data has syntax errors.""" | ||||
|         source_path = StringIO() | ||||
|         parser = Parser() | ||||
|         settings = frontend.OptionParser().get_default_values() | ||||
|         settings.tab_width = 4 | ||||
|         settings.pep_references = None | ||||
|         settings.rfc_references = None | ||||
|         reporter = SilentReporter(source_path, | ||||
|                           settings.report_level, | ||||
|                           settings.halt_level, | ||||
|                           stream=settings.warning_stream, | ||||
|                           debug=settings.debug, | ||||
|                           encoding=settings.error_encoding, | ||||
|                           error_handler=settings.error_encoding_error_handler) | ||||
| 
 | ||||
|         document = nodes.document(settings, reporter, source=source_path) | ||||
|         document.note_source(source_path, -1) | ||||
|         try: | ||||
|             parser.parse(data, document) | ||||
|         except AttributeError: | ||||
|             reporter.messages.append((-1, 'Could not finish the parsing.', | ||||
|                                       '', {})) | ||||
| 
 | ||||
|         return reporter.messages | ||||
| 
 | ||||
|     def _platform(self, value): | ||||
|         if not self.platform_dependent or ';' not in value: | ||||
|             return True, value | ||||
|         value, marker = value.split(';') | ||||
|         return interpret(marker, self.execution_context), value | ||||
| 
 | ||||
|     def _remove_line_prefix(self, value): | ||||
|         return _LINE_PREFIX.sub('\n', value) | ||||
| 
 | ||||
|     # | ||||
|     # Public API | ||||
|     # | ||||
|     def get_fullname(self): | ||||
|         """Return the distribution name with version""" | ||||
|         return '%s-%s' % (self['Name'], self['Version']) | ||||
| 
 | ||||
|     def is_metadata_field(self, name): | ||||
|         """return True if name is a valid metadata key""" | ||||
|         name = self._convert_name(name) | ||||
|         return name in _ALL_FIELDS | ||||
| 
 | ||||
|     def is_multi_field(self, name): | ||||
|         name = self._convert_name(name) | ||||
|         return name in _LISTFIELDS | ||||
| 
 | ||||
|     def read(self, filepath): | ||||
|         """Read the metadata values from a file path.""" | ||||
|         with open(filepath, 'r', encoding='ascii') as fp: | ||||
|             self.read_file(fp) | ||||
| 
 | ||||
|     def read_file(self, fileob): | ||||
|         """Read the metadata values from a file object.""" | ||||
|         msg = message_from_file(fileob) | ||||
|         self._fields['Metadata-Version'] = msg['metadata-version'] | ||||
| 
 | ||||
|         for field in _version2fieldlist(self['Metadata-Version']): | ||||
|             if field in _LISTFIELDS: | ||||
|                 # we can have multiple lines | ||||
|                 values = msg.get_all(field) | ||||
|                 if field in _LISTTUPLEFIELDS and values is not None: | ||||
|                     values = [tuple(value.split(',')) for value in values] | ||||
|                 self.set(field, values) | ||||
|             else: | ||||
|                 # single line | ||||
|                 value = msg[field] | ||||
|                 if value is not None and value != 'UNKNOWN': | ||||
|                     self.set(field, value) | ||||
| 
 | ||||
|     def write(self, filepath): | ||||
|         """Write the metadata fields to filepath.""" | ||||
|         with open(filepath, 'w') as fp: | ||||
|             self.write_file(fp) | ||||
| 
 | ||||
|     def write_file(self, fileobject): | ||||
|         """Write the PKG-INFO format data to a file object.""" | ||||
|         self._set_best_version() | ||||
|         for field in _version2fieldlist(self['Metadata-Version']): | ||||
|             values = self.get(field) | ||||
|             if field in _ELEMENTSFIELD: | ||||
|                 self._write_field(fileobject, field, ','.join(values)) | ||||
|                 continue | ||||
|             if field not in _LISTFIELDS: | ||||
|                 if field == 'Description': | ||||
|                     values = values.replace('\n', '\n       |') | ||||
|                 values = [values] | ||||
| 
 | ||||
|             if field in _LISTTUPLEFIELDS: | ||||
|                 values = [','.join(value) for value in values] | ||||
| 
 | ||||
|             for value in values: | ||||
|                 self._write_field(fileobject, field, value) | ||||
| 
 | ||||
|     def update(self, other=None, **kwargs): | ||||
|         """Set metadata values from the given iterable `other` and kwargs. | ||||
| 
 | ||||
|         Behavior is like `dict.update`: If `other` has a ``keys`` method, | ||||
|         they are looped over and ``self[key]`` is assigned ``other[key]``. | ||||
|         Else, ``other`` is an iterable of ``(key, value)`` iterables. | ||||
| 
 | ||||
|         Keys that don't match a metadata field or that have an empty value are | ||||
|         dropped. | ||||
|         """ | ||||
|         def _set(key, value): | ||||
|             if key in _ATTR2FIELD and value: | ||||
|                 self.set(self._convert_name(key), value) | ||||
| 
 | ||||
|         if other is None: | ||||
|             pass | ||||
|         elif hasattr(other, 'keys'): | ||||
|             for k in other.keys(): | ||||
|                 _set(k, other[k]) | ||||
|         else: | ||||
|             for k, v in other: | ||||
|                 _set(k, v) | ||||
| 
 | ||||
|         if kwargs: | ||||
|             self.update(kwargs) | ||||
| 
 | ||||
|     def set(self, name, value): | ||||
|         """Control then set a metadata field.""" | ||||
|         name = self._convert_name(name) | ||||
| 
 | ||||
|         if ((name in _ELEMENTSFIELD or name == 'Platform') and | ||||
|             not isinstance(value, (list, tuple))): | ||||
|             if isinstance(value, str): | ||||
|                 value = [v.strip() for v in value.split(',')] | ||||
|             else: | ||||
|                 value = [] | ||||
|         elif (name in _LISTFIELDS and | ||||
|               not isinstance(value, (list, tuple))): | ||||
|             if isinstance(value, str): | ||||
|                 value = [value] | ||||
|             else: | ||||
|                 value = [] | ||||
| 
 | ||||
|         if logger.isEnabledFor(logging.WARNING): | ||||
|             if name in _PREDICATE_FIELDS and value is not None: | ||||
|                 for v in value: | ||||
|                     # check that the values are valid predicates | ||||
|                     if not is_valid_predicate(v.split(';')[0]): | ||||
|                         logger.warning( | ||||
|                             '%r is not a valid predicate (field %r)', | ||||
|                             v, name) | ||||
|             # FIXME this rejects UNKNOWN, is that right? | ||||
|             elif name in _VERSIONS_FIELDS and value is not None: | ||||
|                 if not is_valid_versions(value): | ||||
|                     logger.warning('%r is not a valid version (field %r)', | ||||
|                                    value, name) | ||||
|             elif name in _VERSION_FIELDS and value is not None: | ||||
|                 if not is_valid_version(value): | ||||
|                     logger.warning('%r is not a valid version (field %r)', | ||||
|                                    value, name) | ||||
| 
 | ||||
|         if name in _UNICODEFIELDS: | ||||
|             if name == 'Description': | ||||
|                 value = self._remove_line_prefix(value) | ||||
| 
 | ||||
|         self._fields[name] = value | ||||
|         self._set_best_version() | ||||
| 
 | ||||
|     def get(self, name, default=_MISSING): | ||||
|         """Get a metadata field.""" | ||||
|         name = self._convert_name(name) | ||||
|         if name not in self._fields: | ||||
|             if default is _MISSING: | ||||
|                 default = self._default_value(name) | ||||
|             return default | ||||
|         if name in _UNICODEFIELDS: | ||||
|             value = self._fields[name] | ||||
|             return value | ||||
|         elif name in _LISTFIELDS: | ||||
|             value = self._fields[name] | ||||
|             if value is None: | ||||
|                 return [] | ||||
|             res = [] | ||||
|             for val in value: | ||||
|                 valid, val = self._platform(val) | ||||
|                 if not valid: | ||||
|                     continue | ||||
|                 if name not in _LISTTUPLEFIELDS: | ||||
|                     res.append(val) | ||||
|                 else: | ||||
|                     # That's for Project-URL | ||||
|                     res.append((val[0], val[1])) | ||||
|             return res | ||||
| 
 | ||||
|         elif name in _ELEMENTSFIELD: | ||||
|             valid, value = self._platform(self._fields[name]) | ||||
|             if not valid: | ||||
|                 return [] | ||||
|             if isinstance(value, str): | ||||
|                 return value.split(',') | ||||
|         valid, value = self._platform(self._fields[name]) | ||||
|         if not valid: | ||||
|             return None | ||||
|         return value | ||||
| 
 | ||||
|     def check(self, strict=False, restructuredtext=False): | ||||
|         """Check if the metadata is compliant. If strict is False then raise if | ||||
|         no Name or Version are provided""" | ||||
|         # XXX should check the versions (if the file was loaded) | ||||
|         missing, warnings = [], [] | ||||
| 
 | ||||
|         for attr in ('Name', 'Version'):  # required by PEP 345 | ||||
|             if attr not in self: | ||||
|                 missing.append(attr) | ||||
| 
 | ||||
|         if strict and missing != []: | ||||
|             msg = 'missing required metadata: %s' % ', '.join(missing) | ||||
|             raise MetadataMissingError(msg) | ||||
| 
 | ||||
|         for attr in ('Home-page', 'Author'): | ||||
|             if attr not in self: | ||||
|                 missing.append(attr) | ||||
| 
 | ||||
|         if _HAS_DOCUTILS and restructuredtext: | ||||
|             warnings.extend(self._check_rst_data(self['Description'])) | ||||
| 
 | ||||
|         # checking metadata 1.2 (XXX needs to check 1.1, 1.0) | ||||
|         if self['Metadata-Version'] != '1.2': | ||||
|             return missing, warnings | ||||
| 
 | ||||
|         def is_valid_predicates(value): | ||||
|             for v in value: | ||||
|                 if not is_valid_predicate(v.split(';')[0]): | ||||
|                     return False | ||||
|             return True | ||||
| 
 | ||||
|         for fields, controller in ((_PREDICATE_FIELDS, is_valid_predicates), | ||||
|                                    (_VERSIONS_FIELDS, is_valid_versions), | ||||
|                                    (_VERSION_FIELDS, is_valid_version)): | ||||
|             for field in fields: | ||||
|                 value = self.get(field, None) | ||||
|                 if value is not None and not controller(value): | ||||
|                     warnings.append('Wrong value for %r: %s' % (field, value)) | ||||
| 
 | ||||
|         return missing, warnings | ||||
| 
 | ||||
|     def todict(self): | ||||
|         """Return fields as a dict. | ||||
| 
 | ||||
|         Field names will be converted to use the underscore-lowercase style | ||||
|         instead of hyphen-mixed case (i.e. home_page instead of Home-page). | ||||
|         """ | ||||
|         data = { | ||||
|             'metadata_version': self['Metadata-Version'], | ||||
|             'name': self['Name'], | ||||
|             'version': self['Version'], | ||||
|             'summary': self['Summary'], | ||||
|             'home_page': self['Home-page'], | ||||
|             'author': self['Author'], | ||||
|             'author_email': self['Author-email'], | ||||
|             'license': self['License'], | ||||
|             'description': self['Description'], | ||||
|             'keywords': self['Keywords'], | ||||
|             'platform': self['Platform'], | ||||
|             'classifier': self['Classifier'], | ||||
|             'download_url': self['Download-URL'], | ||||
|         } | ||||
| 
 | ||||
|         if self['Metadata-Version'] == '1.2': | ||||
|             data['requires_dist'] = self['Requires-Dist'] | ||||
|             data['requires_python'] = self['Requires-Python'] | ||||
|             data['requires_external'] = self['Requires-External'] | ||||
|             data['provides_dist'] = self['Provides-Dist'] | ||||
|             data['obsoletes_dist'] = self['Obsoletes-Dist'] | ||||
|             data['project_url'] = [','.join(url) for url in | ||||
|                                    self['Project-URL']] | ||||
| 
 | ||||
|         elif self['Metadata-Version'] == '1.1': | ||||
|             data['provides'] = self['Provides'] | ||||
|             data['requires'] = self['Requires'] | ||||
|             data['obsoletes'] = self['Obsoletes'] | ||||
| 
 | ||||
|         return data | ||||
| 
 | ||||
|     # Mapping API | ||||
| 
 | ||||
|     def keys(self): | ||||
|         return _version2fieldlist(self['Metadata-Version']) | ||||
| 
 | ||||
|     def __iter__(self): | ||||
|         for key in self.keys(): | ||||
|             yield key | ||||
| 
 | ||||
|     def values(self): | ||||
|         return [self[key] for key in list(self.keys())] | ||||
| 
 | ||||
|     def items(self): | ||||
|         return [(key, self[key]) for key in list(self.keys())] | ||||
							
								
								
									
										9
									
								
								Lib/packaging/pypi/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Lib/packaging/pypi/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| """Low-level and high-level APIs to interact with project indexes.""" | ||||
| 
 | ||||
| __all__ = ['simple', | ||||
|            'xmlrpc', | ||||
|            'dist', | ||||
|            'errors', | ||||
|            'mirrors'] | ||||
| 
 | ||||
| from packaging.pypi.dist import ReleaseInfo, ReleasesList, DistInfo | ||||
							
								
								
									
										48
									
								
								Lib/packaging/pypi/base.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								Lib/packaging/pypi/base.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | |||
| """Base class for index crawlers.""" | ||||
| 
 | ||||
| from packaging.pypi.dist import ReleasesList | ||||
| 
 | ||||
| 
 | ||||
| class BaseClient: | ||||
|     """Base class containing common methods for the index crawlers/clients""" | ||||
| 
 | ||||
|     def __init__(self, prefer_final, prefer_source): | ||||
|         self._prefer_final = prefer_final | ||||
|         self._prefer_source = prefer_source | ||||
|         self._index = self | ||||
| 
 | ||||
|     def _get_prefer_final(self, prefer_final=None): | ||||
|         """Return the prefer_final internal parameter or the specified one if | ||||
|         provided""" | ||||
|         if prefer_final: | ||||
|             return prefer_final | ||||
|         else: | ||||
|             return self._prefer_final | ||||
| 
 | ||||
|     def _get_prefer_source(self, prefer_source=None): | ||||
|         """Return the prefer_source internal parameter or the specified one if | ||||
|         provided""" | ||||
|         if prefer_source: | ||||
|             return prefer_source | ||||
|         else: | ||||
|             return self._prefer_source | ||||
| 
 | ||||
|     def _get_project(self, project_name): | ||||
|         """Return an project instance, create it if necessary""" | ||||
|         return self._projects.setdefault(project_name.lower(), | ||||
|                     ReleasesList(project_name, index=self._index)) | ||||
| 
 | ||||
|     def download_distribution(self, requirements, temp_path=None, | ||||
|                               prefer_source=None, prefer_final=None): | ||||
|         """Download a distribution from the last release according to the | ||||
|         requirements. | ||||
| 
 | ||||
|         If temp_path is provided, download to this path, otherwise, create a | ||||
|         temporary location for the download and return it. | ||||
|         """ | ||||
|         prefer_final = self._get_prefer_final(prefer_final) | ||||
|         prefer_source = self._get_prefer_source(prefer_source) | ||||
|         release = self.get_release(requirements, prefer_final) | ||||
|         if release: | ||||
|             dist = release.get_distribution(prefer_source=prefer_source) | ||||
|             return dist.download(temp_path) | ||||
							
								
								
									
										547
									
								
								Lib/packaging/pypi/dist.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										547
									
								
								Lib/packaging/pypi/dist.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,547 @@ | |||
| """Classes representing releases and distributions retrieved from indexes. | ||||
| 
 | ||||
| A project (= unique name) can have several releases (= versions) and | ||||
| each release can have several distributions (= sdist and bdists). | ||||
| 
 | ||||
| Release objects contain metadata-related information (see PEP 376); | ||||
| distribution objects contain download-related information. | ||||
| """ | ||||
| 
 | ||||
| import sys | ||||
| import mimetypes | ||||
| import re | ||||
| import tempfile | ||||
| import urllib.request | ||||
| import urllib.parse | ||||
| import urllib.error | ||||
| import urllib.parse | ||||
| import hashlib | ||||
| from shutil import unpack_archive | ||||
| 
 | ||||
| from packaging.errors import IrrationalVersionError | ||||
| from packaging.version import (suggest_normalized_version, NormalizedVersion, | ||||
|                                get_version_predicate) | ||||
| from packaging.metadata import Metadata | ||||
| from packaging.pypi.errors import (HashDoesNotMatch, UnsupportedHashName, | ||||
|                                    CantParseArchiveName) | ||||
| 
 | ||||
| 
 | ||||
| __all__ = ['ReleaseInfo', 'DistInfo', 'ReleasesList', 'get_infos_from_url'] | ||||
| 
 | ||||
| EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz .egg".split() | ||||
| MD5_HASH = re.compile(r'^.*#md5=([a-f0-9]+)$') | ||||
| DIST_TYPES = ['bdist', 'sdist'] | ||||
| 
 | ||||
| 
 | ||||
| class IndexReference: | ||||
|     """Mixin used to store the index reference""" | ||||
|     def set_index(self, index=None): | ||||
|         self._index = index | ||||
| 
 | ||||
| 
 | ||||
| class ReleaseInfo(IndexReference): | ||||
|     """Represent a release of a project (a project with a specific version). | ||||
|     The release contain the _metadata informations related to this specific | ||||
|     version, and is also a container for distribution related informations. | ||||
| 
 | ||||
|     See the DistInfo class for more information about distributions. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, name, version, metadata=None, hidden=False, | ||||
|                  index=None, **kwargs): | ||||
|         """ | ||||
|         :param name: the name of the distribution | ||||
|         :param version: the version of the distribution | ||||
|         :param metadata: the metadata fields of the release. | ||||
|         :type metadata: dict | ||||
|         :param kwargs: optional arguments for a new distribution. | ||||
|         """ | ||||
|         self.set_index(index) | ||||
|         self.name = name | ||||
|         self._version = None | ||||
|         self.version = version | ||||
|         if metadata: | ||||
|             self.metadata = Metadata(mapping=metadata) | ||||
|         else: | ||||
|             self.metadata = None | ||||
|         self.dists = {} | ||||
|         self.hidden = hidden | ||||
| 
 | ||||
|         if 'dist_type' in kwargs: | ||||
|             dist_type = kwargs.pop('dist_type') | ||||
|             self.add_distribution(dist_type, **kwargs) | ||||
| 
 | ||||
|     def set_version(self, version): | ||||
|         try: | ||||
|             self._version = NormalizedVersion(version) | ||||
|         except IrrationalVersionError: | ||||
|             suggestion = suggest_normalized_version(version) | ||||
|             if suggestion: | ||||
|                 self.version = suggestion | ||||
|             else: | ||||
|                 raise IrrationalVersionError(version) | ||||
| 
 | ||||
|     def get_version(self): | ||||
|         return self._version | ||||
| 
 | ||||
|     version = property(get_version, set_version) | ||||
| 
 | ||||
|     def fetch_metadata(self): | ||||
|         """If the metadata is not set, use the indexes to get it""" | ||||
|         if not self.metadata: | ||||
|             self._index.get_metadata(self.name, str(self.version)) | ||||
|         return self.metadata | ||||
| 
 | ||||
|     @property | ||||
|     def is_final(self): | ||||
|         """proxy to version.is_final""" | ||||
|         return self.version.is_final | ||||
| 
 | ||||
|     def fetch_distributions(self): | ||||
|         if self.dists is None: | ||||
|             self._index.get_distributions(self.name, str(self.version)) | ||||
|             if self.dists is None: | ||||
|                 self.dists = {} | ||||
|         return self.dists | ||||
| 
 | ||||
|     def add_distribution(self, dist_type='sdist', python_version=None, | ||||
|                          **params): | ||||
|         """Add distribution informations to this release. | ||||
|         If distribution information is already set for this distribution type, | ||||
|         add the given url paths to the distribution. This can be useful while | ||||
|         some of them fails to download. | ||||
| 
 | ||||
|         :param dist_type: the distribution type (eg. "sdist", "bdist", etc.) | ||||
|         :param params: the fields to be passed to the distribution object | ||||
|                        (see the :class:DistInfo constructor). | ||||
|         """ | ||||
|         if dist_type not in DIST_TYPES: | ||||
|             raise ValueError(dist_type) | ||||
|         if dist_type in self.dists: | ||||
|             self.dists[dist_type].add_url(**params) | ||||
|         else: | ||||
|             self.dists[dist_type] = DistInfo(self, dist_type, | ||||
|                                              index=self._index, **params) | ||||
|         if python_version: | ||||
|             self.dists[dist_type].python_version = python_version | ||||
| 
 | ||||
|     def get_distribution(self, dist_type=None, prefer_source=True): | ||||
|         """Return a distribution. | ||||
| 
 | ||||
|         If dist_type is set, find first for this distribution type, and just | ||||
|         act as an alias of __get_item__. | ||||
| 
 | ||||
|         If prefer_source is True, search first for source distribution, and if | ||||
|         not return one existing distribution. | ||||
|         """ | ||||
|         if len(self.dists) == 0: | ||||
|             raise LookupError() | ||||
|         if dist_type: | ||||
|             return self[dist_type] | ||||
|         if prefer_source: | ||||
|             if "sdist" in self.dists: | ||||
|                 dist = self["sdist"] | ||||
|             else: | ||||
|                 dist = next(self.dists.values()) | ||||
|             return dist | ||||
| 
 | ||||
|     def unpack(self, path=None, prefer_source=True): | ||||
|         """Unpack the distribution to the given path. | ||||
| 
 | ||||
|         If not destination is given, creates a temporary location. | ||||
| 
 | ||||
|         Returns the location of the extracted files (root). | ||||
|         """ | ||||
|         return self.get_distribution(prefer_source=prefer_source)\ | ||||
|                    .unpack(path=path) | ||||
| 
 | ||||
|     def download(self, temp_path=None, prefer_source=True): | ||||
|         """Download the distribution, using the requirements. | ||||
| 
 | ||||
|         If more than one distribution match the requirements, use the last | ||||
|         version. | ||||
|         Download the distribution, and put it in the temp_path. If no temp_path | ||||
|         is given, creates and return one. | ||||
| 
 | ||||
|         Returns the complete absolute path to the downloaded archive. | ||||
|         """ | ||||
|         return self.get_distribution(prefer_source=prefer_source)\ | ||||
|                    .download(path=temp_path) | ||||
| 
 | ||||
|     def set_metadata(self, metadata): | ||||
|         if not self.metadata: | ||||
|             self.metadata = Metadata() | ||||
|         self.metadata.update(metadata) | ||||
| 
 | ||||
|     def __getitem__(self, item): | ||||
|         """distributions are available using release["sdist"]""" | ||||
|         return self.dists[item] | ||||
| 
 | ||||
|     def _check_is_comparable(self, other): | ||||
|         if not isinstance(other, ReleaseInfo): | ||||
|             raise TypeError("cannot compare %s and %s" | ||||
|                 % (type(self).__name__, type(other).__name__)) | ||||
|         elif self.name != other.name: | ||||
|             raise TypeError("cannot compare %s and %s" | ||||
|                 % (self.name, other.name)) | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return "<%s %s>" % (self.name, self.version) | ||||
| 
 | ||||
|     def __eq__(self, other): | ||||
|         self._check_is_comparable(other) | ||||
|         return self.version == other.version | ||||
| 
 | ||||
|     def __lt__(self, other): | ||||
|         self._check_is_comparable(other) | ||||
|         return self.version < other.version | ||||
| 
 | ||||
|     def __ne__(self, other): | ||||
|         return not self.__eq__(other) | ||||
| 
 | ||||
|     def __gt__(self, other): | ||||
|         return not (self.__lt__(other) or self.__eq__(other)) | ||||
| 
 | ||||
|     def __le__(self, other): | ||||
|         return self.__eq__(other) or self.__lt__(other) | ||||
| 
 | ||||
|     def __ge__(self, other): | ||||
|         return self.__eq__(other) or self.__gt__(other) | ||||
| 
 | ||||
|     # See http://docs.python.org/reference/datamodel#object.__hash__ | ||||
|     __hash__ = object.__hash__ | ||||
| 
 | ||||
| 
 | ||||
| class DistInfo(IndexReference): | ||||
|     """Represents a distribution retrieved from an index (sdist, bdist, ...) | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, release, dist_type=None, url=None, hashname=None, | ||||
|                  hashval=None, is_external=True, python_version=None, | ||||
|                  index=None): | ||||
|         """Create a new instance of DistInfo. | ||||
| 
 | ||||
|         :param release: a DistInfo class is relative to a release. | ||||
|         :param dist_type: the type of the dist (eg. source, bin-*, etc.) | ||||
|         :param url: URL where we found this distribution | ||||
|         :param hashname: the name of the hash we want to use. Refer to the | ||||
|                          hashlib.new documentation for more information. | ||||
|         :param hashval: the hash value. | ||||
|         :param is_external: we need to know if the provided url comes from | ||||
|                             an index browsing, or from an external resource. | ||||
| 
 | ||||
|         """ | ||||
|         self.set_index(index) | ||||
|         self.release = release | ||||
|         self.dist_type = dist_type | ||||
|         self.python_version = python_version | ||||
|         self._unpacked_dir = None | ||||
|         # set the downloaded path to None by default. The goal here | ||||
|         # is to not download distributions multiple times | ||||
|         self.downloaded_location = None | ||||
|         # We store urls in dict, because we need to have a bit more infos | ||||
|         # than the simple URL. It will be used later to find the good url to | ||||
|         # use. | ||||
|         # We have two _url* attributes: _url and urls. urls contains a list | ||||
|         # of dict for the different urls, and _url contains the choosen url, in | ||||
|         # order to dont make the selection process multiple times. | ||||
|         self.urls = [] | ||||
|         self._url = None | ||||
|         self.add_url(url, hashname, hashval, is_external) | ||||
| 
 | ||||
|     def add_url(self, url=None, hashname=None, hashval=None, is_external=True): | ||||
|         """Add a new url to the list of urls""" | ||||
|         if hashname is not None: | ||||
|             try: | ||||
|                 hashlib.new(hashname) | ||||
|             except ValueError: | ||||
|                 raise UnsupportedHashName(hashname) | ||||
|         if not url in [u['url'] for u in self.urls]: | ||||
|             self.urls.append({ | ||||
|                 'url': url, | ||||
|                 'hashname': hashname, | ||||
|                 'hashval': hashval, | ||||
|                 'is_external': is_external, | ||||
|             }) | ||||
|             # reset the url selection process | ||||
|             self._url = None | ||||
| 
 | ||||
|     @property | ||||
|     def url(self): | ||||
|         """Pick up the right url for the list of urls in self.urls""" | ||||
|         # We return internal urls over externals. | ||||
|         # If there is more than one internal or external, return the first | ||||
|         # one. | ||||
|         if self._url is None: | ||||
|             if len(self.urls) > 1: | ||||
|                 internals_urls = [u for u in self.urls \ | ||||
|                                   if u['is_external'] == False] | ||||
|                 if len(internals_urls) >= 1: | ||||
|                     self._url = internals_urls[0] | ||||
|             if self._url is None: | ||||
|                 self._url = self.urls[0] | ||||
|         return self._url | ||||
| 
 | ||||
|     @property | ||||
|     def is_source(self): | ||||
|         """return if the distribution is a source one or not""" | ||||
|         return self.dist_type == 'sdist' | ||||
| 
 | ||||
|     def download(self, path=None): | ||||
|         """Download the distribution to a path, and return it. | ||||
| 
 | ||||
|         If the path is given in path, use this, otherwise, generates a new one | ||||
|         Return the download location. | ||||
|         """ | ||||
|         if path is None: | ||||
|             path = tempfile.mkdtemp() | ||||
| 
 | ||||
|         # if we do not have downloaded it yet, do it. | ||||
|         if self.downloaded_location is None: | ||||
|             url = self.url['url'] | ||||
|             archive_name = urllib.parse.urlparse(url)[2].split('/')[-1] | ||||
|             filename, headers = urllib.request.urlretrieve(url, | ||||
|                                                    path + "/" + archive_name) | ||||
|             self.downloaded_location = filename | ||||
|             self._check_md5(filename) | ||||
|         return self.downloaded_location | ||||
| 
 | ||||
|     def unpack(self, path=None): | ||||
|         """Unpack the distribution to the given path. | ||||
| 
 | ||||
|         If not destination is given, creates a temporary location. | ||||
| 
 | ||||
|         Returns the location of the extracted files (root). | ||||
|         """ | ||||
|         if not self._unpacked_dir: | ||||
|             if path is None: | ||||
|                 path = tempfile.mkdtemp() | ||||
| 
 | ||||
|             filename = self.download(path) | ||||
|             content_type = mimetypes.guess_type(filename)[0] | ||||
|             unpack_archive(filename, path) | ||||
|             self._unpacked_dir = path | ||||
| 
 | ||||
|         return path | ||||
| 
 | ||||
|     def _check_md5(self, filename): | ||||
|         """Check that the md5 checksum of the given file matches the one in | ||||
|         url param""" | ||||
|         hashname = self.url['hashname'] | ||||
|         expected_hashval = self.url['hashval'] | ||||
|         if not None in (expected_hashval, hashname): | ||||
|             with open(filename, 'rb') as f: | ||||
|                 hashval = hashlib.new(hashname) | ||||
|                 hashval.update(f.read()) | ||||
| 
 | ||||
|             if hashval.hexdigest() != expected_hashval: | ||||
|                 raise HashDoesNotMatch("got %s instead of %s" | ||||
|                     % (hashval.hexdigest(), expected_hashval)) | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         if self.release is None: | ||||
|             return "<? ? %s>" % self.dist_type | ||||
| 
 | ||||
|         return "<%s %s %s>" % ( | ||||
|             self.release.name, self.release.version, self.dist_type or "") | ||||
| 
 | ||||
| 
 | ||||
| class ReleasesList(IndexReference): | ||||
|     """A container of Release. | ||||
| 
 | ||||
|     Provides useful methods and facilities to sort and filter releases. | ||||
|     """ | ||||
|     def __init__(self, name, releases=None, contains_hidden=False, index=None): | ||||
|         self.set_index(index) | ||||
|         self.releases = [] | ||||
|         self.name = name | ||||
|         self.contains_hidden = contains_hidden | ||||
|         if releases: | ||||
|             self.add_releases(releases) | ||||
| 
 | ||||
|     def fetch_releases(self): | ||||
|         self._index.get_releases(self.name) | ||||
|         return self.releases | ||||
| 
 | ||||
|     def filter(self, predicate): | ||||
|         """Filter and return a subset of releases matching the given predicate. | ||||
|         """ | ||||
|         return ReleasesList(self.name, [release for release in self.releases | ||||
|                                         if predicate.match(release.version)], | ||||
|                                         index=self._index) | ||||
| 
 | ||||
|     def get_last(self, requirements, prefer_final=None): | ||||
|         """Return the "last" release, that satisfy the given predicates. | ||||
| 
 | ||||
|         "last" is defined by the version number of the releases, you also could | ||||
|         set prefer_final parameter to True or False to change the order results | ||||
|         """ | ||||
|         predicate = get_version_predicate(requirements) | ||||
|         releases = self.filter(predicate) | ||||
|         if len(releases) == 0: | ||||
|             return None | ||||
|         releases.sort_releases(prefer_final, reverse=True) | ||||
|         return releases[0] | ||||
| 
 | ||||
|     def add_releases(self, releases): | ||||
|         """Add releases in the release list. | ||||
| 
 | ||||
|         :param: releases is a list of ReleaseInfo objects. | ||||
|         """ | ||||
|         for r in releases: | ||||
|             self.add_release(release=r) | ||||
| 
 | ||||
|     def add_release(self, version=None, dist_type='sdist', release=None, | ||||
|                     **dist_args): | ||||
|         """Add a release to the list. | ||||
| 
 | ||||
|         The release can be passed in the `release` parameter, and in this case, | ||||
|         it will be crawled to extract the useful informations if necessary, or | ||||
|         the release informations can be directly passed in the `version` and | ||||
|         `dist_type` arguments. | ||||
| 
 | ||||
|         Other keywords arguments can be provided, and will be forwarded to the | ||||
|         distribution creation (eg. the arguments of the DistInfo constructor). | ||||
|         """ | ||||
|         if release: | ||||
|             if release.name.lower() != self.name.lower(): | ||||
|                 raise ValueError("%s is not the same project as %s" % | ||||
|                                  (release.name, self.name)) | ||||
|             version = str(release.version) | ||||
| 
 | ||||
|             if not version in self.get_versions(): | ||||
|                 # append only if not already exists | ||||
|                 self.releases.append(release) | ||||
|             for dist in release.dists.values(): | ||||
|                 for url in dist.urls: | ||||
|                     self.add_release(version, dist.dist_type, **url) | ||||
|         else: | ||||
|             matches = [r for r in self.releases | ||||
|                        if str(r.version) == version and r.name == self.name] | ||||
|             if not matches: | ||||
|                 release = ReleaseInfo(self.name, version, index=self._index) | ||||
|                 self.releases.append(release) | ||||
|             else: | ||||
|                 release = matches[0] | ||||
| 
 | ||||
|             release.add_distribution(dist_type=dist_type, **dist_args) | ||||
| 
 | ||||
|     def sort_releases(self, prefer_final=False, reverse=True, *args, **kwargs): | ||||
|         """Sort the results with the given properties. | ||||
| 
 | ||||
|         The `prefer_final` argument can be used to specify if final | ||||
|         distributions (eg. not dev, bet or alpha) would be prefered or not. | ||||
| 
 | ||||
|         Results can be inverted by using `reverse`. | ||||
| 
 | ||||
|         Any other parameter provided will be forwarded to the sorted call. You | ||||
|         cannot redefine the key argument of "sorted" here, as it is used | ||||
|         internally to sort the releases. | ||||
|         """ | ||||
| 
 | ||||
|         sort_by = [] | ||||
|         if prefer_final: | ||||
|             sort_by.append("is_final") | ||||
|         sort_by.append("version") | ||||
| 
 | ||||
|         self.releases.sort( | ||||
|             key=lambda i: tuple(getattr(i, arg) for arg in sort_by), | ||||
|             reverse=reverse, *args, **kwargs) | ||||
| 
 | ||||
|     def get_release(self, version): | ||||
|         """Return a release from its version.""" | ||||
|         matches = [r for r in self.releases if str(r.version) == version] | ||||
|         if len(matches) != 1: | ||||
|             raise KeyError(version) | ||||
|         return matches[0] | ||||
| 
 | ||||
|     def get_versions(self): | ||||
|         """Return a list of releases versions contained""" | ||||
|         return [str(r.version) for r in self.releases] | ||||
| 
 | ||||
|     def __getitem__(self, key): | ||||
|         return self.releases[key] | ||||
| 
 | ||||
|     def __len__(self): | ||||
|         return len(self.releases) | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         string = 'Project "%s"' % self.name | ||||
|         if self.get_versions(): | ||||
|             string += ' versions: %s' % ', '.join(self.get_versions()) | ||||
|         return '<%s>' % string | ||||
| 
 | ||||
| 
 | ||||
| def get_infos_from_url(url, probable_dist_name=None, is_external=True): | ||||
|     """Get useful informations from an URL. | ||||
| 
 | ||||
|     Return a dict of (name, version, url, hashtype, hash, is_external) | ||||
| 
 | ||||
|     :param url: complete url of the distribution | ||||
|     :param probable_dist_name: A probable name of the project. | ||||
|     :param is_external: Tell if the url commes from an index or from | ||||
|                         an external URL. | ||||
|     """ | ||||
|     # if the url contains a md5 hash, get it. | ||||
|     md5_hash = None | ||||
|     match = MD5_HASH.match(url) | ||||
|     if match is not None: | ||||
|         md5_hash = match.group(1) | ||||
|         # remove the hash | ||||
|         url = url.replace("#md5=%s" % md5_hash, "") | ||||
| 
 | ||||
|     # parse the archive name to find dist name and version | ||||
|     archive_name = urllib.parse.urlparse(url)[2].split('/')[-1] | ||||
|     extension_matched = False | ||||
|     # remove the extension from the name | ||||
|     for ext in EXTENSIONS: | ||||
|         if archive_name.endswith(ext): | ||||
|             archive_name = archive_name[:-len(ext)] | ||||
|             extension_matched = True | ||||
| 
 | ||||
|     name, version = split_archive_name(archive_name) | ||||
|     if extension_matched is True: | ||||
|         return {'name': name, | ||||
|                 'version': version, | ||||
|                 'url': url, | ||||
|                 'hashname': "md5", | ||||
|                 'hashval': md5_hash, | ||||
|                 'is_external': is_external, | ||||
|                 'dist_type': 'sdist'} | ||||
| 
 | ||||
| 
 | ||||
| def split_archive_name(archive_name, probable_name=None): | ||||
|     """Split an archive name into two parts: name and version. | ||||
| 
 | ||||
|     Return the tuple (name, version) | ||||
|     """ | ||||
|     # Try to determine wich part is the name and wich is the version using the | ||||
|     # "-" separator. Take the larger part to be the version number then reduce | ||||
|     # if this not works. | ||||
|     def eager_split(str, maxsplit=2): | ||||
|         # split using the "-" separator | ||||
|         splits = str.rsplit("-", maxsplit) | ||||
|         name = splits[0] | ||||
|         version = "-".join(splits[1:]) | ||||
|         if version.startswith("-"): | ||||
|             version = version[1:] | ||||
|         if suggest_normalized_version(version) is None and maxsplit >= 0: | ||||
|             # we dont get a good version number: recurse ! | ||||
|             return eager_split(str, maxsplit - 1) | ||||
|         else: | ||||
|             return name, version | ||||
|     if probable_name is not None: | ||||
|         probable_name = probable_name.lower() | ||||
|     name = None | ||||
|     if probable_name is not None and probable_name in archive_name: | ||||
|         # we get the name from probable_name, if given. | ||||
|         name = probable_name | ||||
|         version = archive_name.lstrip(name) | ||||
|     else: | ||||
|         name, version = eager_split(archive_name) | ||||
| 
 | ||||
|     version = suggest_normalized_version(version) | ||||
|     if version is not None and name != "": | ||||
|         return name.lower(), version | ||||
|     else: | ||||
|         raise CantParseArchiveName(archive_name) | ||||
							
								
								
									
										39
									
								
								Lib/packaging/pypi/errors.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								Lib/packaging/pypi/errors.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | |||
| """Exceptions raised by packaging.pypi code.""" | ||||
| 
 | ||||
| from packaging.errors import PackagingPyPIError | ||||
| 
 | ||||
| 
 | ||||
| class ProjectNotFound(PackagingPyPIError): | ||||
|     """Project has not been found""" | ||||
| 
 | ||||
| 
 | ||||
| class DistributionNotFound(PackagingPyPIError): | ||||
|     """The release has not been found""" | ||||
| 
 | ||||
| 
 | ||||
| class ReleaseNotFound(PackagingPyPIError): | ||||
|     """The release has not been found""" | ||||
| 
 | ||||
| 
 | ||||
| class CantParseArchiveName(PackagingPyPIError): | ||||
|     """An archive name can't be parsed to find distribution name and version""" | ||||
| 
 | ||||
| 
 | ||||
| class DownloadError(PackagingPyPIError): | ||||
|     """An error has occurs while downloading""" | ||||
| 
 | ||||
| 
 | ||||
| class HashDoesNotMatch(DownloadError): | ||||
|     """Compared hashes does not match""" | ||||
| 
 | ||||
| 
 | ||||
| class UnsupportedHashName(PackagingPyPIError): | ||||
|     """A unsupported hashname has been used""" | ||||
| 
 | ||||
| 
 | ||||
| class UnableToDownload(PackagingPyPIError): | ||||
|     """All mirrors have been tried, without success""" | ||||
| 
 | ||||
| 
 | ||||
| class InvalidSearchField(PackagingPyPIError): | ||||
|     """An invalid search field has been used""" | ||||
							
								
								
									
										52
									
								
								Lib/packaging/pypi/mirrors.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								Lib/packaging/pypi/mirrors.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | |||
| """Utilities related to the mirror infrastructure defined in PEP 381.""" | ||||
| 
 | ||||
| from string import ascii_lowercase | ||||
| import socket | ||||
| 
 | ||||
| DEFAULT_MIRROR_URL = "last.pypi.python.org" | ||||
| 
 | ||||
| 
 | ||||
| def get_mirrors(hostname=None): | ||||
|     """Return the list of mirrors from the last record found on the DNS | ||||
|     entry:: | ||||
| 
 | ||||
|     >>> from packaging.pypi.mirrors import get_mirrors | ||||
|     >>> get_mirrors() | ||||
|     ['a.pypi.python.org', 'b.pypi.python.org', 'c.pypi.python.org', | ||||
|     'd.pypi.python.org'] | ||||
| 
 | ||||
|     """ | ||||
|     if hostname is None: | ||||
|         hostname = DEFAULT_MIRROR_URL | ||||
| 
 | ||||
|     # return the last mirror registered on PyPI. | ||||
|     try: | ||||
|         hostname = socket.gethostbyname_ex(hostname)[0] | ||||
|     except socket.gaierror: | ||||
|         return [] | ||||
|     end_letter = hostname.split(".", 1) | ||||
| 
 | ||||
|     # determine the list from the last one. | ||||
|     return ["%s.%s" % (s, end_letter[1]) for s in string_range(end_letter[0])] | ||||
| 
 | ||||
| 
 | ||||
| def string_range(last): | ||||
|     """Compute the range of string between "a" and last. | ||||
| 
 | ||||
|     This works for simple "a to z" lists, but also for "a to zz" lists. | ||||
|     """ | ||||
|     for k in range(len(last)): | ||||
|         for x in product(ascii_lowercase, repeat=(k + 1)): | ||||
|             result = ''.join(x) | ||||
|             yield result | ||||
|             if result == last: | ||||
|                 return | ||||
| 
 | ||||
| 
 | ||||
| def product(*args, **kwds): | ||||
|     pools = [tuple(arg) for arg in args] * kwds.get('repeat', 1) | ||||
|     result = [[]] | ||||
|     for pool in pools: | ||||
|         result = [x + [y] for x in result for y in pool] | ||||
|     for prod in result: | ||||
|         yield tuple(prod) | ||||
							
								
								
									
										452
									
								
								Lib/packaging/pypi/simple.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										452
									
								
								Lib/packaging/pypi/simple.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,452 @@ | |||
| """Spider using the screen-scraping "simple" PyPI API. | ||||
| 
 | ||||
| This module contains the class SimpleIndexCrawler, a simple spider that | ||||
| can be used to find and retrieve distributions from a project index | ||||
| (like the Python Package Index), using its so-called simple API (see | ||||
| reference implementation available at http://pypi.python.org/simple/). | ||||
| """ | ||||
| 
 | ||||
| import http.client | ||||
| import re | ||||
| import socket | ||||
| import sys | ||||
| import urllib.request | ||||
| import urllib.parse | ||||
| import urllib.error | ||||
| import os | ||||
| 
 | ||||
| 
 | ||||
| from fnmatch import translate | ||||
| from packaging import logger | ||||
| from packaging.metadata import Metadata | ||||
| from packaging.version import get_version_predicate | ||||
| from packaging import __version__ as packaging_version | ||||
| from packaging.pypi.base import BaseClient | ||||
| from packaging.pypi.dist import (ReleasesList, EXTENSIONS, | ||||
|                                   get_infos_from_url, MD5_HASH) | ||||
| from packaging.pypi.errors import (PackagingPyPIError, DownloadError, | ||||
|                                     UnableToDownload, CantParseArchiveName, | ||||
|                                     ReleaseNotFound, ProjectNotFound) | ||||
| from packaging.pypi.mirrors import get_mirrors | ||||
| from packaging.metadata import Metadata | ||||
| 
 | ||||
| __all__ = ['Crawler', 'DEFAULT_SIMPLE_INDEX_URL'] | ||||
| 
 | ||||
| # -- Constants ----------------------------------------------- | ||||
| DEFAULT_SIMPLE_INDEX_URL = "http://a.pypi.python.org/simple/" | ||||
| DEFAULT_HOSTS = ("*",) | ||||
| SOCKET_TIMEOUT = 15 | ||||
| USER_AGENT = "Python-urllib/%s packaging/%s" % ( | ||||
|     sys.version[:3], packaging_version) | ||||
| 
 | ||||
| # -- Regexps ------------------------------------------------- | ||||
| EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.]+)$') | ||||
| HREF = re.compile("""href\\s*=\\s*['"]?([^'"> ]+)""", re.I) | ||||
| URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):', re.I).match | ||||
| 
 | ||||
| # This pattern matches a character entity reference (a decimal numeric | ||||
| # references, a hexadecimal numeric reference, or a named reference). | ||||
| ENTITY_SUB = re.compile(r'&(#(\d+|x[\da-fA-F]+)|[\w.:-]+);?').sub | ||||
| REL = re.compile("""<([^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*)>""", re.I) | ||||
| 
 | ||||
| 
 | ||||
| def socket_timeout(timeout=SOCKET_TIMEOUT): | ||||
|     """Decorator to add a socket timeout when requesting pages on PyPI. | ||||
|     """ | ||||
|     def _socket_timeout(func): | ||||
|         def _socket_timeout(self, *args, **kwargs): | ||||
|             old_timeout = socket.getdefaulttimeout() | ||||
|             if hasattr(self, "_timeout"): | ||||
|                 timeout = self._timeout | ||||
|             socket.setdefaulttimeout(timeout) | ||||
|             try: | ||||
|                 return func(self, *args, **kwargs) | ||||
|             finally: | ||||
|                 socket.setdefaulttimeout(old_timeout) | ||||
|         return _socket_timeout | ||||
|     return _socket_timeout | ||||
| 
 | ||||
| 
 | ||||
| def with_mirror_support(): | ||||
|     """Decorator that makes the mirroring support easier""" | ||||
|     def wrapper(func): | ||||
|         def wrapped(self, *args, **kwargs): | ||||
|             try: | ||||
|                 return func(self, *args, **kwargs) | ||||
|             except DownloadError: | ||||
|                 # if an error occurs, try with the next index_url | ||||
|                 if self._mirrors_tries >= self._mirrors_max_tries: | ||||
|                     try: | ||||
|                         self._switch_to_next_mirror() | ||||
|                     except KeyError: | ||||
|                         raise UnableToDownload("Tried all mirrors") | ||||
|                 else: | ||||
|                     self._mirrors_tries += 1 | ||||
|                 self._projects.clear() | ||||
|                 return wrapped(self, *args, **kwargs) | ||||
|         return wrapped | ||||
|     return wrapper | ||||
| 
 | ||||
| 
 | ||||
| class Crawler(BaseClient): | ||||
|     """Provides useful tools to request the Python Package Index simple API. | ||||
| 
 | ||||
|     You can specify both mirrors and mirrors_url, but mirrors_url will only be | ||||
|     used if mirrors is set to None. | ||||
| 
 | ||||
|     :param index_url: the url of the simple index to search on. | ||||
|     :param prefer_final: if the version is not mentioned, and the last | ||||
|                          version is not a "final" one (alpha, beta, etc.), | ||||
|                          pick up the last final version. | ||||
|     :param prefer_source: if the distribution type is not mentioned, pick up | ||||
|                           the source one if available. | ||||
|     :param follow_externals: tell if following external links is needed or | ||||
|                              not. Default is False. | ||||
|     :param hosts: a list of hosts allowed to be processed while using | ||||
|                   follow_externals=True. Default behavior is to follow all | ||||
|                   hosts. | ||||
|     :param follow_externals: tell if following external links is needed or | ||||
|                              not. Default is False. | ||||
|     :param mirrors_url: the url to look on for DNS records giving mirror | ||||
|                         adresses. | ||||
|     :param mirrors: a list of mirrors (see PEP 381). | ||||
|     :param timeout: time in seconds to consider a url has timeouted. | ||||
|     :param mirrors_max_tries": number of times to try requesting informations | ||||
|                                on mirrors before switching. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, index_url=DEFAULT_SIMPLE_INDEX_URL, prefer_final=False, | ||||
|                  prefer_source=True, hosts=DEFAULT_HOSTS, | ||||
|                  follow_externals=False, mirrors_url=None, mirrors=None, | ||||
|                  timeout=SOCKET_TIMEOUT, mirrors_max_tries=0): | ||||
|         super(Crawler, self).__init__(prefer_final, prefer_source) | ||||
|         self.follow_externals = follow_externals | ||||
| 
 | ||||
|         # mirroring attributes. | ||||
|         if not index_url.endswith("/"): | ||||
|             index_url += "/" | ||||
|         # if no mirrors are defined, use the method described in PEP 381. | ||||
|         if mirrors is None: | ||||
|             mirrors = get_mirrors(mirrors_url) | ||||
|         self._mirrors = set(mirrors) | ||||
|         self._mirrors_used = set() | ||||
|         self.index_url = index_url | ||||
|         self._mirrors_max_tries = mirrors_max_tries | ||||
|         self._mirrors_tries = 0 | ||||
|         self._timeout = timeout | ||||
| 
 | ||||
|         # create a regexp to match all given hosts | ||||
|         self._allowed_hosts = re.compile('|'.join(map(translate, hosts))).match | ||||
| 
 | ||||
|         # we keep an index of pages we have processed, in order to avoid | ||||
|         # scanning them multple time (eg. if there is multiple pages pointing | ||||
|         # on one) | ||||
|         self._processed_urls = [] | ||||
|         self._projects = {} | ||||
| 
 | ||||
|     @with_mirror_support() | ||||
|     def search_projects(self, name=None, **kwargs): | ||||
|         """Search the index for projects containing the given name. | ||||
| 
 | ||||
|         Return a list of names. | ||||
|         """ | ||||
|         with self._open_url(self.index_url) as index: | ||||
|             if '*' in name: | ||||
|                 name.replace('*', '.*') | ||||
|             else: | ||||
|                 name = "%s%s%s" % ('*.?', name, '*.?') | ||||
|             name = name.replace('*', '[^<]*')  # avoid matching end tag | ||||
|             projectname = re.compile('<a[^>]*>(%s)</a>' % name, re.I) | ||||
|             matching_projects = [] | ||||
| 
 | ||||
|             index_content = index.read() | ||||
| 
 | ||||
|         # FIXME should use bytes I/O and regexes instead of decoding | ||||
|         index_content = index_content.decode() | ||||
| 
 | ||||
|         for match in projectname.finditer(index_content): | ||||
|             project_name = match.group(1) | ||||
|             matching_projects.append(self._get_project(project_name)) | ||||
|         return matching_projects | ||||
| 
 | ||||
|     def get_releases(self, requirements, prefer_final=None, | ||||
|                      force_update=False): | ||||
|         """Search for releases and return a ReleaseList object containing | ||||
|         the results. | ||||
|         """ | ||||
|         predicate = get_version_predicate(requirements) | ||||
|         if predicate.name.lower() in self._projects and not force_update: | ||||
|             return self._projects.get(predicate.name.lower()) | ||||
|         prefer_final = self._get_prefer_final(prefer_final) | ||||
|         logger.info('reading info on PyPI about %s', predicate.name) | ||||
|         self._process_index_page(predicate.name) | ||||
| 
 | ||||
|         if predicate.name.lower() not in self._projects: | ||||
|             raise ProjectNotFound() | ||||
| 
 | ||||
|         releases = self._projects.get(predicate.name.lower()) | ||||
|         releases.sort_releases(prefer_final=prefer_final) | ||||
|         return releases | ||||
| 
 | ||||
|     def get_release(self, requirements, prefer_final=None): | ||||
|         """Return only one release that fulfill the given requirements""" | ||||
|         predicate = get_version_predicate(requirements) | ||||
|         release = self.get_releases(predicate, prefer_final)\ | ||||
|                       .get_last(predicate) | ||||
|         if not release: | ||||
|             raise ReleaseNotFound("No release matches the given criterias") | ||||
|         return release | ||||
| 
 | ||||
|     def get_distributions(self, project_name, version): | ||||
|         """Return the distributions found on the index for the specific given | ||||
|         release""" | ||||
|         # as the default behavior of get_release is to return a release | ||||
|         # containing the distributions, just alias it. | ||||
|         return self.get_release("%s (%s)" % (project_name, version)) | ||||
| 
 | ||||
|     def get_metadata(self, project_name, version): | ||||
|         """Return the metadatas from the simple index. | ||||
| 
 | ||||
|         Currently, download one archive, extract it and use the PKG-INFO file. | ||||
|         """ | ||||
|         release = self.get_distributions(project_name, version) | ||||
|         if not release.metadata: | ||||
|             location = release.get_distribution().unpack() | ||||
|             pkg_info = os.path.join(location, 'PKG-INFO') | ||||
|             release.metadata = Metadata(pkg_info) | ||||
|         return release | ||||
| 
 | ||||
|     def _switch_to_next_mirror(self): | ||||
|         """Switch to the next mirror (eg. point self.index_url to the next | ||||
|         mirror url. | ||||
| 
 | ||||
|         Raise a KeyError if all mirrors have been tried. | ||||
|         """ | ||||
|         self._mirrors_used.add(self.index_url) | ||||
|         index_url = self._mirrors.pop() | ||||
|         if not ("http://" or "https://" or "file://") in index_url: | ||||
|             index_url = "http://%s" % index_url | ||||
| 
 | ||||
|         if not index_url.endswith("/simple"): | ||||
|             index_url = "%s/simple/" % index_url | ||||
| 
 | ||||
|         self.index_url = index_url | ||||
| 
 | ||||
|     def _is_browsable(self, url): | ||||
|         """Tell if the given URL can be browsed or not. | ||||
| 
 | ||||
|         It uses the follow_externals and the hosts list to tell if the given | ||||
|         url is browsable or not. | ||||
|         """ | ||||
|         # if _index_url is contained in the given URL, we are browsing the | ||||
|         # index, and it's always "browsable". | ||||
|         # local files are always considered browable resources | ||||
|         if self.index_url in url or urllib.parse.urlparse(url)[0] == "file": | ||||
|             return True | ||||
|         elif self.follow_externals: | ||||
|             if self._allowed_hosts(urllib.parse.urlparse(url)[1]):  # 1 is netloc | ||||
|                 return True | ||||
|             else: | ||||
|                 return False | ||||
|         return False | ||||
| 
 | ||||
|     def _is_distribution(self, link): | ||||
|         """Tell if the given URL matches to a distribution name or not. | ||||
|         """ | ||||
|         #XXX find a better way to check that links are distributions | ||||
|         # Using a regexp ? | ||||
|         for ext in EXTENSIONS: | ||||
|             if ext in link: | ||||
|                 return True | ||||
|         return False | ||||
| 
 | ||||
|     def _register_release(self, release=None, release_info={}): | ||||
|         """Register a new release. | ||||
| 
 | ||||
|         Both a release or a dict of release_info can be provided, the prefered | ||||
|         way (eg. the quicker) is the dict one. | ||||
| 
 | ||||
|         Return the list of existing releases for the given project. | ||||
|         """ | ||||
|         # Check if the project already has a list of releases (refering to | ||||
|         # the project name). If not, create a new release list. | ||||
|         # Then, add the release to the list. | ||||
|         if release: | ||||
|             name = release.name | ||||
|         else: | ||||
|             name = release_info['name'] | ||||
|         if not name.lower() in self._projects: | ||||
|             self._projects[name.lower()] = ReleasesList(name, index=self._index) | ||||
| 
 | ||||
|         if release: | ||||
|             self._projects[name.lower()].add_release(release=release) | ||||
|         else: | ||||
|             name = release_info.pop('name') | ||||
|             version = release_info.pop('version') | ||||
|             dist_type = release_info.pop('dist_type') | ||||
|             self._projects[name.lower()].add_release(version, dist_type, | ||||
|                                                      **release_info) | ||||
|         return self._projects[name.lower()] | ||||
| 
 | ||||
|     def _process_url(self, url, project_name=None, follow_links=True): | ||||
|         """Process an url and search for distributions packages. | ||||
| 
 | ||||
|         For each URL found, if it's a download, creates a PyPIdistribution | ||||
|         object. If it's a homepage and we can follow links, process it too. | ||||
| 
 | ||||
|         :param url: the url to process | ||||
|         :param project_name: the project name we are searching for. | ||||
|         :param follow_links: Do not want to follow links more than from one | ||||
|                              level. This parameter tells if we want to follow | ||||
|                              the links we find (eg. run recursively this | ||||
|                              method on it) | ||||
|         """ | ||||
|         with self._open_url(url) as f: | ||||
|             base_url = f.url | ||||
|             if url not in self._processed_urls: | ||||
|                 self._processed_urls.append(url) | ||||
|                 link_matcher = self._get_link_matcher(url) | ||||
|                 for link, is_download in link_matcher(f.read().decode(), base_url): | ||||
|                     if link not in self._processed_urls: | ||||
|                         if self._is_distribution(link) or is_download: | ||||
|                             self._processed_urls.append(link) | ||||
|                             # it's a distribution, so create a dist object | ||||
|                             try: | ||||
|                                 infos = get_infos_from_url(link, project_name, | ||||
|                                             is_external=not self.index_url in url) | ||||
|                             except CantParseArchiveName as e: | ||||
|                                 logger.warning( | ||||
|                                     "version has not been parsed: %s", e) | ||||
|                             else: | ||||
|                                 self._register_release(release_info=infos) | ||||
|                         else: | ||||
|                             if self._is_browsable(link) and follow_links: | ||||
|                                 self._process_url(link, project_name, | ||||
|                                     follow_links=False) | ||||
| 
 | ||||
|     def _get_link_matcher(self, url): | ||||
|         """Returns the right link matcher function of the given url | ||||
|         """ | ||||
|         if self.index_url in url: | ||||
|             return self._simple_link_matcher | ||||
|         else: | ||||
|             return self._default_link_matcher | ||||
| 
 | ||||
|     def _get_full_url(self, url, base_url): | ||||
|         return urllib.parse.urljoin(base_url, self._htmldecode(url)) | ||||
| 
 | ||||
|     def _simple_link_matcher(self, content, base_url): | ||||
|         """Yield all links with a rel="download" or rel="homepage". | ||||
| 
 | ||||
|         This matches the simple index requirements for matching links. | ||||
|         If follow_externals is set to False, dont yeld the external | ||||
|         urls. | ||||
| 
 | ||||
|         :param content: the content of the page we want to parse | ||||
|         :param base_url: the url of this page. | ||||
|         """ | ||||
|         for match in HREF.finditer(content): | ||||
|             url = self._get_full_url(match.group(1), base_url) | ||||
|             if MD5_HASH.match(url): | ||||
|                 yield (url, True) | ||||
| 
 | ||||
|         for match in REL.finditer(content): | ||||
|             # search for rel links. | ||||
|             tag, rel = match.groups() | ||||
|             rels = [s.strip() for s in rel.lower().split(',')] | ||||
|             if 'homepage' in rels or 'download' in rels: | ||||
|                 for match in HREF.finditer(tag): | ||||
|                     url = self._get_full_url(match.group(1), base_url) | ||||
|                     if 'download' in rels or self._is_browsable(url): | ||||
|                         # yield a list of (url, is_download) | ||||
|                         yield (url, 'download' in rels) | ||||
| 
 | ||||
|     def _default_link_matcher(self, content, base_url): | ||||
|         """Yield all links found on the page. | ||||
|         """ | ||||
|         for match in HREF.finditer(content): | ||||
|             url = self._get_full_url(match.group(1), base_url) | ||||
|             if self._is_browsable(url): | ||||
|                 yield (url, False) | ||||
| 
 | ||||
|     @with_mirror_support() | ||||
|     def _process_index_page(self, name): | ||||
|         """Find and process a PyPI page for the given project name. | ||||
| 
 | ||||
|         :param name: the name of the project to find the page | ||||
|         """ | ||||
|         # Browse and index the content of the given PyPI page. | ||||
|         url = self.index_url + name + "/" | ||||
|         self._process_url(url, name) | ||||
| 
 | ||||
|     @socket_timeout() | ||||
|     def _open_url(self, url): | ||||
|         """Open a urllib2 request, handling HTTP authentication, and local | ||||
|         files support. | ||||
| 
 | ||||
|         """ | ||||
|         scheme, netloc, path, params, query, frag = urllib.parse.urlparse(url) | ||||
| 
 | ||||
|         # authentication stuff | ||||
|         if scheme in ('http', 'https'): | ||||
|             auth, host = urllib.parse.splituser(netloc) | ||||
|         else: | ||||
|             auth = None | ||||
| 
 | ||||
|         # add index.html automatically for filesystem paths | ||||
|         if scheme == 'file': | ||||
|             if url.endswith('/'): | ||||
|                 url += "index.html" | ||||
| 
 | ||||
|         # add authorization headers if auth is provided | ||||
|         if auth: | ||||
|             auth = "Basic " + \ | ||||
|                 urllib.parse.unquote(auth).encode('base64').strip() | ||||
|             new_url = urllib.parse.urlunparse(( | ||||
|                 scheme, host, path, params, query, frag)) | ||||
|             request = urllib.request.Request(new_url) | ||||
|             request.add_header("Authorization", auth) | ||||
|         else: | ||||
|             request = urllib.request.Request(url) | ||||
|         request.add_header('User-Agent', USER_AGENT) | ||||
|         try: | ||||
|             fp = urllib.request.urlopen(request) | ||||
|         except (ValueError, http.client.InvalidURL) as v: | ||||
|             msg = ' '.join([str(arg) for arg in v.args]) | ||||
|             raise PackagingPyPIError('%s %s' % (url, msg)) | ||||
|         except urllib.error.HTTPError as v: | ||||
|             return v | ||||
|         except urllib.error.URLError as v: | ||||
|             raise DownloadError("Download error for %s: %s" % (url, v.reason)) | ||||
|         except http.client.BadStatusLine as v: | ||||
|             raise DownloadError('%s returned a bad status line. ' | ||||
|                 'The server might be down, %s' % (url, v.line)) | ||||
|         except http.client.HTTPException as v: | ||||
|             raise DownloadError("Download error for %s: %s" % (url, v)) | ||||
|         except socket.timeout: | ||||
|             raise DownloadError("The server timeouted") | ||||
| 
 | ||||
|         if auth: | ||||
|             # Put authentication info back into request URL if same host, | ||||
|             # so that links found on the page will work | ||||
|             s2, h2, path2, param2, query2, frag2 = \ | ||||
|                 urllib.parse.urlparse(fp.url) | ||||
|             if s2 == scheme and h2 == host: | ||||
|                 fp.url = urllib.parse.urlunparse( | ||||
|                     (s2, netloc, path2, param2, query2, frag2)) | ||||
|         return fp | ||||
| 
 | ||||
|     def _decode_entity(self, match): | ||||
|         what = match.group(1) | ||||
|         if what.startswith('#x'): | ||||
|             what = int(what[2:], 16) | ||||
|         elif what.startswith('#'): | ||||
|             what = int(what[1:]) | ||||
|         else: | ||||
|             from html.entities import name2codepoint | ||||
|             what = name2codepoint.get(what, match.group(0)) | ||||
|         return chr(what) | ||||
| 
 | ||||
|     def _htmldecode(self, text): | ||||
|         """Decode HTML entities in the given text.""" | ||||
|         return ENTITY_SUB(self._decode_entity, text) | ||||
							
								
								
									
										99
									
								
								Lib/packaging/pypi/wrapper.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								Lib/packaging/pypi/wrapper.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,99 @@ | |||
| """Convenient client for all PyPI APIs. | ||||
| 
 | ||||
| This module provides a ClientWrapper class which will use the "simple" | ||||
| or XML-RPC API to request information or files from an index. | ||||
| """ | ||||
| 
 | ||||
| from packaging.pypi import simple, xmlrpc | ||||
| 
 | ||||
| _WRAPPER_MAPPINGS = {'get_release': 'simple', | ||||
|                      'get_releases': 'simple', | ||||
|                      'search_projects': 'simple', | ||||
|                      'get_metadata': 'xmlrpc', | ||||
|                      'get_distributions': 'simple'} | ||||
| 
 | ||||
| _WRAPPER_INDEXES = {'xmlrpc': xmlrpc.Client, | ||||
|                     'simple': simple.Crawler} | ||||
| 
 | ||||
| 
 | ||||
| def switch_index_if_fails(func, wrapper): | ||||
|     """Decorator that switch of index (for instance from xmlrpc to simple) | ||||
|     if the first mirror return an empty list or raises an exception. | ||||
|     """ | ||||
|     def decorator(*args, **kwargs): | ||||
|         retry = True | ||||
|         exception = None | ||||
|         methods = [func] | ||||
|         for f in wrapper._indexes.values(): | ||||
|             if f != func.__self__ and hasattr(f, func.__name__): | ||||
|                 methods.append(getattr(f, func.__name__)) | ||||
|         for method in methods: | ||||
|             try: | ||||
|                 response = method(*args, **kwargs) | ||||
|                 retry = False | ||||
|             except Exception as e: | ||||
|                 exception = e | ||||
|             if not retry: | ||||
|                 break | ||||
|         if retry and exception: | ||||
|             raise exception | ||||
|         else: | ||||
|             return response | ||||
|     return decorator | ||||
| 
 | ||||
| 
 | ||||
| class ClientWrapper: | ||||
|     """Wrapper around simple and xmlrpc clients, | ||||
| 
 | ||||
|     Choose the best implementation to use depending the needs, using the given | ||||
|     mappings. | ||||
|     If one of the indexes returns an error, tries to use others indexes. | ||||
| 
 | ||||
|     :param index: tell which index to rely on by default. | ||||
|     :param index_classes: a dict of name:class to use as indexes. | ||||
|     :param indexes: a dict of name:index already instantiated | ||||
|     :param mappings: the mappings to use for this wrapper | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, default_index='simple', index_classes=_WRAPPER_INDEXES, | ||||
|                  indexes={}, mappings=_WRAPPER_MAPPINGS): | ||||
|         self._projects = {} | ||||
|         self._mappings = mappings | ||||
|         self._indexes = indexes | ||||
|         self._default_index = default_index | ||||
| 
 | ||||
|         # instantiate the classes and set their _project attribute to the one | ||||
|         # of the wrapper. | ||||
|         for name, cls in index_classes.items(): | ||||
|             obj = self._indexes.setdefault(name, cls()) | ||||
|             obj._projects = self._projects | ||||
|             obj._index = self | ||||
| 
 | ||||
|     def __getattr__(self, method_name): | ||||
|         """When asking for methods of the wrapper, return the implementation of | ||||
|         the wrapped classes, depending the mapping. | ||||
| 
 | ||||
|         Decorate the methods to switch of implementation if an error occurs | ||||
|         """ | ||||
|         real_method = None | ||||
|         if method_name in _WRAPPER_MAPPINGS: | ||||
|             obj = self._indexes[_WRAPPER_MAPPINGS[method_name]] | ||||
|             real_method = getattr(obj, method_name) | ||||
|         else: | ||||
|             # the method is not defined in the mappings, so we try first to get | ||||
|             # it via the default index, and rely on others if needed. | ||||
|             try: | ||||
|                 real_method = getattr(self._indexes[self._default_index], | ||||
|                                       method_name) | ||||
|             except AttributeError: | ||||
|                 other_indexes = [i for i in self._indexes | ||||
|                                  if i != self._default_index] | ||||
|                 for index in other_indexes: | ||||
|                     real_method = getattr(self._indexes[index], method_name, | ||||
|                                           None) | ||||
|                     if real_method: | ||||
|                         break | ||||
|         if real_method: | ||||
|             return switch_index_if_fails(real_method, self) | ||||
|         else: | ||||
|             raise AttributeError("No index have attribute '%s'" % method_name) | ||||
							
								
								
									
										200
									
								
								Lib/packaging/pypi/xmlrpc.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								Lib/packaging/pypi/xmlrpc.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,200 @@ | |||
| """Spider using the XML-RPC PyPI API. | ||||
| 
 | ||||
| This module contains the class Client, a spider that can be used to find | ||||
| and retrieve distributions from a project index (like the Python Package | ||||
| Index), using its XML-RPC API (see documentation of the reference | ||||
| implementation at http://wiki.python.org/moin/PyPiXmlRpc). | ||||
| """ | ||||
| 
 | ||||
| import xmlrpc.client | ||||
| 
 | ||||
| from packaging import logger | ||||
| from packaging.errors import IrrationalVersionError | ||||
| from packaging.version import get_version_predicate | ||||
| from packaging.pypi.base import BaseClient | ||||
| from packaging.pypi.errors import (ProjectNotFound, InvalidSearchField, | ||||
|                                    ReleaseNotFound) | ||||
| from packaging.pypi.dist import ReleaseInfo | ||||
| 
 | ||||
| __all__ = ['Client', 'DEFAULT_XMLRPC_INDEX_URL'] | ||||
| 
 | ||||
| DEFAULT_XMLRPC_INDEX_URL = 'http://python.org/pypi' | ||||
| 
 | ||||
| _SEARCH_FIELDS = ['name', 'version', 'author', 'author_email', 'maintainer', | ||||
|                   'maintainer_email', 'home_page', 'license', 'summary', | ||||
|                   'description', 'keywords', 'platform', 'download_url'] | ||||
| 
 | ||||
| 
 | ||||
| class Client(BaseClient): | ||||
|     """Client to query indexes using XML-RPC method calls. | ||||
| 
 | ||||
|     If no server_url is specified, use the default PyPI XML-RPC URL, | ||||
|     defined in the DEFAULT_XMLRPC_INDEX_URL constant:: | ||||
| 
 | ||||
|         >>> client = XMLRPCClient() | ||||
|         >>> client.server_url == DEFAULT_XMLRPC_INDEX_URL | ||||
|         True | ||||
| 
 | ||||
|         >>> client = XMLRPCClient("http://someurl/") | ||||
|         >>> client.server_url | ||||
|         'http://someurl/' | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, server_url=DEFAULT_XMLRPC_INDEX_URL, prefer_final=False, | ||||
|                  prefer_source=True): | ||||
|         super(Client, self).__init__(prefer_final, prefer_source) | ||||
|         self.server_url = server_url | ||||
|         self._projects = {} | ||||
| 
 | ||||
|     def get_release(self, requirements, prefer_final=False): | ||||
|         """Return a release with all complete metadata and distribution | ||||
|         related informations. | ||||
|         """ | ||||
|         prefer_final = self._get_prefer_final(prefer_final) | ||||
|         predicate = get_version_predicate(requirements) | ||||
|         releases = self.get_releases(predicate.name) | ||||
|         release = releases.get_last(predicate, prefer_final) | ||||
|         self.get_metadata(release.name, str(release.version)) | ||||
|         self.get_distributions(release.name, str(release.version)) | ||||
|         return release | ||||
| 
 | ||||
|     def get_releases(self, requirements, prefer_final=None, show_hidden=True, | ||||
|                      force_update=False): | ||||
|         """Return the list of existing releases for a specific project. | ||||
| 
 | ||||
|         Cache the results from one call to another. | ||||
| 
 | ||||
|         If show_hidden is True, return the hidden releases too. | ||||
|         If force_update is True, reprocess the index to update the | ||||
|         informations (eg. make a new XML-RPC call). | ||||
|         :: | ||||
| 
 | ||||
|             >>> client = XMLRPCClient() | ||||
|             >>> client.get_releases('Foo') | ||||
|             ['1.1', '1.2', '1.3'] | ||||
| 
 | ||||
|         If no such project exists, raise a ProjectNotFound exception:: | ||||
| 
 | ||||
|             >>> client.get_project_versions('UnexistingProject') | ||||
|             ProjectNotFound: UnexistingProject | ||||
| 
 | ||||
|         """ | ||||
|         def get_versions(project_name, show_hidden): | ||||
|             return self.proxy.package_releases(project_name, show_hidden) | ||||
| 
 | ||||
|         predicate = get_version_predicate(requirements) | ||||
|         prefer_final = self._get_prefer_final(prefer_final) | ||||
|         project_name = predicate.name | ||||
|         if not force_update and (project_name.lower() in self._projects): | ||||
|             project = self._projects[project_name.lower()] | ||||
|             if not project.contains_hidden and show_hidden: | ||||
|                 # if hidden releases are requested, and have an existing | ||||
|                 # list of releases that does not contains hidden ones | ||||
|                 all_versions = get_versions(project_name, show_hidden) | ||||
|                 existing_versions = project.get_versions() | ||||
|                 hidden_versions = set(all_versions) - set(existing_versions) | ||||
|                 for version in hidden_versions: | ||||
|                     project.add_release(release=ReleaseInfo(project_name, | ||||
|                                             version, index=self._index)) | ||||
|         else: | ||||
|             versions = get_versions(project_name, show_hidden) | ||||
|             if not versions: | ||||
|                 raise ProjectNotFound(project_name) | ||||
|             project = self._get_project(project_name) | ||||
|             project.add_releases([ReleaseInfo(project_name, version, | ||||
|                                               index=self._index) | ||||
|                                   for version in versions]) | ||||
|         project = project.filter(predicate) | ||||
|         if len(project) == 0: | ||||
|             raise ReleaseNotFound("%s" % predicate) | ||||
|         project.sort_releases(prefer_final) | ||||
|         return project | ||||
| 
 | ||||
| 
 | ||||
|     def get_distributions(self, project_name, version): | ||||
|         """Grab informations about distributions from XML-RPC. | ||||
| 
 | ||||
|         Return a ReleaseInfo object, with distribution-related informations | ||||
|         filled in. | ||||
|         """ | ||||
|         url_infos = self.proxy.release_urls(project_name, version) | ||||
|         project = self._get_project(project_name) | ||||
|         if version not in project.get_versions(): | ||||
|             project.add_release(release=ReleaseInfo(project_name, version, | ||||
|                                                     index=self._index)) | ||||
|         release = project.get_release(version) | ||||
|         for info in url_infos: | ||||
|             packagetype = info['packagetype'] | ||||
|             dist_infos = {'url': info['url'], | ||||
|                           'hashval': info['md5_digest'], | ||||
|                           'hashname': 'md5', | ||||
|                           'is_external': False, | ||||
|                           'python_version': info['python_version']} | ||||
|             release.add_distribution(packagetype, **dist_infos) | ||||
|         return release | ||||
| 
 | ||||
|     def get_metadata(self, project_name, version): | ||||
|         """Retrieve project metadata. | ||||
| 
 | ||||
|         Return a ReleaseInfo object, with metadata informations filled in. | ||||
|         """ | ||||
|         # to be case-insensitive, get the informations from the XMLRPC API | ||||
|         projects = [d['name'] for d in | ||||
|                     self.proxy.search({'name': project_name}) | ||||
|                     if d['name'].lower() == project_name] | ||||
|         if len(projects) > 0: | ||||
|             project_name = projects[0] | ||||
| 
 | ||||
|         metadata = self.proxy.release_data(project_name, version) | ||||
|         project = self._get_project(project_name) | ||||
|         if version not in project.get_versions(): | ||||
|             project.add_release(release=ReleaseInfo(project_name, version, | ||||
|                                                     index=self._index)) | ||||
|         release = project.get_release(version) | ||||
|         release.set_metadata(metadata) | ||||
|         return release | ||||
| 
 | ||||
|     def search_projects(self, name=None, operator="or", **kwargs): | ||||
|         """Find using the keys provided in kwargs. | ||||
| 
 | ||||
|         You can set operator to "and" or "or". | ||||
|         """ | ||||
|         for key in kwargs: | ||||
|             if key not in _SEARCH_FIELDS: | ||||
|                 raise InvalidSearchField(key) | ||||
|         if name: | ||||
|             kwargs["name"] = name | ||||
|         projects = self.proxy.search(kwargs, operator) | ||||
|         for p in projects: | ||||
|             project = self._get_project(p['name']) | ||||
|             try: | ||||
|                 project.add_release(release=ReleaseInfo(p['name'], | ||||
|                     p['version'], metadata={'summary': p['summary']}, | ||||
|                     index=self._index)) | ||||
|             except IrrationalVersionError as e: | ||||
|                 logger.warning("Irrational version error found: %s", e) | ||||
|         return [self._projects[p['name'].lower()] for p in projects] | ||||
| 
 | ||||
|     def get_all_projects(self): | ||||
|         """Return the list of all projects registered in the package index""" | ||||
|         projects = self.proxy.list_packages() | ||||
|         for name in projects: | ||||
|             self.get_releases(name, show_hidden=True) | ||||
| 
 | ||||
|         return [self._projects[name.lower()] for name in set(projects)] | ||||
| 
 | ||||
|     @property | ||||
|     def proxy(self): | ||||
|         """Property used to return the XMLRPC server proxy. | ||||
| 
 | ||||
|         If no server proxy is defined yet, creates a new one:: | ||||
| 
 | ||||
|             >>> client = XmlRpcClient() | ||||
|             >>> client.proxy() | ||||
|             <ServerProxy for python.org/pypi> | ||||
| 
 | ||||
|         """ | ||||
|         if not hasattr(self, '_server_proxy'): | ||||
|             self._server_proxy = xmlrpc.client.ServerProxy(self.server_url) | ||||
| 
 | ||||
|         return self._server_proxy | ||||
							
								
								
									
										25
									
								
								Lib/packaging/resources.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Lib/packaging/resources.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| """Data file path abstraction. | ||||
| 
 | ||||
| Functions in this module use sysconfig to find the paths to the resource | ||||
| files registered in project's setup.cfg file.  See the documentation for | ||||
| more information. | ||||
| """ | ||||
| # TODO write that documentation | ||||
| 
 | ||||
| from packaging.database import get_distribution | ||||
| 
 | ||||
| __all__ = ['get_file_path', 'get_file'] | ||||
| 
 | ||||
| 
 | ||||
| def get_file_path(distribution_name, relative_path): | ||||
|     """Return the path to a resource file.""" | ||||
|     dist = get_distribution(distribution_name) | ||||
|     if dist != None: | ||||
|         return dist.get_resource_path(relative_path) | ||||
|     raise LookupError('no distribution named %r found' % distribution_name) | ||||
| 
 | ||||
| 
 | ||||
| def get_file(distribution_name, relative_path, *args, **kwargs): | ||||
|     """Open and return a resource file.""" | ||||
|     return open(get_file_path(distribution_name, relative_path), | ||||
|                 *args, **kwargs) | ||||
							
								
								
									
										645
									
								
								Lib/packaging/run.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										645
									
								
								Lib/packaging/run.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,645 @@ | |||
| """Main command line parser.  Implements the pysetup script.""" | ||||
| 
 | ||||
| import os | ||||
| import re | ||||
| import sys | ||||
| import getopt | ||||
| import logging | ||||
| 
 | ||||
| from packaging import logger | ||||
| from packaging.dist import Distribution | ||||
| from packaging.util import _is_archive_file | ||||
| from packaging.command import get_command_class, STANDARD_COMMANDS | ||||
| from packaging.install import install, install_local_project, remove | ||||
| from packaging.database import get_distribution, get_distributions | ||||
| from packaging.depgraph import generate_graph | ||||
| from packaging.fancy_getopt import FancyGetopt | ||||
| from packaging.errors import (PackagingArgError, PackagingError, | ||||
|                               PackagingModuleError, PackagingClassError, | ||||
|                               CCompilerError) | ||||
| 
 | ||||
| 
 | ||||
| command_re = re.compile(r'^[a-zA-Z]([a-zA-Z0-9_]*)$') | ||||
| 
 | ||||
| common_usage = """\ | ||||
| Actions: | ||||
| %(actions)s | ||||
| 
 | ||||
| To get more help on an action, use: | ||||
| 
 | ||||
|     pysetup action --help | ||||
| """ | ||||
| 
 | ||||
| create_usage = """\ | ||||
| Usage: pysetup create | ||||
|    or: pysetup create --help | ||||
| 
 | ||||
| Create a new Python package. | ||||
| """ | ||||
| 
 | ||||
| graph_usage = """\ | ||||
| Usage: pysetup graph dist | ||||
|    or: pysetup graph --help | ||||
| 
 | ||||
| Print dependency graph for the distribution. | ||||
| 
 | ||||
| positional arguments: | ||||
|    dist  installed distribution name | ||||
| """ | ||||
| 
 | ||||
| install_usage = """\ | ||||
| Usage: pysetup install [dist] | ||||
|    or: pysetup install [archive] | ||||
|    or: pysetup install [src_dir] | ||||
|    or: pysetup install --help | ||||
| 
 | ||||
| Install a Python distribution from the indexes, source directory, or sdist. | ||||
| 
 | ||||
| positional arguments: | ||||
|    archive  path to source distribution (zip, tar.gz) | ||||
|    dist     distribution name to install from the indexes | ||||
|    scr_dir  path to source directory | ||||
| 
 | ||||
| """ | ||||
| 
 | ||||
| metadata_usage = """\ | ||||
| Usage: pysetup metadata [dist] [-f field ...] | ||||
|    or: pysetup metadata [dist] [--all] | ||||
|    or: pysetup metadata --help | ||||
| 
 | ||||
| Print metadata for the distribution. | ||||
| 
 | ||||
| positional arguments: | ||||
|    dist  installed distribution name | ||||
| 
 | ||||
| optional arguments: | ||||
|    -f     metadata field to print | ||||
|    --all  print all metadata fields | ||||
| """ | ||||
| 
 | ||||
| remove_usage = """\ | ||||
| Usage: pysetup remove dist [-y] | ||||
|    or: pysetup remove --help | ||||
| 
 | ||||
| Uninstall a Python distribution. | ||||
| 
 | ||||
| positional arguments: | ||||
|    dist  installed distribution name | ||||
| 
 | ||||
| optional arguments: | ||||
|    -y  auto confirm package removal | ||||
| """ | ||||
| 
 | ||||
| run_usage = """\ | ||||
| Usage: pysetup run [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...] | ||||
|    or: pysetup run --help | ||||
|    or: pysetup run --list-commands | ||||
|    or: pysetup run cmd --help | ||||
| """ | ||||
| 
 | ||||
| list_usage = """\ | ||||
| Usage: pysetup list dist [dist ...] | ||||
|    or: pysetup list --help | ||||
|    or: pysetup list --all | ||||
| 
 | ||||
| Print name, version and location for the matching installed distributions. | ||||
| 
 | ||||
| positional arguments: | ||||
|    dist  installed distribution name | ||||
| 
 | ||||
| optional arguments: | ||||
|    --all  list all installed distributions | ||||
| """ | ||||
| 
 | ||||
| search_usage = """\ | ||||
| Usage: pysetup search [project] [--simple [url]] [--xmlrpc [url] [--fieldname value ...] --operator or|and] | ||||
|    or: pysetup search --help | ||||
| 
 | ||||
| Search the indexes for the matching projects. | ||||
| 
 | ||||
| positional arguments: | ||||
|     project     the project pattern to search for | ||||
| 
 | ||||
| optional arguments: | ||||
|     --xmlrpc [url]      wether to use the xmlrpc index or not. If an url is | ||||
|                         specified, it will be used rather than the default one. | ||||
| 
 | ||||
|     --simple [url]      wether to use the simple index or not. If an url is | ||||
|                         specified, it will be used rather than the default one. | ||||
| 
 | ||||
|     --fieldname value   Make a search on this field. Can only be used if | ||||
|                         --xmlrpc has been selected or is the default index. | ||||
| 
 | ||||
|     --operator or|and   Defines what is the operator to use when doing xmlrpc | ||||
|                         searchs with multiple fieldnames. Can only be used if | ||||
|                         --xmlrpc has been selected or is the default index. | ||||
| """ | ||||
| 
 | ||||
| global_options = [ | ||||
|     # The fourth entry for verbose means that it can be repeated. | ||||
|     ('verbose', 'v', "run verbosely (default)", True), | ||||
|     ('quiet', 'q', "run quietly (turns verbosity off)"), | ||||
|     ('dry-run', 'n', "don't actually do anything"), | ||||
|     ('help', 'h', "show detailed help message"), | ||||
|     ('no-user-cfg', None, 'ignore pydistutils.cfg in your home directory'), | ||||
|     ('version', None, 'Display the version'), | ||||
| ] | ||||
| 
 | ||||
| negative_opt = {'quiet': 'verbose'} | ||||
| 
 | ||||
| display_options = [ | ||||
|     ('help-commands', None, "list all available commands"), | ||||
| ] | ||||
| 
 | ||||
| display_option_names = [x[0].replace('-', '_') for x in display_options] | ||||
| 
 | ||||
| 
 | ||||
| def _parse_args(args, options, long_options): | ||||
|     """Transform sys.argv input into a dict. | ||||
| 
 | ||||
|     :param args: the args to parse (i.e sys.argv) | ||||
|     :param options: the list of options to pass to getopt | ||||
|     :param long_options: the list of string with the names of the long options | ||||
|                          to be passed to getopt. | ||||
| 
 | ||||
|     The function returns a dict with options/long_options as keys and matching | ||||
|     values as values. | ||||
|     """ | ||||
|     optlist, args = getopt.gnu_getopt(args, options, long_options) | ||||
|     optdict = {} | ||||
|     optdict['args'] = args | ||||
|     for k, v in optlist: | ||||
|         k = k.lstrip('-') | ||||
|         if k not in optdict: | ||||
|             optdict[k] = [] | ||||
|             if v: | ||||
|                 optdict[k].append(v) | ||||
|         else: | ||||
|             optdict[k].append(v) | ||||
|     return optdict | ||||
| 
 | ||||
| 
 | ||||
| class action_help: | ||||
|     """Prints a help message when the standard help flags: -h and --help | ||||
|     are used on the commandline. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, help_msg): | ||||
|         self.help_msg = help_msg | ||||
| 
 | ||||
|     def __call__(self, f): | ||||
|         def wrapper(*args, **kwargs): | ||||
|             f_args = args[1] | ||||
|             if '--help' in f_args or '-h' in f_args: | ||||
|                 print(self.help_msg) | ||||
|                 return | ||||
|             return f(*args, **kwargs) | ||||
|         return wrapper | ||||
| 
 | ||||
| 
 | ||||
| @action_help(create_usage) | ||||
| def _create(distpatcher, args, **kw): | ||||
|     from packaging.create import main | ||||
|     return main() | ||||
| 
 | ||||
| 
 | ||||
| @action_help(graph_usage) | ||||
| def _graph(dispatcher, args, **kw): | ||||
|     name = args[1] | ||||
|     dist = get_distribution(name, use_egg_info=True) | ||||
|     if dist is None: | ||||
|         print('Distribution not found.') | ||||
|     else: | ||||
|         dists = get_distributions(use_egg_info=True) | ||||
|         graph = generate_graph(dists) | ||||
|         print(graph.repr_node(dist)) | ||||
| 
 | ||||
| 
 | ||||
| @action_help(install_usage) | ||||
| def _install(dispatcher, args, **kw): | ||||
|     # first check if we are in a source directory | ||||
|     if len(args) < 2: | ||||
|         # are we inside a project dir? | ||||
|         listing = os.listdir(os.getcwd()) | ||||
|         if 'setup.py' in listing or 'setup.cfg' in listing: | ||||
|             args.insert(1, os.getcwd()) | ||||
|         else: | ||||
|             logger.warning('no project to install') | ||||
|             return | ||||
| 
 | ||||
|     # installing from a source dir or archive file? | ||||
|     if os.path.isdir(args[1]) or _is_archive_file(args[1]): | ||||
|         install_local_project(args[1]) | ||||
|     else: | ||||
|         # download from PyPI | ||||
|         install(args[1]) | ||||
| 
 | ||||
| 
 | ||||
| @action_help(metadata_usage) | ||||
| def _metadata(dispatcher, args, **kw): | ||||
|     opts = _parse_args(args[1:], 'f:', ['all']) | ||||
|     if opts['args']: | ||||
|         name = opts['args'][0] | ||||
|         dist = get_distribution(name, use_egg_info=True) | ||||
|         if dist is None: | ||||
|             logger.warning('%s not installed', name) | ||||
|             return | ||||
|     else: | ||||
|         logger.info('searching local dir for metadata') | ||||
|         dist = Distribution() | ||||
|         dist.parse_config_files() | ||||
| 
 | ||||
|     metadata = dist.metadata | ||||
| 
 | ||||
|     if 'all' in opts: | ||||
|         keys = metadata.keys() | ||||
|     else: | ||||
|         if 'f' in opts: | ||||
|             keys = (k for k in opts['f'] if k in metadata) | ||||
|         else: | ||||
|             keys = () | ||||
| 
 | ||||
|     for key in keys: | ||||
|         if key in metadata: | ||||
|             print(metadata._convert_name(key) + ':') | ||||
|             value = metadata[key] | ||||
|             if isinstance(value, list): | ||||
|                 for v in value: | ||||
|                     print('    ' + v) | ||||
|             else: | ||||
|                 print('    ' + value.replace('\n', '\n    ')) | ||||
| 
 | ||||
| 
 | ||||
| @action_help(remove_usage) | ||||
| def _remove(distpatcher, args, **kw): | ||||
|     opts = _parse_args(args[1:], 'y', []) | ||||
|     if 'y' in opts: | ||||
|         auto_confirm = True | ||||
|     else: | ||||
|         auto_confirm = False | ||||
| 
 | ||||
|     for dist in set(opts['args']): | ||||
|         try: | ||||
|             remove(dist, auto_confirm=auto_confirm) | ||||
|         except PackagingError: | ||||
|             logger.warning('%s not installed', dist) | ||||
| 
 | ||||
| 
 | ||||
| @action_help(run_usage) | ||||
| def _run(dispatcher, args, **kw): | ||||
|     parser = dispatcher.parser | ||||
|     args = args[1:] | ||||
| 
 | ||||
|     commands = STANDARD_COMMANDS  # + extra commands | ||||
| 
 | ||||
|     if args == ['--list-commands']: | ||||
|         print('List of available commands:') | ||||
|         cmds = sorted(commands) | ||||
| 
 | ||||
|         for cmd in cmds: | ||||
|             cls = dispatcher.cmdclass.get(cmd) or get_command_class(cmd) | ||||
|             desc = getattr(cls, 'description', | ||||
|                             '(no description available)') | ||||
|             print('  %s: %s' % (cmd, desc)) | ||||
|         return | ||||
| 
 | ||||
|     while args: | ||||
|         args = dispatcher._parse_command_opts(parser, args) | ||||
|         if args is None: | ||||
|             return | ||||
| 
 | ||||
|     # create the Distribution class | ||||
|     # need to feed setup.cfg here ! | ||||
|     dist = Distribution() | ||||
| 
 | ||||
|     # Find and parse the config file(s): they will override options from | ||||
|     # the setup script, but be overridden by the command line. | ||||
| 
 | ||||
|     # XXX still need to be extracted from Distribution | ||||
|     dist.parse_config_files() | ||||
| 
 | ||||
|     try: | ||||
|         for cmd in dispatcher.commands: | ||||
|             dist.run_command(cmd, dispatcher.command_options[cmd]) | ||||
| 
 | ||||
|     except KeyboardInterrupt: | ||||
|         raise SystemExit("interrupted") | ||||
|     except (IOError, os.error, PackagingError, CCompilerError) as msg: | ||||
|         raise SystemExit("error: " + str(msg)) | ||||
| 
 | ||||
|     # XXX this is crappy | ||||
|     return dist | ||||
| 
 | ||||
| 
 | ||||
| @action_help(list_usage) | ||||
| def _list(dispatcher, args, **kw): | ||||
|     opts = _parse_args(args[1:], '', ['all']) | ||||
|     dists = get_distributions(use_egg_info=True) | ||||
|     if 'all' in opts: | ||||
|         results = dists | ||||
|     else: | ||||
|         results = [d for d in dists if d.name.lower() in opts['args']] | ||||
| 
 | ||||
|     for dist in results: | ||||
|         print('%s %s at %s' % (dist.name, dist.metadata['version'], dist.path)) | ||||
| 
 | ||||
| 
 | ||||
| @action_help(search_usage) | ||||
| def _search(dispatcher, args, **kw): | ||||
|     """The search action. | ||||
| 
 | ||||
|     It is able to search for a specific index (specified with --index), using | ||||
|     the simple or xmlrpc index types (with --type xmlrpc / --type simple) | ||||
|     """ | ||||
|     opts = _parse_args(args[1:], '', ['simple', 'xmlrpc']) | ||||
|     # 1. what kind of index is requested ? (xmlrpc / simple) | ||||
| 
 | ||||
| 
 | ||||
| actions = [ | ||||
|     ('run', 'Run one or several commands', _run), | ||||
|     ('metadata', 'Display the metadata of a project', _metadata), | ||||
|     ('install', 'Install a project', _install), | ||||
|     ('remove', 'Remove a project', _remove), | ||||
|     ('search', 'Search for a project in the indexes', _search), | ||||
|     ('list', 'Search for local projects', _list), | ||||
|     ('graph', 'Display a graph', _graph), | ||||
|     ('create', 'Create a Project', _create), | ||||
| ] | ||||
| 
 | ||||
| 
 | ||||
| class Dispatcher: | ||||
|     """Reads the command-line options | ||||
|     """ | ||||
|     def __init__(self, args=None): | ||||
|         self.verbose = 1 | ||||
|         self.dry_run = False | ||||
|         self.help = False | ||||
|         self.script_name = 'pysetup' | ||||
|         self.cmdclass = {} | ||||
|         self.commands = [] | ||||
|         self.command_options = {} | ||||
| 
 | ||||
|         for attr in display_option_names: | ||||
|             setattr(self, attr, False) | ||||
| 
 | ||||
|         self.parser = FancyGetopt(global_options + display_options) | ||||
|         self.parser.set_negative_aliases(negative_opt) | ||||
|         # FIXME this parses everything, including command options (e.g. "run | ||||
|         # build -i" errors with "option -i not recognized") | ||||
|         args = self.parser.getopt(args=args, object=self) | ||||
| 
 | ||||
|         # if first arg is "run", we have some commands | ||||
|         if len(args) == 0: | ||||
|             self.action = None | ||||
|         else: | ||||
|             self.action = args[0] | ||||
| 
 | ||||
|         allowed = [action[0] for action in actions] + [None] | ||||
|         if self.action not in allowed: | ||||
|             msg = 'Unrecognized action "%s"' % self.action | ||||
|             raise PackagingArgError(msg) | ||||
| 
 | ||||
|         # setting up the logging level from the command-line options | ||||
|         # -q gets warning, error and critical | ||||
|         if self.verbose == 0: | ||||
|             level = logging.WARNING | ||||
|         # default level or -v gets info too | ||||
|         # XXX there's a bug somewhere: the help text says that -v is default | ||||
|         # (and verbose is set to 1 above), but when the user explicitly gives | ||||
|         # -v on the command line, self.verbose is incremented to 2!  Here we | ||||
|         # compensate for that (I tested manually).  On a related note, I think | ||||
|         # it's a good thing to use -q/nothing/-v/-vv on the command line | ||||
|         # instead of logging constants; it will be easy to add support for | ||||
|         # logging configuration in setup.cfg for advanced users. --merwok | ||||
|         elif self.verbose in (1, 2): | ||||
|             level = logging.INFO | ||||
|         else:  # -vv and more for debug | ||||
|             level = logging.DEBUG | ||||
| 
 | ||||
|         # for display options we return immediately | ||||
|         option_order = self.parser.get_option_order() | ||||
| 
 | ||||
|         self.args = args | ||||
| 
 | ||||
|         if self.help or self.action is None: | ||||
|             self._show_help(self.parser, display_options_=False) | ||||
| 
 | ||||
|     def _parse_command_opts(self, parser, args): | ||||
|         # Pull the current command from the head of the command line | ||||
|         command = args[0] | ||||
|         if not command_re.match(command): | ||||
|             raise SystemExit("invalid command name %r" % (command,)) | ||||
|         self.commands.append(command) | ||||
| 
 | ||||
|         # Dig up the command class that implements this command, so we | ||||
|         # 1) know that it's a valid command, and 2) know which options | ||||
|         # it takes. | ||||
|         try: | ||||
|             cmd_class = get_command_class(command) | ||||
|         except PackagingModuleError as msg: | ||||
|             raise PackagingArgError(msg) | ||||
| 
 | ||||
|         # XXX We want to push this in packaging.command | ||||
|         # | ||||
|         # Require that the command class be derived from Command -- want | ||||
|         # to be sure that the basic "command" interface is implemented. | ||||
|         for meth in ('initialize_options', 'finalize_options', 'run'): | ||||
|             if hasattr(cmd_class, meth): | ||||
|                 continue | ||||
|             raise PackagingClassError( | ||||
|                 'command %r must implement %r' % (cmd_class, meth)) | ||||
| 
 | ||||
|         # Also make sure that the command object provides a list of its | ||||
|         # known options. | ||||
|         if not (hasattr(cmd_class, 'user_options') and | ||||
|                 isinstance(cmd_class.user_options, list)): | ||||
|             raise PackagingClassError( | ||||
|                 "command class %s must provide " | ||||
|                 "'user_options' attribute (a list of tuples)" % cmd_class) | ||||
| 
 | ||||
|         # If the command class has a list of negative alias options, | ||||
|         # merge it in with the global negative aliases. | ||||
|         _negative_opt = negative_opt.copy() | ||||
| 
 | ||||
|         if hasattr(cmd_class, 'negative_opt'): | ||||
|             _negative_opt.update(cmd_class.negative_opt) | ||||
| 
 | ||||
|         # Check for help_options in command class.  They have a different | ||||
|         # format (tuple of four) so we need to preprocess them here. | ||||
|         if (hasattr(cmd_class, 'help_options') and | ||||
|             isinstance(cmd_class.help_options, list)): | ||||
|             help_options = cmd_class.help_options[:] | ||||
|         else: | ||||
|             help_options = [] | ||||
| 
 | ||||
|         # All commands support the global options too, just by adding | ||||
|         # in 'global_options'. | ||||
|         parser.set_option_table(global_options + | ||||
|                                 cmd_class.user_options + | ||||
|                                 help_options) | ||||
|         parser.set_negative_aliases(_negative_opt) | ||||
|         args, opts = parser.getopt(args[1:]) | ||||
| 
 | ||||
|         if hasattr(opts, 'help') and opts.help: | ||||
|             self._show_command_help(cmd_class) | ||||
|             return | ||||
| 
 | ||||
|         if (hasattr(cmd_class, 'help_options') and | ||||
|             isinstance(cmd_class.help_options, list)): | ||||
|             help_option_found = False | ||||
|             for help_option, short, desc, func in cmd_class.help_options: | ||||
|                 if hasattr(opts, help_option.replace('-', '_')): | ||||
|                     help_option_found = True | ||||
|                     if hasattr(func, '__call__'): | ||||
|                         func() | ||||
|                     else: | ||||
|                         raise PackagingClassError( | ||||
|                             "invalid help function %r for help option %r: " | ||||
|                             "must be a callable object (function, etc.)" | ||||
|                             % (func, help_option)) | ||||
| 
 | ||||
|             if help_option_found: | ||||
|                 return | ||||
| 
 | ||||
|         # Put the options from the command line into their official | ||||
|         # holding pen, the 'command_options' dictionary. | ||||
|         opt_dict = self.get_option_dict(command) | ||||
|         for name, value in vars(opts).items(): | ||||
|             opt_dict[name] = ("command line", value) | ||||
| 
 | ||||
|         return args | ||||
| 
 | ||||
|     def get_option_dict(self, command): | ||||
|         """Get the option dictionary for a given command.  If that | ||||
|         command's option dictionary hasn't been created yet, then create it | ||||
|         and return the new dictionary; otherwise, return the existing | ||||
|         option dictionary. | ||||
|         """ | ||||
|         d = self.command_options.get(command) | ||||
|         if d is None: | ||||
|             d = self.command_options[command] = {} | ||||
|         return d | ||||
| 
 | ||||
|     def show_help(self): | ||||
|         self._show_help(self.parser) | ||||
| 
 | ||||
|     def print_usage(self, parser): | ||||
|         parser.set_option_table(global_options) | ||||
| 
 | ||||
|         actions_ = ['    %s: %s' % (name, desc) for name, desc, __ in actions] | ||||
|         usage = common_usage % {'actions': '\n'.join(actions_)} | ||||
| 
 | ||||
|         parser.print_help(usage + "\nGlobal options:") | ||||
| 
 | ||||
|     def _show_help(self, parser, global_options_=True, display_options_=True, | ||||
|                    commands=[]): | ||||
|         # late import because of mutual dependence between these modules | ||||
|         from packaging.command.cmd import Command | ||||
| 
 | ||||
|         print('Usage: pysetup [options] action [action_options]') | ||||
|         print('') | ||||
|         if global_options_: | ||||
|             self.print_usage(self.parser) | ||||
|             print('') | ||||
| 
 | ||||
|         if display_options_: | ||||
|             parser.set_option_table(display_options) | ||||
|             parser.print_help( | ||||
|                 "Information display options (just display " + | ||||
|                 "information, ignore any commands)") | ||||
|             print('') | ||||
| 
 | ||||
|         for command in commands: | ||||
|             if isinstance(command, type) and issubclass(command, Command): | ||||
|                 cls = command | ||||
|             else: | ||||
|                 cls = get_command_class(command) | ||||
|             if (hasattr(cls, 'help_options') and | ||||
|                 isinstance(cls.help_options, list)): | ||||
|                 parser.set_option_table(cls.user_options + cls.help_options) | ||||
|             else: | ||||
|                 parser.set_option_table(cls.user_options) | ||||
| 
 | ||||
|             parser.print_help("Options for %r command:" % cls.__name__) | ||||
|             print('') | ||||
| 
 | ||||
|     def _show_command_help(self, command): | ||||
|         if isinstance(command, str): | ||||
|             command = get_command_class(command) | ||||
| 
 | ||||
|         name = command.get_command_name() | ||||
| 
 | ||||
|         desc = getattr(command, 'description', '(no description available)') | ||||
|         print('Description: %s' % desc) | ||||
|         print('') | ||||
| 
 | ||||
|         if (hasattr(command, 'help_options') and | ||||
|             isinstance(command.help_options, list)): | ||||
|             self.parser.set_option_table(command.user_options + | ||||
|                                          command.help_options) | ||||
|         else: | ||||
|             self.parser.set_option_table(command.user_options) | ||||
| 
 | ||||
|         self.parser.print_help("Options:") | ||||
|         print('') | ||||
| 
 | ||||
|     def _get_command_groups(self): | ||||
|         """Helper function to retrieve all the command class names divided | ||||
|         into standard commands (listed in | ||||
|         packaging.command.STANDARD_COMMANDS) and extra commands (given in | ||||
|         self.cmdclass and not standard commands). | ||||
|         """ | ||||
|         extra_commands = [cmd for cmd in self.cmdclass | ||||
|                           if cmd not in STANDARD_COMMANDS] | ||||
|         return STANDARD_COMMANDS, extra_commands | ||||
| 
 | ||||
|     def print_commands(self): | ||||
|         """Print out a help message listing all available commands with a | ||||
|         description of each.  The list is divided into standard commands | ||||
|         (listed in packaging.command.STANDARD_COMMANDS) and extra commands | ||||
|         (given in self.cmdclass and not standard commands).  The | ||||
|         descriptions come from the command class attribute | ||||
|         'description'. | ||||
|         """ | ||||
|         std_commands, extra_commands = self._get_command_groups() | ||||
|         max_length = max(len(command) | ||||
|                          for commands in (std_commands, extra_commands) | ||||
|                          for command in commands) | ||||
| 
 | ||||
|         self.print_command_list(std_commands, "Standard commands", max_length) | ||||
|         if extra_commands: | ||||
|             print() | ||||
|             self.print_command_list(extra_commands, "Extra commands", | ||||
|                                     max_length) | ||||
| 
 | ||||
|     def print_command_list(self, commands, header, max_length): | ||||
|         """Print a subset of the list of all commands -- used by | ||||
|         'print_commands()'. | ||||
|         """ | ||||
|         print(header + ":") | ||||
| 
 | ||||
|         for cmd in commands: | ||||
|             cls = self.cmdclass.get(cmd) or get_command_class(cmd) | ||||
|             description = getattr(cls, 'description', | ||||
|                                   '(no description available)') | ||||
| 
 | ||||
|             print("  %-*s  %s" % (max_length, cmd, description)) | ||||
| 
 | ||||
|     def __call__(self): | ||||
|         if self.action is None: | ||||
|             return | ||||
|         for action, desc, func in actions: | ||||
|             if action == self.action: | ||||
|                 return func(self, self.args) | ||||
|         return -1 | ||||
| 
 | ||||
| 
 | ||||
| def main(args=None): | ||||
|     dispatcher = Dispatcher(args) | ||||
|     if dispatcher.action is None: | ||||
|         return | ||||
| 
 | ||||
|     return dispatcher() | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     sys.exit(main()) | ||||
							
								
								
									
										44
									
								
								Lib/packaging/tests/LONG_DESC.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								Lib/packaging/tests/LONG_DESC.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,44 @@ | |||
| CLVault | ||||
| ======= | ||||
| 
 | ||||
| CLVault uses Keyring to provide a command-line utility to safely store | ||||
| and retrieve passwords. | ||||
| 
 | ||||
| Install it using pip or the setup.py script:: | ||||
| 
 | ||||
|     $ python setup.py install | ||||
| 
 | ||||
|     $ pip install clvault | ||||
| 
 | ||||
| Once it's installed, you will have three scripts installed in your | ||||
| Python scripts folder, you can use to list, store and retrieve passwords:: | ||||
| 
 | ||||
|     $ clvault-set blog | ||||
|     Set your password: | ||||
|     Set the associated username (can be blank): tarek | ||||
|     Set a description (can be blank): My blog password | ||||
|     Password set. | ||||
| 
 | ||||
|     $ clvault-get blog | ||||
|     The username is "tarek" | ||||
|     The password has been copied in your clipboard | ||||
| 
 | ||||
|     $ clvault-list | ||||
|     Registered services: | ||||
|     blog    My blog password | ||||
| 
 | ||||
| 
 | ||||
| *clvault-set* takes a service name then prompt you for a password, and some | ||||
| optional information about your service. The password is safely stored in | ||||
| a keyring while the description is saved in a ``.clvault`` file in your | ||||
| home directory. This file is created automatically the first time the command | ||||
| is used. | ||||
| 
 | ||||
| *clvault-get* copies the password for a given service in your clipboard, and | ||||
| displays the associated user if any. | ||||
| 
 | ||||
| *clvault-list* lists all registered services, with their description when | ||||
| given. | ||||
| 
 | ||||
| 
 | ||||
| Project page: http://bitbucket.org/tarek/clvault | ||||
							
								
								
									
										57
									
								
								Lib/packaging/tests/PKG-INFO
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								Lib/packaging/tests/PKG-INFO
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,57 @@ | |||
| Metadata-Version: 1.2 | ||||
| Name: CLVault | ||||
| Version: 0.5 | ||||
| Summary: Command-Line utility to store and retrieve passwords | ||||
| Home-page: http://bitbucket.org/tarek/clvault | ||||
| Author: Tarek Ziade | ||||
| Author-email: tarek@ziade.org | ||||
| License: PSF | ||||
| Keywords: keyring,password,crypt | ||||
| Requires-Dist: foo; sys.platform == 'okook' | ||||
| Requires-Dist: bar; sys.platform == '%s' | ||||
| Platform: UNKNOWN | ||||
| Description: CLVault | ||||
|        |======= | ||||
|        | | ||||
|        |CLVault uses Keyring to provide a command-line utility to safely store | ||||
|        |and retrieve passwords. | ||||
|        | | ||||
|        |Install it using pip or the setup.py script:: | ||||
|        | | ||||
|        |    $ python setup.py install | ||||
|        | | ||||
|        |    $ pip install clvault | ||||
|        | | ||||
|        |Once it's installed, you will have three scripts installed in your | ||||
|        |Python scripts folder, you can use to list, store and retrieve passwords:: | ||||
|        | | ||||
|        |    $ clvault-set blog | ||||
|        |    Set your password: | ||||
|        |    Set the associated username (can be blank): tarek | ||||
|        |    Set a description (can be blank): My blog password | ||||
|        |    Password set. | ||||
|        | | ||||
|        |    $ clvault-get blog | ||||
|        |    The username is "tarek" | ||||
|        |    The password has been copied in your clipboard | ||||
|        | | ||||
|        |    $ clvault-list | ||||
|        |    Registered services: | ||||
|        |    blog    My blog password | ||||
|        | | ||||
|        | | ||||
|        |*clvault-set* takes a service name then prompt you for a password, and some | ||||
|        |optional information about your service. The password is safely stored in | ||||
|        |a keyring while the description is saved in a ``.clvault`` file in your | ||||
|        |home directory. This file is created automatically the first time the command | ||||
|        |is used. | ||||
|        | | ||||
|        |*clvault-get* copies the password for a given service in your clipboard, and | ||||
|        |displays the associated user if any. | ||||
|        | | ||||
|        |*clvault-list* lists all registered services, with their description when | ||||
|        |given. | ||||
|        | | ||||
|        | | ||||
|        |Project page: http://bitbucket.org/tarek/clvault | ||||
|        | | ||||
							
								
								
									
										182
									
								
								Lib/packaging/tests/SETUPTOOLS-PKG-INFO
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								Lib/packaging/tests/SETUPTOOLS-PKG-INFO
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,182 @@ | |||
| Metadata-Version: 1.0 | ||||
| Name: setuptools | ||||
| Version: 0.6c9 | ||||
| Summary: Download, build, install, upgrade, and uninstall Python packages -- easily! | ||||
| Home-page: http://pypi.python.org/pypi/setuptools | ||||
| Author: Phillip J. Eby | ||||
| Author-email: distutils-sig@python.org | ||||
| License: PSF or ZPL | ||||
| Description: =============================== | ||||
|         Installing and Using Setuptools | ||||
|         =============================== | ||||
| 
 | ||||
|         .. contents:: **Table of Contents** | ||||
| 
 | ||||
| 
 | ||||
|         ------------------------- | ||||
|         Installation Instructions | ||||
|         ------------------------- | ||||
| 
 | ||||
|         Windows | ||||
|         ======= | ||||
| 
 | ||||
|         Install setuptools using the provided ``.exe`` installer.  If you've previously | ||||
|         installed older versions of setuptools, please delete all ``setuptools*.egg`` | ||||
|         and ``setuptools.pth`` files from your system's ``site-packages`` directory | ||||
|         (and any other ``sys.path`` directories) FIRST. | ||||
| 
 | ||||
|         If you are upgrading a previous version of setuptools that was installed using | ||||
|         an ``.exe`` installer, please be sure to also *uninstall that older version* | ||||
|         via your system's "Add/Remove Programs" feature, BEFORE installing the newer | ||||
|         version. | ||||
| 
 | ||||
|         Once installation is complete, you will find an ``easy_install.exe`` program in | ||||
|         your Python ``Scripts`` subdirectory.  Be sure to add this directory to your | ||||
|         ``PATH`` environment variable, if you haven't already done so. | ||||
| 
 | ||||
| 
 | ||||
|         RPM-Based Systems | ||||
|         ================= | ||||
| 
 | ||||
|         Install setuptools using the provided source RPM.  The included ``.spec`` file | ||||
|         assumes you are installing using the default ``python`` executable, and is not | ||||
|         specific to a particular Python version.  The ``easy_install`` executable will | ||||
|         be installed to a system ``bin`` directory such as ``/usr/bin``. | ||||
| 
 | ||||
|         If you wish to install to a location other than the default Python | ||||
|         installation's default ``site-packages`` directory (and ``$prefix/bin`` for | ||||
|         scripts), please use the ``.egg``-based installation approach described in the | ||||
|         following section. | ||||
| 
 | ||||
| 
 | ||||
|         Cygwin, Mac OS X, Linux, Other | ||||
|         ============================== | ||||
| 
 | ||||
|         1. Download the appropriate egg for your version of Python (e.g. | ||||
|         ``setuptools-0.6c9-py2.4.egg``).  Do NOT rename it. | ||||
| 
 | ||||
|         2. Run it as if it were a shell script, e.g. ``sh setuptools-0.6c9-py2.4.egg``. | ||||
|         Setuptools will install itself using the matching version of Python (e.g. | ||||
|         ``python2.4``), and will place the ``easy_install`` executable in the | ||||
|         default location for installing Python scripts (as determined by the | ||||
|         standard distutils configuration files, or by the Python installation). | ||||
| 
 | ||||
|         If you want to install setuptools to somewhere other than ``site-packages`` or | ||||
|         your default distutils installation locations for libraries and scripts, you | ||||
|         may include EasyInstall command-line options such as ``--prefix``, | ||||
|         ``--install-dir``, and so on, following the ``.egg`` filename on the same | ||||
|         command line.  For example:: | ||||
| 
 | ||||
|         sh setuptools-0.6c9-py2.4.egg --prefix=~ | ||||
| 
 | ||||
|         You can use ``--help`` to get a full options list, but we recommend consulting | ||||
|         the `EasyInstall manual`_ for detailed instructions, especially `the section | ||||
|         on custom installation locations`_. | ||||
| 
 | ||||
|         .. _EasyInstall manual: http://peak.telecommunity.com/DevCenter/EasyInstall | ||||
|         .. _the section on custom installation locations: http://peak.telecommunity.com/DevCenter/EasyInstall#custom-installation-locations | ||||
| 
 | ||||
| 
 | ||||
|         Cygwin Note | ||||
|         ----------- | ||||
| 
 | ||||
|         If you are trying to install setuptools for the **Windows** version of Python | ||||
|         (as opposed to the Cygwin version that lives in ``/usr/bin``), you must make | ||||
|         sure that an appropriate executable (``python2.3``, ``python2.4``, or | ||||
|         ``python2.5``) is on your **Cygwin** ``PATH`` when invoking the egg.  For | ||||
|         example, doing the following at a Cygwin bash prompt will install setuptools | ||||
|         for the **Windows** Python found at ``C:\\Python24``:: | ||||
| 
 | ||||
|         ln -s /cygdrive/c/Python24/python.exe python2.4 | ||||
|         PATH=.:$PATH sh setuptools-0.6c9-py2.4.egg | ||||
|         rm python2.4 | ||||
| 
 | ||||
| 
 | ||||
|         Downloads | ||||
|         ========= | ||||
| 
 | ||||
|         All setuptools downloads can be found at `the project's home page in the Python | ||||
|         Package Index`_.  Scroll to the very bottom of the page to find the links. | ||||
| 
 | ||||
|         .. _the project's home page in the Python Package Index: http://pypi.python.org/pypi/setuptools | ||||
| 
 | ||||
|         In addition to the PyPI downloads, the development version of ``setuptools`` | ||||
|         is available from the `Python SVN sandbox`_, and in-development versions of the | ||||
|         `0.6 branch`_ are available as well. | ||||
| 
 | ||||
|         .. _0.6 branch: http://svn.python.org/projects/sandbox/branches/setuptools-0.6/#egg=setuptools-dev06 | ||||
| 
 | ||||
|         .. _Python SVN sandbox: http://svn.python.org/projects/sandbox/trunk/setuptools/#egg=setuptools-dev | ||||
| 
 | ||||
|         -------------------------------- | ||||
|         Using Setuptools and EasyInstall | ||||
|         -------------------------------- | ||||
| 
 | ||||
|         Here are some of the available manuals, tutorials, and other resources for | ||||
|         learning about Setuptools, Python Eggs, and EasyInstall: | ||||
| 
 | ||||
|         * `The EasyInstall user's guide and reference manual`_ | ||||
|         * `The setuptools Developer's Guide`_ | ||||
|         * `The pkg_resources API reference`_ | ||||
|         * `Package Compatibility Notes`_ (user-maintained) | ||||
|         * `The Internal Structure of Python Eggs`_ | ||||
| 
 | ||||
|         Questions, comments, and bug reports should be directed to the `distutils-sig | ||||
|         mailing list`_.  If you have written (or know of) any tutorials, documentation, | ||||
|         plug-ins, or other resources for setuptools users, please let us know about | ||||
|         them there, so this reference list can be updated.  If you have working, | ||||
|         *tested* patches to correct problems or add features, you may submit them to | ||||
|         the `setuptools bug tracker`_. | ||||
| 
 | ||||
|         .. _setuptools bug tracker: http://bugs.python.org/setuptools/ | ||||
|         .. _Package Compatibility Notes: http://peak.telecommunity.com/DevCenter/PackageNotes | ||||
|         .. _The Internal Structure of Python Eggs: http://peak.telecommunity.com/DevCenter/EggFormats | ||||
|         .. _The setuptools Developer's Guide: http://peak.telecommunity.com/DevCenter/setuptools | ||||
|         .. _The pkg_resources API reference: http://peak.telecommunity.com/DevCenter/PkgResources | ||||
|         .. _The EasyInstall user's guide and reference manual: http://peak.telecommunity.com/DevCenter/EasyInstall | ||||
|         .. _distutils-sig mailing list: http://mail.python.org/pipermail/distutils-sig/ | ||||
| 
 | ||||
| 
 | ||||
|         ------- | ||||
|         Credits | ||||
|         ------- | ||||
| 
 | ||||
|         * The original design for the ``.egg`` format and the ``pkg_resources`` API was | ||||
|         co-created by Phillip Eby and Bob Ippolito.  Bob also implemented the first | ||||
|         version of ``pkg_resources``, and supplied the OS X operating system version | ||||
|         compatibility algorithm. | ||||
| 
 | ||||
|         * Ian Bicking implemented many early "creature comfort" features of | ||||
|         easy_install, including support for downloading via Sourceforge and | ||||
|         Subversion repositories.  Ian's comments on the Web-SIG about WSGI | ||||
|         application deployment also inspired the concept of "entry points" in eggs, | ||||
|         and he has given talks at PyCon and elsewhere to inform and educate the | ||||
|         community about eggs and setuptools. | ||||
| 
 | ||||
|         * Jim Fulton contributed time and effort to build automated tests of various | ||||
|         aspects of ``easy_install``, and supplied the doctests for the command-line | ||||
|         ``.exe`` wrappers on Windows. | ||||
| 
 | ||||
|         * Phillip J. Eby is the principal author and maintainer of setuptools, and | ||||
|         first proposed the idea of an importable binary distribution format for | ||||
|         Python application plug-ins. | ||||
| 
 | ||||
|         * Significant parts of the implementation of setuptools were funded by the Open | ||||
|         Source Applications Foundation, to provide a plug-in infrastructure for the | ||||
|         Chandler PIM application.  In addition, many OSAF staffers (such as Mike | ||||
|         "Code Bear" Taylor) contributed their time and stress as guinea pigs for the | ||||
|         use of eggs and setuptools, even before eggs were "cool".  (Thanks, guys!) | ||||
| 
 | ||||
| 
 | ||||
| Keywords: CPAN PyPI distutils eggs package management | ||||
| Platform: UNKNOWN | ||||
| Classifier: Development Status :: 3 - Alpha | ||||
| Classifier: Intended Audience :: Developers | ||||
| Classifier: License :: OSI Approved :: Python Software Foundation License | ||||
| Classifier: License :: OSI Approved :: Zope Public License | ||||
| Classifier: Operating System :: OS Independent | ||||
| Classifier: Programming Language :: Python | ||||
| Classifier: Topic :: Software Development :: Libraries :: Python Modules | ||||
| Classifier: Topic :: System :: Archiving :: Packaging | ||||
| Classifier: Topic :: System :: Systems Administration | ||||
| Classifier: Topic :: Utilities | ||||
							
								
								
									
										183
									
								
								Lib/packaging/tests/SETUPTOOLS-PKG-INFO2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								Lib/packaging/tests/SETUPTOOLS-PKG-INFO2
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,183 @@ | |||
| Metadata-Version: 1.1 | ||||
| Name: setuptools | ||||
| Version: 0.6c9 | ||||
| Summary: Download, build, install, upgrade, and uninstall Python packages -- easily! | ||||
| Home-page: http://pypi.python.org/pypi/setuptools | ||||
| Author: Phillip J. Eby | ||||
| Author-email: distutils-sig@python.org | ||||
| License: PSF or ZPL | ||||
| Description: =============================== | ||||
|         Installing and Using Setuptools | ||||
|         =============================== | ||||
| 
 | ||||
|         .. contents:: **Table of Contents** | ||||
| 
 | ||||
| 
 | ||||
|         ------------------------- | ||||
|         Installation Instructions | ||||
|         ------------------------- | ||||
| 
 | ||||
|         Windows | ||||
|         ======= | ||||
| 
 | ||||
|         Install setuptools using the provided ``.exe`` installer.  If you've previously | ||||
|         installed older versions of setuptools, please delete all ``setuptools*.egg`` | ||||
|         and ``setuptools.pth`` files from your system's ``site-packages`` directory | ||||
|         (and any other ``sys.path`` directories) FIRST. | ||||
| 
 | ||||
|         If you are upgrading a previous version of setuptools that was installed using | ||||
|         an ``.exe`` installer, please be sure to also *uninstall that older version* | ||||
|         via your system's "Add/Remove Programs" feature, BEFORE installing the newer | ||||
|         version. | ||||
| 
 | ||||
|         Once installation is complete, you will find an ``easy_install.exe`` program in | ||||
|         your Python ``Scripts`` subdirectory.  Be sure to add this directory to your | ||||
|         ``PATH`` environment variable, if you haven't already done so. | ||||
| 
 | ||||
| 
 | ||||
|         RPM-Based Systems | ||||
|         ================= | ||||
| 
 | ||||
|         Install setuptools using the provided source RPM.  The included ``.spec`` file | ||||
|         assumes you are installing using the default ``python`` executable, and is not | ||||
|         specific to a particular Python version.  The ``easy_install`` executable will | ||||
|         be installed to a system ``bin`` directory such as ``/usr/bin``. | ||||
| 
 | ||||
|         If you wish to install to a location other than the default Python | ||||
|         installation's default ``site-packages`` directory (and ``$prefix/bin`` for | ||||
|         scripts), please use the ``.egg``-based installation approach described in the | ||||
|         following section. | ||||
| 
 | ||||
| 
 | ||||
|         Cygwin, Mac OS X, Linux, Other | ||||
|         ============================== | ||||
| 
 | ||||
|         1. Download the appropriate egg for your version of Python (e.g. | ||||
|         ``setuptools-0.6c9-py2.4.egg``).  Do NOT rename it. | ||||
| 
 | ||||
|         2. Run it as if it were a shell script, e.g. ``sh setuptools-0.6c9-py2.4.egg``. | ||||
|         Setuptools will install itself using the matching version of Python (e.g. | ||||
|         ``python2.4``), and will place the ``easy_install`` executable in the | ||||
|         default location for installing Python scripts (as determined by the | ||||
|         standard distutils configuration files, or by the Python installation). | ||||
| 
 | ||||
|         If you want to install setuptools to somewhere other than ``site-packages`` or | ||||
|         your default distutils installation locations for libraries and scripts, you | ||||
|         may include EasyInstall command-line options such as ``--prefix``, | ||||
|         ``--install-dir``, and so on, following the ``.egg`` filename on the same | ||||
|         command line.  For example:: | ||||
| 
 | ||||
|         sh setuptools-0.6c9-py2.4.egg --prefix=~ | ||||
| 
 | ||||
|         You can use ``--help`` to get a full options list, but we recommend consulting | ||||
|         the `EasyInstall manual`_ for detailed instructions, especially `the section | ||||
|         on custom installation locations`_. | ||||
| 
 | ||||
|         .. _EasyInstall manual: http://peak.telecommunity.com/DevCenter/EasyInstall | ||||
|         .. _the section on custom installation locations: http://peak.telecommunity.com/DevCenter/EasyInstall#custom-installation-locations | ||||
| 
 | ||||
| 
 | ||||
|         Cygwin Note | ||||
|         ----------- | ||||
| 
 | ||||
|         If you are trying to install setuptools for the **Windows** version of Python | ||||
|         (as opposed to the Cygwin version that lives in ``/usr/bin``), you must make | ||||
|         sure that an appropriate executable (``python2.3``, ``python2.4``, or | ||||
|         ``python2.5``) is on your **Cygwin** ``PATH`` when invoking the egg.  For | ||||
|         example, doing the following at a Cygwin bash prompt will install setuptools | ||||
|         for the **Windows** Python found at ``C:\\Python24``:: | ||||
| 
 | ||||
|         ln -s /cygdrive/c/Python24/python.exe python2.4 | ||||
|         PATH=.:$PATH sh setuptools-0.6c9-py2.4.egg | ||||
|         rm python2.4 | ||||
| 
 | ||||
| 
 | ||||
|         Downloads | ||||
|         ========= | ||||
| 
 | ||||
|         All setuptools downloads can be found at `the project's home page in the Python | ||||
|         Package Index`_.  Scroll to the very bottom of the page to find the links. | ||||
| 
 | ||||
|         .. _the project's home page in the Python Package Index: http://pypi.python.org/pypi/setuptools | ||||
| 
 | ||||
|         In addition to the PyPI downloads, the development version of ``setuptools`` | ||||
|         is available from the `Python SVN sandbox`_, and in-development versions of the | ||||
|         `0.6 branch`_ are available as well. | ||||
| 
 | ||||
|         .. _0.6 branch: http://svn.python.org/projects/sandbox/branches/setuptools-0.6/#egg=setuptools-dev06 | ||||
| 
 | ||||
|         .. _Python SVN sandbox: http://svn.python.org/projects/sandbox/trunk/setuptools/#egg=setuptools-dev | ||||
| 
 | ||||
|         -------------------------------- | ||||
|         Using Setuptools and EasyInstall | ||||
|         -------------------------------- | ||||
| 
 | ||||
|         Here are some of the available manuals, tutorials, and other resources for | ||||
|         learning about Setuptools, Python Eggs, and EasyInstall: | ||||
| 
 | ||||
|         * `The EasyInstall user's guide and reference manual`_ | ||||
|         * `The setuptools Developer's Guide`_ | ||||
|         * `The pkg_resources API reference`_ | ||||
|         * `Package Compatibility Notes`_ (user-maintained) | ||||
|         * `The Internal Structure of Python Eggs`_ | ||||
| 
 | ||||
|         Questions, comments, and bug reports should be directed to the `distutils-sig | ||||
|         mailing list`_.  If you have written (or know of) any tutorials, documentation, | ||||
|         plug-ins, or other resources for setuptools users, please let us know about | ||||
|         them there, so this reference list can be updated.  If you have working, | ||||
|         *tested* patches to correct problems or add features, you may submit them to | ||||
|         the `setuptools bug tracker`_. | ||||
| 
 | ||||
|         .. _setuptools bug tracker: http://bugs.python.org/setuptools/ | ||||
|         .. _Package Compatibility Notes: http://peak.telecommunity.com/DevCenter/PackageNotes | ||||
|         .. _The Internal Structure of Python Eggs: http://peak.telecommunity.com/DevCenter/EggFormats | ||||
|         .. _The setuptools Developer's Guide: http://peak.telecommunity.com/DevCenter/setuptools | ||||
|         .. _The pkg_resources API reference: http://peak.telecommunity.com/DevCenter/PkgResources | ||||
|         .. _The EasyInstall user's guide and reference manual: http://peak.telecommunity.com/DevCenter/EasyInstall | ||||
|         .. _distutils-sig mailing list: http://mail.python.org/pipermail/distutils-sig/ | ||||
| 
 | ||||
| 
 | ||||
|         ------- | ||||
|         Credits | ||||
|         ------- | ||||
| 
 | ||||
|         * The original design for the ``.egg`` format and the ``pkg_resources`` API was | ||||
|         co-created by Phillip Eby and Bob Ippolito.  Bob also implemented the first | ||||
|         version of ``pkg_resources``, and supplied the OS X operating system version | ||||
|         compatibility algorithm. | ||||
| 
 | ||||
|         * Ian Bicking implemented many early "creature comfort" features of | ||||
|         easy_install, including support for downloading via Sourceforge and | ||||
|         Subversion repositories.  Ian's comments on the Web-SIG about WSGI | ||||
|         application deployment also inspired the concept of "entry points" in eggs, | ||||
|         and he has given talks at PyCon and elsewhere to inform and educate the | ||||
|         community about eggs and setuptools. | ||||
| 
 | ||||
|         * Jim Fulton contributed time and effort to build automated tests of various | ||||
|         aspects of ``easy_install``, and supplied the doctests for the command-line | ||||
|         ``.exe`` wrappers on Windows. | ||||
| 
 | ||||
|         * Phillip J. Eby is the principal author and maintainer of setuptools, and | ||||
|         first proposed the idea of an importable binary distribution format for | ||||
|         Python application plug-ins. | ||||
| 
 | ||||
|         * Significant parts of the implementation of setuptools were funded by the Open | ||||
|         Source Applications Foundation, to provide a plug-in infrastructure for the | ||||
|         Chandler PIM application.  In addition, many OSAF staffers (such as Mike | ||||
|         "Code Bear" Taylor) contributed their time and stress as guinea pigs for the | ||||
|         use of eggs and setuptools, even before eggs were "cool".  (Thanks, guys!) | ||||
| 
 | ||||
| 
 | ||||
| Keywords: CPAN PyPI distutils eggs package management | ||||
| Platform: UNKNOWN | ||||
| Classifier: Development Status :: 3 - Alpha | ||||
| Classifier: Intended Audience :: Developers | ||||
| Classifier: License :: OSI Approved :: Python Software Foundation License | ||||
| Classifier: License :: OSI Approved :: Zope Public License | ||||
| Classifier: Operating System :: OS Independent | ||||
| Classifier: Programming Language :: Python | ||||
| Classifier: Topic :: Software Development :: Libraries :: Python Modules | ||||
| Classifier: Topic :: System :: Archiving :: Packaging | ||||
| Classifier: Topic :: System :: Systems Administration | ||||
| Classifier: Topic :: Utilities | ||||
| Requires: Foo | ||||
							
								
								
									
										133
									
								
								Lib/packaging/tests/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								Lib/packaging/tests/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,133 @@ | |||
| """Test suite for packaging. | ||||
| 
 | ||||
| This test suite consists of a collection of test modules in the | ||||
| packaging.tests package.  Each test module has a name starting with | ||||
| 'test' and contains a function test_suite().  The function is expected | ||||
| to return an initialized unittest.TestSuite instance. | ||||
| 
 | ||||
| Utility code is included in packaging.tests.support. | ||||
| """ | ||||
| 
 | ||||
| # Put this text back for the backport | ||||
| #Always import unittest from this module, it will be the right version | ||||
| #(standard library unittest for 3.2 and higher, third-party unittest2 | ||||
| #elease for older versions). | ||||
| 
 | ||||
| import os | ||||
| import sys | ||||
| import unittest | ||||
| from test.support import TESTFN | ||||
| 
 | ||||
| # XXX move helpers to support, add tests for them, remove things that | ||||
| # duplicate test.support (or keep them for the backport; needs thinking) | ||||
| 
 | ||||
| here = os.path.dirname(__file__) or os.curdir | ||||
| verbose = 1 | ||||
| 
 | ||||
| def test_suite(): | ||||
|     suite = unittest.TestSuite() | ||||
|     for fn in os.listdir(here): | ||||
|         if fn.startswith("test") and fn.endswith(".py"): | ||||
|             modname = "packaging.tests." + fn[:-3] | ||||
|             __import__(modname) | ||||
|             module = sys.modules[modname] | ||||
|             suite.addTest(module.test_suite()) | ||||
|     return suite | ||||
| 
 | ||||
| 
 | ||||
| class Error(Exception): | ||||
|     """Base class for regression test exceptions.""" | ||||
| 
 | ||||
| 
 | ||||
| class TestFailed(Error): | ||||
|     """Test failed.""" | ||||
| 
 | ||||
| 
 | ||||
| class BasicTestRunner: | ||||
|     def run(self, test): | ||||
|         result = unittest.TestResult() | ||||
|         test(result) | ||||
|         return result | ||||
| 
 | ||||
| 
 | ||||
| def _run_suite(suite, verbose_=1): | ||||
|     """Run tests from a unittest.TestSuite-derived class.""" | ||||
|     global verbose | ||||
|     verbose = verbose_ | ||||
|     if verbose_: | ||||
|         runner = unittest.TextTestRunner(sys.stdout, verbosity=2) | ||||
|     else: | ||||
|         runner = BasicTestRunner() | ||||
| 
 | ||||
|     result = runner.run(suite) | ||||
|     if not result.wasSuccessful(): | ||||
|         if len(result.errors) == 1 and not result.failures: | ||||
|             err = result.errors[0][1] | ||||
|         elif len(result.failures) == 1 and not result.errors: | ||||
|             err = result.failures[0][1] | ||||
|         else: | ||||
|             err = "errors occurred; run in verbose mode for details" | ||||
|         raise TestFailed(err) | ||||
| 
 | ||||
| 
 | ||||
| def run_unittest(classes, verbose_=1): | ||||
|     """Run tests from unittest.TestCase-derived classes. | ||||
| 
 | ||||
|     Originally extracted from stdlib test.test_support and modified to | ||||
|     support unittest2. | ||||
|     """ | ||||
|     valid_types = (unittest.TestSuite, unittest.TestCase) | ||||
|     suite = unittest.TestSuite() | ||||
|     for cls in classes: | ||||
|         if isinstance(cls, str): | ||||
|             if cls in sys.modules: | ||||
|                 suite.addTest(unittest.findTestCases(sys.modules[cls])) | ||||
|             else: | ||||
|                 raise ValueError("str arguments must be keys in sys.modules") | ||||
|         elif isinstance(cls, valid_types): | ||||
|             suite.addTest(cls) | ||||
|         else: | ||||
|             suite.addTest(unittest.makeSuite(cls)) | ||||
|     _run_suite(suite, verbose_) | ||||
| 
 | ||||
| 
 | ||||
| def reap_children(): | ||||
|     """Use this function at the end of test_main() whenever sub-processes | ||||
|     are started.  This will help ensure that no extra children (zombies) | ||||
|     stick around to hog resources and create problems when looking | ||||
|     for refleaks. | ||||
| 
 | ||||
|     Extracted from stdlib test.support. | ||||
|     """ | ||||
| 
 | ||||
|     # Reap all our dead child processes so we don't leave zombies around. | ||||
|     # These hog resources and might be causing some of the buildbots to die. | ||||
|     if hasattr(os, 'waitpid'): | ||||
|         any_process = -1 | ||||
|         while True: | ||||
|             try: | ||||
|                 # This will raise an exception on Windows.  That's ok. | ||||
|                 pid, status = os.waitpid(any_process, os.WNOHANG) | ||||
|                 if pid == 0: | ||||
|                     break | ||||
|             except: | ||||
|                 break | ||||
| 
 | ||||
| 
 | ||||
| def captured_stdout(func, *args, **kw): | ||||
|     import io | ||||
|     orig_stdout = getattr(sys, 'stdout') | ||||
|     setattr(sys, 'stdout', io.StringIO()) | ||||
|     try: | ||||
|         res = func(*args, **kw) | ||||
|         sys.stdout.seek(0) | ||||
|         return res, sys.stdout.read() | ||||
|     finally: | ||||
|         setattr(sys, 'stdout', orig_stdout) | ||||
| 
 | ||||
| 
 | ||||
| def unload(name): | ||||
|     try: | ||||
|         del sys.modules[name] | ||||
|     except KeyError: | ||||
|         pass | ||||
							
								
								
									
										20
									
								
								Lib/packaging/tests/__main__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Lib/packaging/tests/__main__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| """Packaging test suite runner.""" | ||||
| 
 | ||||
| # Ripped from importlib tests, thanks Brett! | ||||
| 
 | ||||
| import os | ||||
| import sys | ||||
| import unittest | ||||
| from test.support import run_unittest, reap_children | ||||
| 
 | ||||
| 
 | ||||
| def test_main(): | ||||
|     start_dir = os.path.dirname(__file__) | ||||
|     top_dir = os.path.dirname(os.path.dirname(start_dir)) | ||||
|     test_loader = unittest.TestLoader() | ||||
|     run_unittest(test_loader.discover(start_dir, top_level_dir=top_dir)) | ||||
|     reap_children() | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     test_main() | ||||
|  | @ -0,0 +1,4 @@ | |||
| Metadata-version: 1.2 | ||||
| Name: babar | ||||
| Version: 0.1 | ||||
| Author: FELD Boris | ||||
|  | @ -0,0 +1,2 @@ | |||
| babar.png,babar.png | ||||
| babar.cfg,babar.cfg | ||||
							
								
								
									
										1
									
								
								Lib/packaging/tests/fake_dists/babar.cfg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								Lib/packaging/tests/fake_dists/babar.cfg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| Config | ||||
							
								
								
									
										0
									
								
								Lib/packaging/tests/fake_dists/babar.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								Lib/packaging/tests/fake_dists/babar.png
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| Metadata-Version: 1.2 | ||||
| Name: bacon | ||||
| Version: 0.1 | ||||
| Provides-Dist: truffles (2.0) | ||||
| Provides-Dist: bacon (0.1) | ||||
| Obsoletes-Dist: truffles (>=0.9,<=1.5) | ||||
|  | @ -0,0 +1,18 @@ | |||
| Metadata-Version: 1.0 | ||||
| Name: banana | ||||
| Version: 0.4 | ||||
| Summary: A yellow fruit | ||||
| Home-page: http://en.wikipedia.org/wiki/Banana | ||||
| Author: Josip Djolonga | ||||
| Author-email: foo@nbar.com | ||||
| License: BSD | ||||
| Description: A fruit | ||||
| Keywords: foo bar | ||||
| Platform: UNKNOWN | ||||
| Classifier: Development Status :: 4 - Beta | ||||
| Classifier: Intended Audience :: Developers | ||||
| Classifier: Intended Audience :: Science/Research | ||||
| Classifier: License :: OSI Approved :: BSD License | ||||
| Classifier: Operating System :: OS Independent | ||||
| Classifier: Programming Language :: Python | ||||
| Classifier: Topic :: Scientific/Engineering :: GIS | ||||
|  | @ -0,0 +1 @@ | |||
| 
 | ||||
|  | @ -0,0 +1,3 @@ | |||
| 
 | ||||
|       # -*- Entry points: -*- | ||||
|        | ||||
|  | @ -0,0 +1 @@ | |||
| 
 | ||||
|  | @ -0,0 +1,6 @@ | |||
| # this should be ignored | ||||
| 
 | ||||
| strawberry >=0.5 | ||||
| 
 | ||||
| [section ignored] | ||||
| foo ==0.5 | ||||
							
								
								
									
										5
									
								
								Lib/packaging/tests/fake_dists/cheese-2.0.2.egg-info
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								Lib/packaging/tests/fake_dists/cheese-2.0.2.egg-info
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | |||
| Metadata-Version: 1.2 | ||||
| Name: cheese | ||||
| Version: 2.0.2 | ||||
| Provides-Dist: truffles (1.0.2) | ||||
| Obsoletes-Dist: truffles (!=1.2,<=2.0) | ||||
|  | @ -0,0 +1,9 @@ | |||
| Metadata-Version: 1.2 | ||||
| Name: choxie | ||||
| Version: 2.0.0.9 | ||||
| Summary: Chocolate with a kick! | ||||
| Requires-Dist: towel-stuff (0.1) | ||||
| Requires-Dist: nut | ||||
| Provides-Dist: truffles (1.0) | ||||
| Obsoletes-Dist: truffles (<=0.8,>=0.5) | ||||
| Obsoletes-Dist: truffles (<=0.9,>=0.6) | ||||
|  | @ -0,0 +1 @@ | |||
| # -*- coding: utf-8 -*- | ||||
|  | @ -0,0 +1,10 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| from towel_stuff import Towel | ||||
| 
 | ||||
| class Chocolate(object): | ||||
|     """A piece of chocolate.""" | ||||
| 
 | ||||
|     def wrap_with_towel(self): | ||||
|         towel = Towel() | ||||
|         towel.wrap(self) | ||||
|         return towel | ||||
|  | @ -0,0 +1,5 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| from choxie.chocolate import Chocolate | ||||
| 
 | ||||
| class Truffle(Chocolate): | ||||
|     """A truffle.""" | ||||
|  | @ -0,0 +1,5 @@ | |||
| Metadata-Version: 1.2 | ||||
| Name: coconuts-aster | ||||
| Version: 10.3 | ||||
| Provides-Dist: strawberry (0.6) | ||||
| Provides-Dist: banana (0.4) | ||||
|  | @ -0,0 +1,5 @@ | |||
| Metadata-Version: 1.2 | ||||
| Name: grammar | ||||
| Version: 1.0a4 | ||||
| Requires-Dist: truffles (>=1.2) | ||||
| Author: Sherlock Holmes | ||||
|  | @ -0,0 +1 @@ | |||
| # -*- coding: utf-8 -*- | ||||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Tarek Ziade
						Tarek Ziade