ci.yml 14 KB

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