ci.yml 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. name: CI
  2. env:
  3. # add 3.10+ after patching nose (https://github.com/nose-devs/nose/issues/1099)
  4. # or switching to fork of https://github.com/mdmintz/pynose
  5. all-cpython-versions: 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9
  6. main-cpython-versions: 2.7, 3.2, 3.5, 3.9
  7. pypy-versions: pypy-2.7, pypy-3.6, pypy-3.7
  8. cpython-versions: main
  9. test-set: both
  10. on:
  11. push:
  12. pull_request:
  13. workflow_dispatch:
  14. inputs:
  15. cpython-versions:
  16. type: choice
  17. description: CPython versions (main = 2.7, 3.2, 3.5, 3.9)
  18. options:
  19. - all
  20. - main
  21. required: true
  22. default: main
  23. test-set:
  24. type: choice
  25. description: core, download
  26. options:
  27. - both
  28. - core
  29. - download
  30. required: true
  31. default: core
  32. permissions:
  33. contents: read
  34. jobs:
  35. select:
  36. name: Select tests from inputs
  37. runs-on: ubuntu-latest
  38. outputs:
  39. cpython-versions: ${{ steps.run.outputs.cpython-versions }}
  40. test-set: ${{ steps.run.outputs.test-set }}
  41. own-pip-versions: ${{ steps.run.outputs.own-pip-versions }}
  42. steps:
  43. - id: run
  44. run: |
  45. # Make a JSON Array from comma/space-separated string (no extra escaping)
  46. json_list() { \
  47. ret=""; IFS="${IFS},"; set -- $*; \
  48. for a in "$@"; do \
  49. ret=$(printf '%s"%s"' "${ret}${ret:+, }" "$a"); \
  50. done; \
  51. printf '[%s]' "$ret"; }
  52. tests="${{ inputs.test-set || env.test-set }}"
  53. [ $tests = both ] && tests="core download"
  54. printf 'test-set=%s\n' "$(json_list $tests)" >> "$GITHUB_OUTPUT"
  55. versions="${{ inputs.cpython-versions || env.cpython-versions }}"
  56. if [ "$versions" = all ]; then \
  57. versions="${{ env.all-cpython-versions }}"; else \
  58. versions="${{ env.main-cpython-versions }}"; \
  59. fi
  60. printf 'cpython-versions=%s\n' \
  61. "$(json_list ${versions}${versions:+, }${{ env.pypy-versions }})" >> "$GITHUB_OUTPUT"
  62. # versions with a special get-pip.py in a per-version subdirectory
  63. printf 'own-pip-versions=%s\n' \
  64. "$(json_list 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6)" >> "$GITHUB_OUTPUT"
  65. tests:
  66. name: Run tests
  67. needs: select
  68. permissions:
  69. contents: read
  70. packages: write
  71. runs-on: ${{ matrix.os }}
  72. env:
  73. PIP: python -m pip
  74. PIP_DISABLE_PIP_VERSION_CHECK: true
  75. PIP_NO_PYTHON_VERSION_WARNING: true
  76. strategy:
  77. fail-fast: true
  78. matrix:
  79. os: [ubuntu-20.04]
  80. # outside steps, use github.env...., not env....
  81. python-version: ${{ fromJSON(needs.select.outputs.cpython-versions) }}
  82. python-impl: [cpython]
  83. ytdl-test-set: ${{ fromJSON(needs.select.outputs.test-set) }}
  84. run-tests-ext: [sh]
  85. include:
  86. - os: windows-2019
  87. python-version: 3.2
  88. python-impl: cpython
  89. ytdl-test-set: ${{ contains(needs.select.outputs.test-set, 'core') && 'core' || 'nocore' }}
  90. run-tests-ext: bat
  91. - os: windows-2019
  92. python-version: 3.2
  93. python-impl: cpython
  94. ytdl-test-set: ${{ contains(needs.select.outputs.test-set, 'download') && 'download' || 'nodownload' }}
  95. run-tests-ext: bat
  96. # jython
  97. - os: ubuntu-20.04
  98. python-impl: jython
  99. ytdl-test-set: ${{ contains(needs.select.outputs.test-set, 'core') && 'core' || 'nocore' }}
  100. run-tests-ext: sh
  101. - os: ubuntu-20.04
  102. python-impl: jython
  103. ytdl-test-set: ${{ contains(needs.select.outputs.test-set, 'download') && 'download' || 'nodownload' }}
  104. run-tests-ext: sh
  105. steps:
  106. - name: Checkout
  107. uses: actions/checkout@v3
  108. #-------- Python 3 -----
  109. - name: Set up supported Python ${{ matrix.python-version }}
  110. id: setup-python
  111. if: ${{ matrix.python-impl == 'cpython' && matrix.python-version != '2.6' && matrix.python-version != '2.7'}}
  112. # wrap broken actions/setup-python@v4
  113. uses: ytdl-org/setup-python@v1
  114. with:
  115. python-version: ${{ matrix.python-version }}
  116. cache-build: true
  117. allow-build: info
  118. - name: Locate supported Python ${{ matrix.python-version }}
  119. if: ${{ env.pythonLocation }}
  120. shell: bash
  121. run: |
  122. echo "PYTHONHOME=${pythonLocation}" >> "$GITHUB_ENV"
  123. export expected="${{ steps.setup-python.outputs.python-path }}"
  124. dirname() { printf '%s\n' \
  125. 'import os, sys' \
  126. 'print(os.path.dirname(sys.argv[1]))' \
  127. | ${expected} - "$1"; }
  128. expd="$(dirname "$expected")"
  129. export python="$(command -v python)"
  130. [ "$expd" = "$(dirname "$python")" ] || echo "PATH=$expd:${PATH}" >> "$GITHUB_ENV"
  131. [ -x "$python" ] || printf '%s\n' \
  132. 'import os' \
  133. 'exp = os.environ["expected"]' \
  134. 'python = os.environ["python"]' \
  135. 'exps = os.path.split(exp)' \
  136. 'if python and (os.path.dirname(python) == exp[0]):' \
  137. ' exit(0)' \
  138. 'exps[1] = "python" + os.path.splitext(exps[1])[1]' \
  139. 'python = os.path.join(*exps)' \
  140. 'try:' \
  141. ' os.symlink(exp, python)' \
  142. 'except AttributeError:' \
  143. ' os.rename(exp, python)' \
  144. | ${expected} -
  145. printf '%s\n' \
  146. 'import sys' \
  147. 'print(sys.path)' \
  148. | ${expected} -
  149. #-------- Python 2.7 --
  150. - name: Set up Python 2.7
  151. if: ${{ matrix.python-version == '2.7' }}
  152. # install 2.7
  153. shell: bash
  154. run: |
  155. sudo apt-get install -y python2 python-is-python2
  156. echo "PYTHONHOME=/usr" >> "$GITHUB_ENV"
  157. #-------- Python 2.6 --
  158. - name: Set up Python 2.6 environment
  159. if: ${{ matrix.python-version == '2.6' }}
  160. shell: bash
  161. run: |
  162. openssl_name=openssl-1.0.2u
  163. echo "openssl_name=${openssl_name}" >> "$GITHUB_ENV"
  164. openssl_dir=$HOME/.local/opt/$openssl_name
  165. echo "openssl_dir=${openssl_dir}" >> "$GITHUB_ENV"
  166. PYENV_ROOT=$HOME/.local/share/pyenv
  167. echo "PYENV_ROOT=${PYENV_ROOT}" >> "$GITHUB_ENV"
  168. sudo apt-get install -y openssl ca-certificates
  169. - name: Cache Python 2.6
  170. id: cache26
  171. if: ${{ matrix.python-version == '2.6' }}
  172. uses: actions/cache@v3
  173. with:
  174. key: python-2.6.9
  175. path: |
  176. ${{ env.openssl_dir }}
  177. ${{ env.PYENV_ROOT }}
  178. - name: Build and set up Python 2.6
  179. if: ${{ matrix.python-version == '2.6' && ! steps.cache26.outputs.cache-hit }}
  180. # dl and build locally
  181. shell: bash
  182. run: |
  183. # Install build environment
  184. sudo apt-get install -y build-essential llvm libssl-dev tk-dev \
  185. libncursesw5-dev libreadline-dev libsqlite3-dev \
  186. libffi-dev xz-utils zlib1g-dev libbz2-dev liblzma-dev
  187. # Download and install OpenSSL 1.0.2, back in time
  188. openssl_name=${{ env.openssl_name }}
  189. openssl_targz=${openssl_name}.tar.gz
  190. openssl_dir=${{ env.openssl_dir }}
  191. openssl_inc=$openssl_dir/include
  192. openssl_lib=$openssl_dir/lib
  193. openssl_ssl=$openssl_dir/ssl
  194. curl -L "https://www.openssl.org/source/$openssl_targz" -o $openssl_targz
  195. tar -xf $openssl_targz
  196. ( cd $openssl_name; \
  197. ./config --prefix=$openssl_dir --openssldir=${openssl_dir}/ssl \
  198. --libdir=lib -Wl,-rpath=${openssl_dir}/lib shared zlib-dynamic && \
  199. make && \
  200. make install )
  201. rm -rf $openssl_name
  202. rmdir $openssl_ssl/certs && ln -s /etc/ssl/certs $openssl_ssl/certs
  203. # Download PyEnv from its GitHub repository.
  204. export PYENV_ROOT=${{ env.PYENV_ROOT }}
  205. export PATH=$PYENV_ROOT/bin:$PATH
  206. git clone "https://github.com/pyenv/pyenv.git" "$PYENV_ROOT"
  207. # Prevent pyenv build trying (and failing) to update pip
  208. export GET_PIP=get-pip-2.6.py
  209. echo 'import sys; sys.exit(0)' > ${GET_PIP}
  210. GET_PIP=$(realpath $GET_PIP)
  211. # Build and install Python
  212. export CFLAGS="-I$openssl_inc"
  213. export LDFLAGS="-L$openssl_lib"
  214. export LD_LIBRARY_PATH="$openssl_lib"
  215. pyenv install 2.6.9
  216. - name: Locate Python 2.6
  217. if: ${{ matrix.python-version == '2.6' }}
  218. shell: bash
  219. run: |
  220. PYTHONHOME="${{ env.PYENV_ROOT }}/versions/2.6.9"
  221. echo "PYTHONHOME=$PYTHONHOME" >> "$GITHUB_ENV"
  222. echo "PATH=${PYTHONHOME}/bin:$PATH" >> "$GITHUB_ENV"
  223. echo "LD_LIBRARY_PATH=${{ env.openssl_dir }}/lib${LD_LIBRARY_PATH:+:}${LD_LIBRARY_PATH}" >> "$GITHUB_ENV"
  224. #-------- Jython ------
  225. - name: Set up Java 8
  226. if: ${{ matrix.python-impl == 'jython' }}
  227. uses: actions/setup-java@v2
  228. with:
  229. java-version: 8
  230. distribution: 'zulu'
  231. - name: Setup Jython environment
  232. if: ${{ matrix.python-impl == 'jython' }}
  233. shell: bash
  234. run: |
  235. echo "JYTHON_ROOT=${HOME}/jython" >> "$GITHUB_ENV"
  236. echo "PIP=pip" >> "$GITHUB_ENV"
  237. - name: Cache Jython
  238. id: cachejy
  239. if: ${{ matrix.python-impl == 'jython' }}
  240. uses: actions/cache@v3
  241. with:
  242. # 2.7.3 now available, may solve SNI issue
  243. key: jython-2.7.1
  244. path: |
  245. ${{ env.JYTHON_ROOT }}
  246. - name: Install Jython
  247. if: ${{ matrix.python-impl == 'jython' && ! steps.cachejy.outputs.cache-hit }}
  248. shell: bash
  249. run: |
  250. JYTHON_ROOT="${{ env.JYTHON_ROOT }}"
  251. curl -L "https://repo1.maven.org/maven2/org/python/jython-installer/2.7.1/jython-installer-2.7.1.jar" -o jython-installer.jar
  252. java -jar jython-installer.jar -s -d "${JYTHON_ROOT}"
  253. echo "${JYTHON_ROOT}/bin" >> "$GITHUB_PATH"
  254. - name: Set up cached Jython
  255. if: ${{ steps.cachejy.outputs.cache-hit }}
  256. shell: bash
  257. run: |
  258. JYTHON_ROOT="${{ env.JYTHON_ROOT }}"
  259. echo "${JYTHON_ROOT}/bin" >> $GITHUB_PATH
  260. #-------- pip ---------
  261. - name: Set up supported Python ${{ matrix.python-version }} pip
  262. if: ${{ (matrix.python-version != '3.2' && steps.setup-python.outputs.python-path) || matrix.python-version == '2.7' }}
  263. # This step may run in either Linux or Windows
  264. shell: bash
  265. run: |
  266. echo "$PATH"
  267. echo "$PYTHONHOME"
  268. # curl is available on both Windows and Linux, -L follows redirects, -O gets name
  269. python -m ensurepip || python -m pip --version || { \
  270. get_pip="${{ contains(needs.select.outputs.own-pip-versions, matrix.python-version) && format('{0}/', matrix.python-version) || '' }}"; \
  271. curl -L -O "https://bootstrap.pypa.io/pip/${get_pip}get-pip.py"; \
  272. python get-pip.py; }
  273. - name: Set up Python 2.6 pip
  274. if: ${{ matrix.python-version == '2.6' }}
  275. shell: bash
  276. run: |
  277. python -m pip --version || { \
  278. curl -L -O "https://bootstrap.pypa.io/pip/2.6/get-pip.py"; \
  279. curl -L -O "https://files.pythonhosted.org/packages/ac/95/a05b56bb975efa78d3557efa36acaf9cf5d2fd0ee0062060493687432e03/pip-9.0.3-py2.py3-none-any.whl"; \
  280. python get-pip.py --no-setuptools --no-wheel pip-9.0.3-py2.py3-none-any.whl; }
  281. # work-around to invoke pip module on 2.6: https://bugs.python.org/issue2751
  282. echo "PIP=python -m pip.__main__" >> "$GITHUB_ENV"
  283. - name: Set up other Python ${{ matrix.python-version }} pip
  284. if: ${{ matrix.python-version == '3.2' && steps.setup-python.outputs.python-path }}
  285. shell: bash
  286. run: |
  287. python -m pip --version || { \
  288. curl -L -O "https://bootstrap.pypa.io/pip/3.2/get-pip.py"; \
  289. curl -L -O "https://files.pythonhosted.org/packages/b2/d0/cd115fe345dd6f07ec1c780020a7dfe74966fceeb171e0f20d1d4905b0b7/pip-7.1.2-py2.py3-none-any.whl"; \
  290. python get-pip.py --no-setuptools --no-wheel pip-7.1.2-py2.py3-none-any.whl; }
  291. #-------- unittest ----
  292. - name: Upgrade Unittest for Python 2.6
  293. if: ${{ matrix.python-version == '2.6' }}
  294. shell: bash
  295. run: |
  296. # Work around deprecation of support for non-SNI clients at PyPI CDN (see https://status.python.org/incidents/hzmjhqsdjqgb)
  297. $PIP -qq show unittest2 || { \
  298. for u in "65/26/32b8464df2a97e6dd1b656ed26b2c194606c16fe163c695a992b36c11cdf/six-1.13.0-py2.py3-none-any.whl" \
  299. "f2/94/3af39d34be01a24a6e65433d19e107099374224905f1e0cc6bbe1fd22a2f/argparse-1.4.0-py2.py3-none-any.whl" \
  300. "c7/a3/c5da2a44c85bfbb6eebcfc1dde24933f8704441b98fdde6528f4831757a6/linecache2-1.0.0-py2.py3-none-any.whl" \
  301. "17/0a/6ac05a3723017a967193456a2efa0aa9ac4b51456891af1e2353bb9de21e/traceback2-1.4.0-py2.py3-none-any.whl" \
  302. "72/20/7f0f433060a962200b7272b8c12ba90ef5b903e218174301d0abfd523813/unittest2-1.1.0-py2.py3-none-any.whl"; do \
  303. curl -L -O "https://files.pythonhosted.org/packages/${u}"; \
  304. $PIP install ${u##*/}; \
  305. done; }
  306. # make tests use unittest2
  307. for test in ./test/test_*.py ./test/helper.py; do
  308. sed -r -i -e '/^import unittest$/s/test/test2 as unittest/' "$test"
  309. done
  310. #-------- nose --------
  311. - name: Install nose for Python ${{ matrix.python-version }}
  312. if: ${{ (matrix.python-version != '3.2' && steps.setup-python.outputs.python-path) || matrix.python-version == '2.7' }}
  313. shell: bash
  314. run: |
  315. echo "$PATH"
  316. echo "$PYTHONHOME"
  317. $PIP -qq show nose || $PIP install nose
  318. - name: Install nose for other Python 2
  319. if: ${{ matrix.python-impl == 'jython' || matrix.python-version == '2.6' }}
  320. shell: bash
  321. run: |
  322. # Work around deprecation of support for non-SNI clients at PyPI CDN (see https://status.python.org/incidents/hzmjhqsdjqgb)
  323. $PIP -qq show nose || { \
  324. curl -L -O "https://files.pythonhosted.org/packages/99/4f/13fb671119e65c4dce97c60e67d3fd9e6f7f809f2b307e2611f4701205cb/nose-1.3.7-py2-none-any.whl"; \
  325. $PIP install nose-1.3.7-py2-none-any.whl; }
  326. - name: Install nose for other Python 3
  327. if: ${{ matrix.python-version == '3.2' && steps.setup-python.outputs.python-path }}
  328. shell: bash
  329. run: |
  330. $PIP -qq show nose || { \
  331. curl -L -O "https://files.pythonhosted.org/packages/15/d8/dd071918c040f50fa1cf80da16423af51ff8ce4a0f2399b7bf8de45ac3d9/nose-1.3.7-py3-none-any.whl"; \
  332. $PIP install nose-1.3.7-py3-none-any.whl; }
  333. - name: Set up nosetest test
  334. if: ${{ contains(needs.select.outputs.test-set, matrix.ytdl-test-set ) }}
  335. shell: bash
  336. run: |
  337. # define a test to validate the Python version used by nosetests
  338. printf '%s\n' \
  339. 'from __future__ import unicode_literals' \
  340. 'import sys, os, platform' \
  341. 'try:' \
  342. ' import unittest2 as unittest' \
  343. 'except ImportError:' \
  344. ' import unittest' \
  345. 'class TestPython(unittest.TestCase):' \
  346. ' def setUp(self):' \
  347. ' self.ver = os.environ["PYTHON_VER"].split("-")' \
  348. ' def test_python_ver(self):' \
  349. ' self.assertEqual(sys.version[:3], self.ver[-1])' \
  350. ' self.assertTrue(sys.version.startswith(self.ver[-1]))' \
  351. ' self.assertIn(self.ver[0], sys.version.lower())' \
  352. ' def test_python_impl(self):' \
  353. ' self.assertIn(platform.python_implementation().lower(), (os.environ["PYTHON_IMPL"], self.ver[0]))' \
  354. > test/test_python.py
  355. #-------- TESTS -------
  356. - name: Run tests
  357. if: ${{ contains(needs.select.outputs.test-set, matrix.ytdl-test-set ) }}
  358. continue-on-error: ${{ matrix.ytdl-test-set == 'download' || matrix.python-impl == 'jython' }}
  359. env:
  360. YTDL_TEST_SET: ${{ matrix.ytdl-test-set }}
  361. PYTHON_VER: ${{ matrix.python-version }}
  362. PYTHON_IMPL: ${{ matrix.python-impl }}
  363. run: |
  364. ./devscripts/run_tests.${{ matrix.run-tests-ext }}
  365. flake8:
  366. name: Linter
  367. runs-on: ubuntu-latest
  368. steps:
  369. - uses: actions/checkout@v3
  370. - name: Set up Python
  371. uses: actions/setup-python@v4
  372. with:
  373. python-version: 3.9
  374. - name: Install flake8
  375. run: pip install flake8
  376. - name: Run flake8
  377. run: flake8 .