1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# Copyright 2005-2009,2011 Joe Wreschnig 4# 5# This program is free software; you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation; either version 2 of the License, or 8# (at your option) any later version. 9 10import glob 11import os 12import shutil 13import sys 14import subprocess 15import tarfile 16 17from distutils.core import setup, Command, Distribution 18from distutils import dir_util 19 20 21def get_command_class(name): 22 # Returns the right class for either distutils or setuptools 23 return Distribution({}).get_command_class(name) 24 25 26distutils_clean = get_command_class("clean") 27 28 29class clean(distutils_clean): 30 def run(self): 31 # In addition to what the normal clean run does, remove pyc 32 # and pyo and backup files from the source tree. 33 distutils_clean.run(self) 34 35 def should_remove(filename): 36 if (filename.lower()[-4:] in [".pyc", ".pyo"] or 37 filename.endswith("~") or 38 (filename.startswith("#") and filename.endswith("#"))): 39 return True 40 else: 41 return False 42 for pathname, dirs, files in os.walk(os.path.dirname(__file__)): 43 for filename in filter(should_remove, files): 44 try: 45 os.unlink(os.path.join(pathname, filename)) 46 except EnvironmentError as err: 47 print(str(err)) 48 49 try: 50 os.unlink("MANIFEST") 51 except OSError: 52 pass 53 54 for base in ["coverage", "build", "dist"]: 55 path = os.path.join(os.path.dirname(__file__), base) 56 if os.path.isdir(path): 57 shutil.rmtree(path) 58 59 60distutils_sdist = get_command_class("sdist") 61 62 63class distcheck(distutils_sdist): 64 65 def _check_manifest(self): 66 assert self.get_archive_files() 67 68 # make sure MANIFEST.in includes all tracked files 69 if subprocess.call(["git", "status"], 70 stdout=subprocess.PIPE, 71 stderr=subprocess.PIPE) == 0: 72 # contains the packaged files after run() is finished 73 included_files = self.filelist.files 74 assert included_files 75 76 process = subprocess.Popen( 77 ["git", "ls-tree", "-r", "HEAD", "--name-only"], 78 stdout=subprocess.PIPE, universal_newlines=True) 79 out, err = process.communicate() 80 assert process.returncode == 0 81 82 tracked_files = out.splitlines() 83 for ignore in [".travis.yml", ".gitignore", ".codecov.yml", 84 "azure-pipelines.yml"]: 85 tracked_files.remove(ignore) 86 87 diff = set(tracked_files) - set(included_files) 88 assert not diff, ( 89 "Not all tracked files included in tarball, check MANIFEST.in", 90 diff) 91 92 def _check_dist(self): 93 assert self.get_archive_files() 94 95 distcheck_dir = os.path.join(self.dist_dir, "distcheck") 96 if os.path.exists(distcheck_dir): 97 dir_util.remove_tree(distcheck_dir) 98 self.mkpath(distcheck_dir) 99 100 archive = self.get_archive_files()[0] 101 tfile = tarfile.open(archive, "r:gz") 102 tfile.extractall(distcheck_dir) 103 tfile.close() 104 105 name = self.distribution.get_fullname() 106 extract_dir = os.path.join(distcheck_dir, name) 107 108 old_pwd = os.getcwd() 109 os.chdir(extract_dir) 110 self.spawn([sys.executable, "setup.py", "test"]) 111 self.spawn([sys.executable, "setup.py", "build"]) 112 self.spawn([sys.executable, "setup.py", "build_sphinx"]) 113 self.spawn([sys.executable, "setup.py", "install", 114 "--root", "../prefix", "--record", "../log.txt"]) 115 os.chdir(old_pwd) 116 117 def run(self): 118 distutils_sdist.run(self) 119 self._check_manifest() 120 self._check_dist() 121 122 123class build_sphinx(Command): 124 description = "build sphinx documentation" 125 user_options = [ 126 ("build-dir=", "d", "build directory"), 127 ] 128 129 def initialize_options(self): 130 self.build_dir = None 131 132 def finalize_options(self): 133 self.build_dir = self.build_dir or "build" 134 135 def run(self): 136 docs = "docs" 137 target = os.path.join(self.build_dir, "sphinx") 138 self.spawn(["sphinx-build", "-b", "html", "-n", docs, target]) 139 140 141class test_cmd(Command): 142 description = "run automated tests" 143 user_options = [ 144 ("to-run=", None, "list of tests to run (default all)"), 145 ("exitfirst", "x", "stop after first failing test"), 146 ("no-quality", None, "skip code quality tests"), 147 ] 148 149 def initialize_options(self): 150 self.to_run = [] 151 self.exitfirst = False 152 self.no_quality = False 153 154 def finalize_options(self): 155 if self.to_run: 156 self.to_run = self.to_run.split(",") 157 self.exitfirst = bool(self.exitfirst) 158 self.no_quality = bool(self.no_quality) 159 160 def run(self): 161 import tests 162 163 status = tests.unit(self.to_run, self.exitfirst, self.no_quality) 164 if status != 0: 165 raise SystemExit(status) 166 167 168class quality_cmd(Command): 169 description = "run pyflakes/pep8 tests" 170 user_options = [] 171 172 def initialize_options(self): 173 pass 174 175 def finalize_options(self): 176 pass 177 178 def run(self): 179 import tests 180 181 status = tests.check() 182 if status != 0: 183 raise SystemExit(status) 184 185 186class coverage_cmd(Command): 187 description = "generate test coverage data" 188 user_options = [] 189 190 def initialize_options(self): 191 pass 192 193 def finalize_options(self): 194 pass 195 196 def run(self): 197 try: 198 from coverage import coverage 199 except ImportError: 200 raise SystemExit( 201 "Missing 'coverage' module. See " 202 "https://pypi.python.org/pypi/coverage or try " 203 "`apt-get install python-coverage python3-coverage`") 204 205 for key in list(sys.modules.keys()): 206 if key.startswith('mutagen'): 207 del(sys.modules[key]) 208 209 cov = coverage() 210 cov.start() 211 212 cmd = self.reinitialize_command("test") 213 cmd.ensure_finalized() 214 cmd.run() 215 216 dest = os.path.join(os.getcwd(), "coverage") 217 218 cov.stop() 219 cov.html_report( 220 directory=dest, 221 ignore_errors=True, 222 include=["mutagen/*"], 223 omit=["mutagen/_senf/*"]) 224 225 print("Coverage summary: file://%s/index.html" % dest) 226 227 228if __name__ == "__main__": 229 from mutagen import version 230 231 with open('README.rst') as h: 232 long_description = h.read() 233 234 # convert to a setuptools compatible version string 235 if version[-1] == -1: 236 version_string = ".".join(map(str, version[:-1])) + ".dev0" 237 else: 238 version_string = ".".join(map(str, version)) 239 240 if os.name == "posix": 241 data_files = [('share/man/man1', glob.glob("man/*.1"))] 242 else: 243 data_files = [] 244 245 cmd_classes = { 246 "clean": clean, 247 "test": test_cmd, 248 "quality": quality_cmd, 249 "coverage": coverage_cmd, 250 "distcheck": distcheck, 251 "build_sphinx": build_sphinx, 252 } 253 254 setup(cmdclass=cmd_classes, 255 name="mutagen", 256 version=version_string, 257 url="https://github.com/quodlibet/mutagen", 258 description="read and write audio tags for many formats", 259 author="Michael Urman", 260 author_email="quod-libet-development@groups.google.com", 261 license="GPL-2.0-or-later", 262 classifiers=[ 263 'Operating System :: OS Independent', 264 'Programming Language :: Python :: 2', 265 'Programming Language :: Python :: 2.7', 266 'Programming Language :: Python :: 3', 267 'Programming Language :: Python :: 3.4', 268 'Programming Language :: Python :: 3.5', 269 'Programming Language :: Python :: 3.6', 270 'Programming Language :: Python :: 3.7', 271 'Programming Language :: Python :: Implementation :: CPython', 272 'Programming Language :: Python :: Implementation :: PyPy', 273 ('License :: OSI Approved :: ' 274 'GNU General Public License v2 or later (GPLv2+)'), 275 'Topic :: Multimedia :: Sound/Audio', 276 ], 277 packages=[ 278 "mutagen", 279 "mutagen.id3", 280 "mutagen.mp4", 281 "mutagen.asf", 282 "mutagen.mp3", 283 "mutagen._senf", 284 "mutagen._tools", 285 ], 286 data_files=data_files, 287 scripts=[os.path.join("tools", name) for name in [ 288 "mid3cp", 289 "mid3iconv", 290 "mid3v2", 291 "moggsplit", 292 "mutagen-inspect", 293 "mutagen-pony", 294 ]], 295 long_description=long_description, 296 ) 297