diff --git a/.github/workflows/test_tox.yml b/.github/workflows/test_tox.yml index 5f13564..acdd90e 100644 --- a/.github/workflows/test_tox.yml +++ b/.github/workflows/test_tox.yml @@ -68,14 +68,13 @@ jobs: posargs: 'PyPy' pytest: false - test_supported_pythons: + test_python_version_glob: uses: ./.github/workflows/tox.yml with: envs: | - linux: pep8 - - linux: py3 - fill: true - fill_platforms: linux,macos + - linux: py3* + - macos: py31* pytest: false test_libraries: diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 3b5a2ae..97ac14f 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -7,21 +7,6 @@ on: description: Array of tox environments to test required: true type: string - fill: - description: Add an extra toxenv to the matrix for each current version of Python supported by the package - required: false - default: false - type: boolean - fill_platforms: - description: Platforms to iterate with fill - required: false - default: '' - type: string - fill_factors: - description: Tox factors to add to toxenvs added with `fill` - required: false - default: '' - type: string libraries: description: Additional packages to install required: false @@ -158,27 +143,18 @@ jobs: version: "0.10.6" enable-cache: false ignore-empty-workdir: "true" - - if: inputs.fill - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ inputs.checkout_ref }} persist-credentials: false - - if: inputs.fill - run: echo $SUPPORTED_PYTHONS_SCRIPT | base64 --decode > supported_pythons.py - env: - SUPPORTED_PYTHONS_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj49My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJjbGljaz09OC4yLjEiLAojICAgICAicGFja2FnaW5nPT0yNS4wIiwKIyAgICAgInJlcXVlc3RzPT0yLjMyLjUiLAojICAgICAidG9tbGk9PTIuNC4wIiwKIyBdCiMgLy8vCmltcG9ydCBvcwppbXBvcnQgd2FybmluZ3MKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCgppbXBvcnQgY2xpY2sKaW1wb3J0IHJlcXVlc3RzCmltcG9ydCB0b21saQpmcm9tIHBhY2thZ2luZy5zcGVjaWZpZXJzIGltcG9ydCBTcGVjaWZpZXJTZXQKZnJvbSBwYWNrYWdpbmcudmVyc2lvbiBpbXBvcnQgVmVyc2lvbgoKCkBjbGljay5jb21tYW5kKCkKQGNsaWNrLm9wdGlvbigiLS1wYWNrYWdlLXNvdXJjZSIsIGRlZmF1bHQ9Tm9uZSkKQGNsaWNrLm9wdGlvbigiLS1mYWN0b3JzIiwgZGVmYXVsdD1Ob25lKQpAY2xpY2sub3B0aW9uKCItLW5vLWVvYXMiLCBpc19mbGFnPVRydWUsIGRlZmF1bHQ9RmFsc2UpCkBjbGljay5vcHRpb24oIi0tcGxhdGZvcm1zIiwgZGVmYXVsdD1Ob25lKQpkZWYgc3VwcG9ydGVkX3B5dGhvbl9lbnZzX2Jsb2NrKAogICAgcGFja2FnZV9zb3VyY2U6IFBhdGggPSBOb25lLAogICAgZmFjdG9yczogbGlzdFtzdHJdID0gTm9uZSwKICAgIG5vX2VvYXM6IGJvb2wgPSBGYWxzZSwKICAgIHBsYXRmb3JtczogbGlzdFtzdHJdID0gTm9uZSwKKToKICAgICIiImVudW1lcmF0ZSB0b3hlbnZzIGZvciBlYWNoIFB5dGhvbiB2ZXJzaW9uIHN1cHBvcnRlZCBieSBwYWNrYWdlIiIiCgogICAgaWYgcGxhdGZvcm1zIGlzIE5vbmU6CiAgICAgICAgcGxhdGZvcm1zID0gWyJsaW51eCJdCiAgICBlbGlmIGlzaW5zdGFuY2UocGxhdGZvcm1zLCBzdHIpOgogICAgICAgIHBsYXRmb3JtcyA9IHBsYXRmb3Jtcy5zcGxpdCgiLCIpCgogICAgdG94ZW52cyA9IHN1cHBvcnRlZF9weXRob25fdG94ZW52cyhwYWNrYWdlX3NvdXJjZSwgZmFjdG9ycywgbm9fZW9hcykKICAgIGVudnNfYmxvY2sgPSAiXFxuIi5qb2luKAogICAgICAgIGYiLSB7cGxhdGZvcm19OiB7dG94ZW52fSIgZm9yIHBsYXRmb3JtIGluIHBsYXRmb3JtcyBmb3IgdG94ZW52IGluIHRveGVudnMKICAgICkKCiAgICBwcmludChlbnZzX2Jsb2NrKQogICAgd2l0aCBvcGVuKG9zLmVudmlyb25bIkdJVEhVQl9PVVRQVVQiXSwgImEiKSBhcyBmOgogICAgICAgIGYud3JpdGUoZiJlbnZzPXtlbnZzX2Jsb2NrfVxuIikKCgpkZWYgc3VwcG9ydGVkX3B5dGhvbl90b3hlbnZzKAogICAgcGFja2FnZV9zb3VyY2U6IFBhdGggPSBOb25lLAogICAgZmFjdG9yczogbGlzdFtzdHJdID0gTm9uZSwKICAgIG5vX2VvYXM6IGJvb2wgPSBGYWxzZSwKKSAtPiBsaXN0W3N0cl06CiAgICBpZiBpc2luc3RhbmNlKGZhY3RvcnMsIHN0cik6CiAgICAgICAgZmFjdG9ycyA9IGZhY3RvcnMuc3BsaXQoIiwiKQoKICAgIHJldHVybiBbCiAgICAgICAgZiJweXtzdHIocHl0aG9uX3ZlcnNpb24pLnJlcGxhY2UoJy4nLCAnJyl9eyctJyArICctJy5qb2luKGZhY3RvcnMpIGlmIGZhY3RvcnMgaXMgbm90IE5vbmUgYW5kIGxlbihmYWN0b3JzKSA+IDAgZWxzZSAnJ30iCiAgICAgICAgZm9yIHB5dGhvbl92ZXJzaW9uIGluIHN1cHBvcnRlZF9weXRob25zKHBhY2thZ2Vfc291cmNlLCBub19lb2FzPW5vX2VvYXMpCiAgICBdCgoKZGVmIHN1cHBvcnRlZF9weXRob25zKAogICAgcGFja2FnZV9zb3VyY2U6IFBhdGggPSBOb25lLAogICAgbm9fZW9hczogYm9vbCA9IEZhbHNlLAopIC0+IGxpc3RbVmVyc2lvbl06CiAgICBjdXJyZW50X3B5dGhvbl92ZXJzaW9ucyA9IGN1cnJlbnRfcHl0aG9ucyhub19lb2FzPW5vX2VvYXMpCgogICAgaWYgbm90IHBhY2thZ2Vfc291cmNlOgogICAgICAgIHN1cHBvcnRlZF92ZXJzaW9ucyA9IGN1cnJlbnRfcHl0aG9uX3ZlcnNpb25zCiAgICBlbHNlOgogICAgICAgIHRyeToKICAgICAgICAgICAgcHlwcm9qZWN0X3RvbWxfZmlsZW5hbWUgPSBQYXRoKHBhY2thZ2Vfc291cmNlKSAvICJweXByb2plY3QudG9tbCIKICAgICAgICAgICAgaWYgcHlwcm9qZWN0X3RvbWxfZmlsZW5hbWUuZXhpc3RzKCk6CiAgICAgICAgICAgICAgICB3aXRoIG9wZW4ocHlwcm9qZWN0X3RvbWxfZmlsZW5hbWUsICJyYiIpIGFzIHB5cHJvamVjdF90b21sX2ZpbGU6CiAgICAgICAgICAgICAgICAgICAgcHlwcm9qZWN0X3RvbWwgPSB0b21saS5sb2FkKHB5cHJvamVjdF90b21sX2ZpbGUpCiAgICAgICAgICAgICAgICBpZiAicHJvamVjdCIgaW4gcHlwcm9qZWN0X3RvbWw6CiAgICAgICAgICAgICAgICAgICAgcHJvamVjdF9tZXRhZGF0YSA9IHB5cHJvamVjdF90b21sWyJwcm9qZWN0Il0KICAgICAgICAgICAgICAgICAgICBpZiAicmVxdWlyZXMtcHl0aG9uIiBpbiBwcm9qZWN0X21ldGFkYXRhOgogICAgICAgICAgICAgICAgICAgICAgICBweXRob25fdmVyc2lvbl9yZXF1aXJlbWVudHMgPSBTcGVjaWZpZXJTZXQoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9qZWN0X21ldGFkYXRhWyJyZXF1aXJlcy1weXRob24iXQogICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICAgICAgcmFpc2UgS2V5RXJyb3IoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYHByb2plY3QucmVxdWlyZXMtcHl0aG9uYCBub3QgZm91bmQgaW4gYHB5cHJvamVjdC50b21sYDsgZW5zdXJlIHlvdXIgcGFja2FnZSBjb25mb3JtcyB0byBQRVA2MjEiCiAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgS2V5RXJyb3IoCiAgICAgICAgICAgICAgICAgICAgICAgICJgcHJvamVjdGAgbm90IGZvdW5kIGluIGBweXByb2plY3QudG9tbGA7IGVuc3VyZSB5b3VyIHBhY2thZ2UgY29uZm9ybXMgdG8gUEVQNjIxIgogICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHJhaXNlIEZpbGVOb3RGb3VuZEVycm9yKAogICAgICAgICAgICAgICAgICAgICJjb3VsZCBub3QgZmluZCBgcHlwcm9qZWN0LnRvbWxgIGluIHRoZSBwcm92aWRlZCBwYWNrYWdlIHNvdXJjZTsgZW5zdXJlIHlvdXIgcGFja2FnZSBjb25mb3JtcyB0byBQRVA2MjEiCiAgICAgICAgICAgICAgICApCgogICAgICAgICAgICBzdXBwb3J0ZWRfdmVyc2lvbnMgPSBbCiAgICAgICAgICAgICAgICBweXRob25fdmVyc2lvbgogICAgICAgICAgICAgICAgZm9yIHB5dGhvbl92ZXJzaW9uIGluIGN1cnJlbnRfcHl0aG9uX3ZlcnNpb25zCiAgICAgICAgICAgICAgICBpZiBweXRob25fdmVyc2lvbiBpbiBweXRob25fdmVyc2lvbl9yZXF1aXJlbWVudHMKICAgICAgICAgICAgXQogICAgICAgIGV4Y2VwdCAoS2V5RXJyb3IsIFR5cGVFcnJvciwgRmlsZU5vdEZvdW5kRXJyb3IpIGFzIGVycm9yOgogICAgICAgICAgICB3YXJuaW5ncy53YXJuKHN0cihlcnJvcikpCiAgICAgICAgICAgIHdhcm5pbmdzLndhcm4oImZhbGxpbmcgYmFjayB0byBjdXJyZW50IFB5dGhvbiB2ZXJzaW9ucy4uLiIpCiAgICAgICAgICAgIHN1cHBvcnRlZF92ZXJzaW9ucyA9IGN1cnJlbnRfcHl0aG9uX3ZlcnNpb25zCgogICAgcmV0dXJuIHN1cHBvcnRlZF92ZXJzaW9ucwoKCmRlZiBjdXJyZW50X3B5dGhvbnMobm9fZW9hczogYm9vbCA9IEZhbHNlKSAtPiBsaXN0W1ZlcnNpb25dOgogICAgdXJsID0gImh0dHBzOi8vZW5kb2ZsaWZlLmRhdGUvYXBpL3YxL3Byb2R1Y3RzL3B5dGhvbiIKICAgIHJlc3BvbnNlID0gcmVxdWVzdHMuZ2V0KCJodHRwczovL2VuZG9mbGlmZS5kYXRlL2FwaS92MS9wcm9kdWN0cy9weXRob24iKQogICAgaWYgcmVzcG9uc2Uuc3RhdHVzX2NvZGUgPT0gMjAwOgogICAgICAgIHJldHVybiBbCiAgICAgICAgICAgIFZlcnNpb24ocHl0aG9uX3ZlcnNpb25bIm5hbWUiXSkKICAgICAgICAgICAgZm9yIHB5dGhvbl92ZXJzaW9uIGluIHJlc3BvbnNlLmpzb24oKVsicmVzdWx0Il1bInJlbGVhc2VzIl0KICAgICAgICAgICAgaWYgbm90IHB5dGhvbl92ZXJzaW9uWyJpc0VvYXMiIGlmIG5vX2VvYXMgZWxzZSAiaXNFb2wiXQogICAgICAgIF0KICAgIGVsc2U6CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcihmInJlcXVlc3QgdG8ge3VybH0gcmV0dXJuZWQgc3RhdHVzIGNvZGUge3Jlc3BvbnNlLnN0YXR1c19jb2RlfSIpCgoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgIHN1cHBvcnRlZF9weXRob25fZW52c19ibG9jaygpCg== - - if: inputs.fill # zizmor: ignore[template-injection] - id: supported-pythons - run: uv run supported_pythons.py --package-source . ${{ inputs.fill_platforms != '' && format('--platforms {0}', inputs.fill_platforms) || '' }} ${{ inputs.fill_factors != '' && format('--factors {0}', inputs.fill_factors) || '' }} - shell: sh - run: echo $TOX_MATRIX_SCRIPT | base64 --decode > tox_matrix.py env: - TOX_MATRIX_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj09My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJjbGljaz09OC4yLjEiLAojICAgICAicHl5YW1sPT02LjAuMiIsCiMgXQojIC8vLwppbXBvcnQganNvbgppbXBvcnQgb3MKaW1wb3J0IHJlCmltcG9ydCB3YXJuaW5ncwoKaW1wb3J0IGNsaWNrCmltcG9ydCB5YW1sCgoKQGNsaWNrLmNvbW1hbmQoKQpAY2xpY2sub3B0aW9uKCItLWVudnMiLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLWxpYnJhcmllcyIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tcG9zYXJncyIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tdG94ZGVwcyIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tdG94YXJncyIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tcHl0ZXN0IiwgZGVmYXVsdD0idHJ1ZSIpCkBjbGljay5vcHRpb24oIi0tcHl0ZXN0LXJlc3VsdHMtc3VtbWFyeSIsIGRlZmF1bHQ9ImZhbHNlIikKQGNsaWNrLm9wdGlvbigiLS1jb3ZlcmFnZSIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tY29uZGEiLCBkZWZhdWx0PSJhdXRvIikKQGNsaWNrLm9wdGlvbigiLS1zZXRlbnYiLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLWRpc3BsYXkiLCBkZWZhdWx0PSJmYWxzZSIpCkBjbGljay5vcHRpb24oIi0tY2FjaGUtcGF0aCIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tY2FjaGUta2V5IiwgZGVmYXVsdD0iIikKQGNsaWNrLm9wdGlvbigiLS1jYWNoZS1yZXN0b3JlLWtleXMiLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLWFydGlmYWN0LXBhdGgiLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLWFydGlmYWN0LWFyY2hpdmUiLCBkZWZhdWx0PSJ0cnVlIikKQGNsaWNrLm9wdGlvbigiLS1hcnRpZmFjdC1pbmNsdWRlLWhpZGRlbi1maWxlcyIsIGRlZmF1bHQ9ImZhbHNlIikKQGNsaWNrLm9wdGlvbigiLS1hcnRpZmFjdC1pZi1uby1maWxlcy1mb3VuZCIsIGRlZmF1bHQ9Indhcm4iKQpAY2xpY2sub3B0aW9uKCItLXJ1bnMtb24iLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLWRlZmF1bHQtcHl0aG9uIiwgZGVmYXVsdD0iIikKQGNsaWNrLm9wdGlvbigiLS10aW1lb3V0LW1pbnV0ZXMiLCBkZWZhdWx0PSIzNjAiKQpkZWYgbG9hZF90b3hfdGFyZ2V0cygKICAgIGVudnMsCiAgICBsaWJyYXJpZXMsCiAgICBwb3NhcmdzLAogICAgdG94ZGVwcywKICAgIHRveGFyZ3MsCiAgICBweXRlc3QsCiAgICBweXRlc3RfcmVzdWx0c19zdW1tYXJ5LAogICAgY292ZXJhZ2UsCiAgICBjb25kYSwKICAgIHNldGVudiwKICAgIGRpc3BsYXksCiAgICBjYWNoZV9wYXRoLAogICAgY2FjaGVfa2V5LAogICAgY2FjaGVfcmVzdG9yZV9rZXlzLAogICAgYXJ0aWZhY3RfcGF0aCwKICAgIGFydGlmYWN0X2FyY2hpdmUsCiAgICBhcnRpZmFjdF9pbmNsdWRlX2hpZGRlbl9maWxlcywKICAgIGFydGlmYWN0X2lmX25vX2ZpbGVzX2ZvdW5kLAogICAgcnVuc19vbiwKICAgIGRlZmF1bHRfcHl0aG9uLAogICAgdGltZW91dF9taW51dGVzLAopOgogICAgIiIiU2NyaXB0IHRvIGxvYWQgdG94IHRhcmdldHMgZm9yIEdpdEh1YiBBY3Rpb25zIHdvcmtmbG93LiIiIgogICAgIyBMb2FkIGVudnMgY29uZmlnCiAgICBlbnZzID0geWFtbC5sb2FkKGVudnMucmVwbGFjZSgiXFxuIiwgIlxuIiksIExvYWRlcj15YW1sLkJhc2VMb2FkZXIpCiAgICBwcmludChqc29uLmR1bXBzKGVudnMsIGluZGVudD0yKSkKCiAgICAjIExvYWQgZ2xvYmFsIGxpYnJhcmllcyBjb25maWcKICAgIGdsb2JhbF9saWJyYXJpZXMgPSB7CiAgICAgICAgImJyZXciOiBbXSwKICAgICAgICAiYnJldy1jYXNrIjogW10sCiAgICAgICAgImFwdCI6IFtdLAogICAgICAgICJjaG9jbyI6IFtdLAogICAgfQogICAgbGlicmFyaWVzID0geWFtbC5sb2FkKGxpYnJhcmllcywgTG9hZGVyPXlhbWwuQmFzZUxvYWRlcikKICAgIGlmIGxpYnJhcmllcyBpcyBub3QgTm9uZToKICAgICAgICBnbG9iYWxfbGlicmFyaWVzLnVwZGF0ZShsaWJyYXJpZXMpCiAgICBwcmludChqc29uLmR1bXBzKGdsb2JhbF9saWJyYXJpZXMsIGluZGVudD0yKSkKCiAgICAjIERlZmF1bHQgaW1hZ2VzIHRvIHVzZSBmb3IgcnVubmVycwogICAgZGVmYXVsdF9ydW5zX29uID0gewogICAgICAgICJsaW51eCI6ICJ1YnVudHUtbGF0ZXN0IiwKICAgICAgICAibWFjb3MiOiAibWFjb3MtbGF0ZXN0IiwKICAgICAgICAid2luZG93cyI6ICJ3aW5kb3dzLWxhdGVzdCIsCiAgICB9CiAgICBjdXN0b21fcnVuc19vbiA9IHlhbWwubG9hZChydW5zX29uLCBMb2FkZXI9eWFtbC5CYXNlTG9hZGVyKQogICAgaWYgaXNpbnN0YW5jZShjdXN0b21fcnVuc19vbiwgZGljdCk6CiAgICAgICAgZGVmYXVsdF9ydW5zX29uLnVwZGF0ZShjdXN0b21fcnVuc19vbikKICAgIHByaW50KGpzb24uZHVtcHMoZGVmYXVsdF9ydW5zX29uLCBpbmRlbnQ9MikpCgogICAgIyBEZWZhdWx0IHN0cmluZyBwYXJhbWV0ZXJzIHdoaWNoIGNhbiBiZSBvdmVyd3JpdHRlbiBieSBlYWNoIGVudgogICAgc3RyaW5nX3BhcmFtZXRlcnMgPSB7CiAgICAgICAgInBvc2FyZ3MiOiBwb3NhcmdzLAogICAgICAgICJ0b3hkZXBzIjogdG94ZGVwcywKICAgICAgICAidG94YXJncyI6IHRveGFyZ3MsCiAgICAgICAgInB5dGVzdCI6IHB5dGVzdCwKICAgICAgICAicHl0ZXN0LXJlc3VsdHMtc3VtbWFyeSI6IHB5dGVzdF9yZXN1bHRzX3N1bW1hcnksCiAgICAgICAgImNvdmVyYWdlIjogY292ZXJhZ2UsCiAgICAgICAgImNvbmRhIjogY29uZGEsCiAgICAgICAgInNldGVudiI6IHNldGVudiwKICAgICAgICAiZGlzcGxheSI6IGRpc3BsYXksCiAgICAgICAgImNhY2hlLXBhdGgiOiBjYWNoZV9wYXRoLAogICAgICAgICJjYWNoZS1rZXkiOiBjYWNoZV9rZXksCiAgICAgICAgImNhY2hlLXJlc3RvcmUta2V5cyI6IGNhY2hlX3Jlc3RvcmVfa2V5cywKICAgICAgICAiYXJ0aWZhY3QtcGF0aCI6IGFydGlmYWN0X3BhdGgsCiAgICAgICAgImFydGlmYWN0LWFyY2hpdmUiOiBhcnRpZmFjdF9hcmNoaXZlLAogICAgICAgICJhcnRpZmFjdC1pbmNsdWRlLWhpZGRlbi1maWxlcyI6IGFydGlmYWN0X2luY2x1ZGVfaGlkZGVuX2ZpbGVzLAogICAgICAgICJhcnRpZmFjdC1pZi1uby1maWxlcy1mb3VuZCI6IGFydGlmYWN0X2lmX25vX2ZpbGVzX2ZvdW5kLAogICAgICAgICJ0aW1lb3V0LW1pbnV0ZXMiOiB0aW1lb3V0X21pbnV0ZXMsCiAgICB9CgogICAgIyBDcmVhdGUgbWF0cml4CiAgICBtYXRyaXggPSB7ImluY2x1ZGUiOiBbXX0KICAgIGZvciBlbnYgaW4gZW52czoKICAgICAgICBtYXRyaXhbImluY2x1ZGUiXS5hcHBlbmQoCiAgICAgICAgICAgIGdldF9tYXRyaXhfaXRlbSgKICAgICAgICAgICAgICAgIGVudiwKICAgICAgICAgICAgICAgIGdsb2JhbF9saWJyYXJpZXM9Z2xvYmFsX2xpYnJhcmllcywKICAgICAgICAgICAgICAgIGdsb2JhbF9zdHJpbmdfcGFyYW1ldGVycz1zdHJpbmdfcGFyYW1ldGVycywKICAgICAgICAgICAgICAgIHJ1bnNfb249ZGVmYXVsdF9ydW5zX29uLAogICAgICAgICAgICAgICAgZGVmYXVsdF9weXRob249ZGVmYXVsdF9weXRob24sCiAgICAgICAgICAgICkKICAgICAgICApCgogICAgIyBPdXRwdXQgbWF0cml4CiAgICBwcmludChqc29uLmR1bXBzKG1hdHJpeCwgaW5kZW50PTIpKQogICAgd2l0aCBvcGVuKG9zLmVudmlyb25bIkdJVEhVQl9PVVRQVVQiXSwgImEiKSBhcyBmOgogICAgICAgIGYud3JpdGUoZiJtYXRyaXg9e2pzb24uZHVtcHMobWF0cml4KX1cbiIpCgoKZGVmIGdldF9tYXRyaXhfaXRlbShlbnYsIGdsb2JhbF9saWJyYXJpZXMsIGdsb2JhbF9zdHJpbmdfcGFyYW1ldGVycywgcnVuc19vbiwgZGVmYXVsdF9weXRob24pOgoKICAgICMgZGVmaW5lIHNwZWMgZm9yIGVhY2ggbWF0cml4IGluY2x1ZGUgKCsgZ2xvYmFsX3N0cmluZ19wYXJhbWV0ZXJzKQogICAgaXRlbSA9IHsKICAgICAgICAib3MiOiBOb25lLAogICAgICAgICJ0b3hlbnYiOiBOb25lLAogICAgICAgICJweXRob25fdmVyc2lvbiI6IE5vbmUsCiAgICAgICAgIm5hbWUiOiBOb25lLAogICAgICAgICJweXRlc3RfZmxhZyI6IE5vbmUsCiAgICAgICAgImxpYnJhcmllc19icmV3IjogTm9uZSwKICAgICAgICAibGlicmFyaWVzX2JyZXdfY2FzayI6IE5vbmUsCiAgICAgICAgImxpYnJhcmllc19hcHQiOiBOb25lLAogICAgICAgICJsaWJyYXJpZXNfY2hvY28iOiBOb25lLAogICAgICAgICJjYWNoZS1wYXRoIjogTm9uZSwKICAgICAgICAiY2FjaGUta2V5IjogTm9uZSwKICAgICAgICAiY2FjaGUtcmVzdG9yZS1rZXlzIjogTm9uZSwKICAgICAgICAiYXJ0aWZhY3QtbmFtZSI6IE5vbmUsCiAgICAgICAgImFydGlmYWN0LXBhdGgiOiBOb25lLAogICAgICAgICJhcnRpZmFjdC1hcmNoaXZlIjogTm9uZSwKICAgICAgICAiYXJ0aWZhY3QtaW5jbHVkZS1oaWRkZW4tZmlsZXMiOiBOb25lLAogICAgICAgICJhcnRpZmFjdC1pZi1uby1maWxlcy1mb3VuZCI6IE5vbmUsCiAgICAgICAgInRpbWVvdXQtbWludXRlcyI6IE5vbmUsCiAgICB9CiAgICBmb3Igc3RyaW5nX3BhcmFtLCBkZWZhdWx0IGluIGdsb2JhbF9zdHJpbmdfcGFyYW1ldGVycy5pdGVtcygpOgogICAgICAgIGVudl92YWx1ZSA9IGVudi5nZXQoc3RyaW5nX3BhcmFtKQogICAgICAgIGl0ZW1bc3RyaW5nX3BhcmFtXSA9IGRlZmF1bHQgaWYgZW52X3ZhbHVlIGlzIE5vbmUgZWxzZSBlbnZfdmFsdWUKCiAgICAjIHNldCBvcyBhbmQgdG94ZW52CiAgICBmb3IgaywgdiBpbiBydW5zX29uLml0ZW1zKCk6CiAgICAgICAgaWYgayBpbiBlbnY6CiAgICAgICAgICAgIHBsYXRmb3JtID0gawogICAgICAgICAgICBpdGVtWyJvcyJdID0gZW52LmdldCgicnVucy1vbiIsIHYpCiAgICAgICAgICAgIGl0ZW1bInRveGVudiJdID0gZW52W2tdCiAgICBhc3NlcnQgaXRlbVsib3MiXSBpcyBub3QgTm9uZSBhbmQgaXRlbVsidG94ZW52Il0gaXMgbm90IE5vbmUKCiAgICAjIHNldCBweXRob25fdmVyc2lvbgogICAgcHl0aG9uX3ZlcnNpb24gPSBlbnYuZ2V0KCJweXRob24tdmVyc2lvbiIpCiAgICBtID0gcmUuc2VhcmNoKCJecHkoMnwzKShbMC05XSt0PykiLCBpdGVtWyJ0b3hlbnYiXSkKICAgIGlmIHB5dGhvbl92ZXJzaW9uIGlzIG5vdCBOb25lOgogICAgICAgIGl0ZW1bInB5dGhvbl92ZXJzaW9uIl0gPSBweXRob25fdmVyc2lvbgogICAgZWxpZiBtIGlzIG5vdCBOb25lOgogICAgICAgIG1ham9yLCBtaW5vciA9IG0uZ3JvdXBzKCkKICAgICAgICBpdGVtWyJweXRob25fdmVyc2lvbiJdID0gZiJ7bWFqb3J9LnttaW5vcn0iCiAgICBlbHNlOgogICAgICAgIGl0ZW1bInB5dGhvbl92ZXJzaW9uIl0gPSBlbnYuZ2V0KCJkZWZhdWx0X3B5dGhvbiIpIG9yIGRlZmF1bHRfcHl0aG9uCgogICAgIyBzZXQgbmFtZQogICAgaXRlbVsibmFtZSJdID0gZW52LmdldCgibmFtZSIpIG9yIGYie2l0ZW1bJ3RveGVudiddfSAoe2l0ZW1bJ29zJ119KSIKCiAgICAjIHNldCBhcnRpZmFjdC1uYW1lIChyZXBsYWNlIGludmFsaWQgcGF0aCBjaGFyYWN0ZXJzKQogICAgaXRlbVsiYXJ0aWZhY3QtbmFtZSJdID0gcmUuc3ViKHIiW1xcIC86PD58Kj9cIiddIiwgIi0iLCBpdGVtWyJuYW1lIl0pCiAgICBpdGVtWyJhcnRpZmFjdC1uYW1lIl0gPSByZS5zdWIociItKyIsICItIiwgaXRlbVsiYXJ0aWZhY3QtbmFtZSJdKQoKICAgICMgc2V0IHB5dGVzdF9mbGFnCiAgICBpdGVtWyJweXRlc3RfZmxhZyJdID0gIiIKICAgIHNlcCA9IHIiXFwiIGlmIHBsYXRmb3JtID09ICJ3aW5kb3dzIiBlbHNlICIvIgogICAgaWYgaXRlbVsicHl0ZXN0Il0gPT0gInRydWUiOgogICAgICAgIGlmICJjb2RlY292IiBpbiBpdGVtLmdldCgiY292ZXJhZ2UiLCAiIik6CiAgICAgICAgICAgICMgTm90ZSB0aGF0IHdlIGRvbid0IGluY2x1ZGUgLS1jb3YgaGVyZSBhcyBpZiBpdCdzIHByb3ZpZGVkIHRvIHB5dGVzdCB0d2ljZSBpdCBicmVha3MgY292IHJlcG9ydGluZy4KICAgICAgICAgICAgIyBMb3RzIG9mIHVzZXJzIG9mIHRoaXMgc3BlY2lmeSAtLWNvdiBpbiB0aGVpciB0b3guaW5pIHNvIGl0J3MgYmVlbiByZW1vdmVkIGZvciBiYWNrd2FyZHMgY29tcGF0aWJpbGl0eS4KICAgICAgICAgICAgIyBodHRwczovL2dpdGh1Yi5jb20vT3BlbkFzdHJvbm9teS9naXRodWItYWN0aW9ucy13b3JrZmxvd3MvaXNzdWVzLzM4MwogICAgICAgICAgICBpdGVtWyJweXRlc3RfZmxhZyJdICs9ICgKICAgICAgICAgICAgICAgIHJmIi0tY292LXJlcG9ydD14bWw6JHt7R0lUSFVCX1dPUktTUEFDRX19e3NlcH1jb3ZlcmFnZS54bWwgIgogICAgICAgICAgICApCgogICAgICAgIGlmIGl0ZW1bInB5dGVzdC1yZXN1bHRzLXN1bW1hcnkiXSA9PSAidHJ1ZSI6CiAgICAgICAgICAgIGl0ZW1bInB5dGVzdF9mbGFnIl0gKz0gcmYiLS1qdW5pdHhtbCAke3tHSVRIVUJfV09SS1NQQUNFfX17c2VwfXJlc3VsdHMueG1sICIKCiAgICAjIHNldCBsaWJyYXJpZXMKICAgIGVudl9saWJyYXJpZXMgPSBlbnYuZ2V0KCJsaWJyYXJpZXMiKQogICAgaWYgaXNpbnN0YW5jZShlbnZfbGlicmFyaWVzLCBzdHIpIGFuZCBsZW4oZW52X2xpYnJhcmllcy5zdHJpcCgpKSA9PSAwOgogICAgICAgIGVudl9saWJyYXJpZXMgPSB7fSAgIyBubyBsaWJyYXJpZXMgcmVxdWVzdGVkIGZvciBlbnZpcm9ubWVudAogICAgbGlicmFyaWVzID0gZ2xvYmFsX2xpYnJhcmllcyBpZiBlbnZfbGlicmFyaWVzIGlzIE5vbmUgZWxzZSBlbnZfbGlicmFyaWVzCiAgICBmb3IgbWFuYWdlciBpbiBbImJyZXciLCAiYnJld19jYXNrIiwgImFwdCIsICJjaG9jbyJdOgogICAgICAgIGl0ZW1bZiJsaWJyYXJpZXNfe21hbmFnZXJ9Il0gPSAiICIuam9pbihsaWJyYXJpZXMuZ2V0KG1hbmFnZXIsIFtdKSkKCiAgICBpZiBpdGVtWyJjb25kYSJdOgogICAgICAgIHdhcm5pbmdzLndhcm4oImBjb25kYWAgcGFyYW1ldGVyIGlzIGRlcHJlY2F0ZWQiKQoKICAgICAgICAjIHNldCAiYXV0byIgY29uZGEgdmFsdWUKICAgICAgICBpZiBpdGVtWyJjb25kYSJdID09ICJhdXRvIjoKICAgICAgICAgICAgaXRlbVsiY29uZGEiXSA9ICJ0cnVlIiBpZiAiY29uZGEiIGluIGl0ZW1bInRveGVudiJdIGVsc2UgImZhbHNlIgoKICAgICAgICAjIGluamVjdCB0b3hkZXBzIGZvciBjb25kYQogICAgICAgIGlmIGl0ZW1bImNvbmRhIl0gPT0gInRydWUiIGFuZCAidG94LWNvbmRhIiBub3QgaW4gaXRlbVsidG94ZGVwcyJdLmxvd2VyKCk6CiAgICAgICAgICAgIGl0ZW1bInRveGRlcHMiXSA9ICgidG94LWNvbmRhICIgKyBpdGVtWyJ0b3hkZXBzIl0pLnN0cmlwKCkKCiAgICAjIG1ha2UgdGltZW91dC1taW51dGVzIGEgbnVtYmVyCiAgICBpdGVtWyJ0aW1lb3V0LW1pbnV0ZXMiXSA9IGludChpdGVtWyJ0aW1lb3V0LW1pbnV0ZXMiXSkKCiAgICAjIHZlcmlmeSB2YWx1ZXMKICAgIGFzc2VydCBpdGVtWyJweXRlc3QiXSBpbiB7InRydWUiLCAiZmFsc2UifQogICAgYXNzZXJ0IGl0ZW1bImNvbmRhIl0gaW4geyJ0cnVlIiwgImZhbHNlIn0KICAgIGFzc2VydCBpdGVtWyJkaXNwbGF5Il0gaW4geyJ0cnVlIiwgImZhbHNlIn0KCiAgICByZXR1cm4gaXRlbQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBsb2FkX3RveF90YXJnZXRzKCkK + TOX_MATRIX_SCRIPT: IyAvLy8gc2NyaXB0CiMgcmVxdWlyZXMtcHl0aG9uID0gIj09My4xMiIKIyBkZXBlbmRlbmNpZXMgPSBbCiMgICAgICJjbGljaz09OC4yLjEiLAojICAgICAicGFja2FnaW5nPT0yNS4wIiwKIyAgICAgInB5eWFtbD09Ni4wLjIiLAojICAgICAicmVxdWVzdHM9PTIuMzIuNSIsCiMgICAgICJ0b21saT09Mi40LjAiLAojIF0KIyAvLy8KaW1wb3J0IGpzb24KaW1wb3J0IG9zCmltcG9ydCByZQppbXBvcnQgd2FybmluZ3MKZnJvbSBjb3B5IGltcG9ydCBjb3B5CmZyb20gcGF0aGxpYiBpbXBvcnQgUGF0aAoKaW1wb3J0IGNsaWNrCmltcG9ydCByZXF1ZXN0cwppbXBvcnQgdG9tbGkKaW1wb3J0IHlhbWwKZnJvbSBwYWNrYWdpbmcuc3BlY2lmaWVycyBpbXBvcnQgU3BlY2lmaWVyU2V0CmZyb20gcGFja2FnaW5nLnZlcnNpb24gaW1wb3J0IFZlcnNpb24KCgpAY2xpY2suY29tbWFuZCgpCkBjbGljay5vcHRpb24oIi0tZW52cyIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tbGlicmFyaWVzIiwgZGVmYXVsdD0iIikKQGNsaWNrLm9wdGlvbigiLS1wb3NhcmdzIiwgZGVmYXVsdD0iIikKQGNsaWNrLm9wdGlvbigiLS10b3hkZXBzIiwgZGVmYXVsdD0iIikKQGNsaWNrLm9wdGlvbigiLS10b3hhcmdzIiwgZGVmYXVsdD0iIikKQGNsaWNrLm9wdGlvbigiLS1weXRlc3QiLCBkZWZhdWx0PSJ0cnVlIikKQGNsaWNrLm9wdGlvbigiLS1weXRlc3QtcmVzdWx0cy1zdW1tYXJ5IiwgZGVmYXVsdD0iZmFsc2UiKQpAY2xpY2sub3B0aW9uKCItLWNvdmVyYWdlIiwgZGVmYXVsdD0iIikKQGNsaWNrLm9wdGlvbigiLS1jb25kYSIsIGRlZmF1bHQ9ImF1dG8iKQpAY2xpY2sub3B0aW9uKCItLXNldGVudiIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tZGlzcGxheSIsIGRlZmF1bHQ9ImZhbHNlIikKQGNsaWNrLm9wdGlvbigiLS1jYWNoZS1wYXRoIiwgZGVmYXVsdD0iIikKQGNsaWNrLm9wdGlvbigiLS1jYWNoZS1rZXkiLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLWNhY2hlLXJlc3RvcmUta2V5cyIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tYXJ0aWZhY3QtcGF0aCIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tYXJ0aWZhY3QtYXJjaGl2ZSIsIGRlZmF1bHQ9InRydWUiKQpAY2xpY2sub3B0aW9uKCItLWFydGlmYWN0LWluY2x1ZGUtaGlkZGVuLWZpbGVzIiwgZGVmYXVsdD0iZmFsc2UiKQpAY2xpY2sub3B0aW9uKCItLWFydGlmYWN0LWlmLW5vLWZpbGVzLWZvdW5kIiwgZGVmYXVsdD0id2FybiIpCkBjbGljay5vcHRpb24oIi0tcnVucy1vbiIsIGRlZmF1bHQ9IiIpCkBjbGljay5vcHRpb24oIi0tZGVmYXVsdC1weXRob24iLCBkZWZhdWx0PSIiKQpAY2xpY2sub3B0aW9uKCItLXRpbWVvdXQtbWludXRlcyIsIGRlZmF1bHQ9IjM2MCIpCmRlZiBsb2FkX3RveF90YXJnZXRzKAogICAgZW52cywKICAgIGxpYnJhcmllcywKICAgIHBvc2FyZ3MsCiAgICB0b3hkZXBzLAogICAgdG94YXJncywKICAgIHB5dGVzdCwKICAgIHB5dGVzdF9yZXN1bHRzX3N1bW1hcnksCiAgICBjb3ZlcmFnZSwKICAgIGNvbmRhLAogICAgc2V0ZW52LAogICAgZGlzcGxheSwKICAgIGNhY2hlX3BhdGgsCiAgICBjYWNoZV9rZXksCiAgICBjYWNoZV9yZXN0b3JlX2tleXMsCiAgICBhcnRpZmFjdF9wYXRoLAogICAgYXJ0aWZhY3RfYXJjaGl2ZSwKICAgIGFydGlmYWN0X2luY2x1ZGVfaGlkZGVuX2ZpbGVzLAogICAgYXJ0aWZhY3RfaWZfbm9fZmlsZXNfZm91bmQsCiAgICBydW5zX29uLAogICAgZGVmYXVsdF9weXRob24sCiAgICB0aW1lb3V0X21pbnV0ZXMsCik6CiAgICAiIiJTY3JpcHQgdG8gbG9hZCB0b3ggdGFyZ2V0cyBmb3IgR2l0SHViIEFjdGlvbnMgd29ya2Zsb3cuIiIiCiAgICAjIExvYWQgZW52cyBjb25maWcKICAgIGVudnMgPSB5YW1sLmxvYWQoZW52cy5yZXBsYWNlKCJcXG4iLCAiXG4iKSwgTG9hZGVyPXlhbWwuQmFzZUxvYWRlcikKICAgIHByaW50KGpzb24uZHVtcHMoZW52cywgaW5kZW50PTIpKQoKICAgICMgTG9hZCBnbG9iYWwgbGlicmFyaWVzIGNvbmZpZwogICAgZ2xvYmFsX2xpYnJhcmllcyA9IHsKICAgICAgICAiYnJldyI6IFtdLAogICAgICAgICJicmV3LWNhc2siOiBbXSwKICAgICAgICAiYXB0IjogW10sCiAgICAgICAgImNob2NvIjogW10sCiAgICB9CiAgICBsaWJyYXJpZXMgPSB5YW1sLmxvYWQobGlicmFyaWVzLCBMb2FkZXI9eWFtbC5CYXNlTG9hZGVyKQogICAgaWYgbGlicmFyaWVzIGlzIG5vdCBOb25lOgogICAgICAgIGdsb2JhbF9saWJyYXJpZXMudXBkYXRlKGxpYnJhcmllcykKICAgIHByaW50KGpzb24uZHVtcHMoZ2xvYmFsX2xpYnJhcmllcywgaW5kZW50PTIpKQoKICAgICMgRGVmYXVsdCBpbWFnZXMgdG8gdXNlIGZvciBydW5uZXJzCiAgICBkZWZhdWx0X3J1bnNfb24gPSB7CiAgICAgICAgImxpbnV4IjogInVidW50dS1sYXRlc3QiLAogICAgICAgICJtYWNvcyI6ICJtYWNvcy1sYXRlc3QiLAogICAgICAgICJ3aW5kb3dzIjogIndpbmRvd3MtbGF0ZXN0IiwKICAgIH0KICAgIGN1c3RvbV9ydW5zX29uID0geWFtbC5sb2FkKHJ1bnNfb24sIExvYWRlcj15YW1sLkJhc2VMb2FkZXIpCiAgICBpZiBpc2luc3RhbmNlKGN1c3RvbV9ydW5zX29uLCBkaWN0KToKICAgICAgICBkZWZhdWx0X3J1bnNfb24udXBkYXRlKGN1c3RvbV9ydW5zX29uKQogICAgcHJpbnQoanNvbi5kdW1wcyhkZWZhdWx0X3J1bnNfb24sIGluZGVudD0yKSkKCiAgICAjIERlZmF1bHQgc3RyaW5nIHBhcmFtZXRlcnMgd2hpY2ggY2FuIGJlIG92ZXJ3cml0dGVuIGJ5IGVhY2ggZW52CiAgICBzdHJpbmdfcGFyYW1ldGVycyA9IHsKICAgICAgICAicG9zYXJncyI6IHBvc2FyZ3MsCiAgICAgICAgInRveGRlcHMiOiB0b3hkZXBzLAogICAgICAgICJ0b3hhcmdzIjogdG94YXJncywKICAgICAgICAicHl0ZXN0IjogcHl0ZXN0LAogICAgICAgICJweXRlc3QtcmVzdWx0cy1zdW1tYXJ5IjogcHl0ZXN0X3Jlc3VsdHNfc3VtbWFyeSwKICAgICAgICAiY292ZXJhZ2UiOiBjb3ZlcmFnZSwKICAgICAgICAiY29uZGEiOiBjb25kYSwKICAgICAgICAic2V0ZW52Ijogc2V0ZW52LAogICAgICAgICJkaXNwbGF5IjogZGlzcGxheSwKICAgICAgICAiY2FjaGUtcGF0aCI6IGNhY2hlX3BhdGgsCiAgICAgICAgImNhY2hlLWtleSI6IGNhY2hlX2tleSwKICAgICAgICAiY2FjaGUtcmVzdG9yZS1rZXlzIjogY2FjaGVfcmVzdG9yZV9rZXlzLAogICAgICAgICJhcnRpZmFjdC1wYXRoIjogYXJ0aWZhY3RfcGF0aCwKICAgICAgICAiYXJ0aWZhY3QtYXJjaGl2ZSI6IGFydGlmYWN0X2FyY2hpdmUsCiAgICAgICAgImFydGlmYWN0LWluY2x1ZGUtaGlkZGVuLWZpbGVzIjogYXJ0aWZhY3RfaW5jbHVkZV9oaWRkZW5fZmlsZXMsCiAgICAgICAgImFydGlmYWN0LWlmLW5vLWZpbGVzLWZvdW5kIjogYXJ0aWZhY3RfaWZfbm9fZmlsZXNfZm91bmQsCiAgICAgICAgInRpbWVvdXQtbWludXRlcyI6IHRpbWVvdXRfbWludXRlcywKICAgIH0KCiAgICAjIENyZWF0ZSBtYXRyaXgKICAgIG1hdHJpeCA9IHsiaW5jbHVkZSI6IFtdfQogICAgZm9yIGVudiBpbiBlbnZzOgogICAgICAgIG1hdHJpeF9pdGVtID0gZ2V0X21hdHJpeF9pdGVtKAogICAgICAgICAgICBlbnYsCiAgICAgICAgICAgIGdsb2JhbF9saWJyYXJpZXM9Z2xvYmFsX2xpYnJhcmllcywKICAgICAgICAgICAgZ2xvYmFsX3N0cmluZ19wYXJhbWV0ZXJzPXN0cmluZ19wYXJhbWV0ZXJzLAogICAgICAgICAgICBydW5zX29uPWRlZmF1bHRfcnVuc19vbiwKICAgICAgICAgICAgZGVmYXVsdF9weXRob249ZGVmYXVsdF9weXRob24sCiAgICAgICAgKQoKICAgICAgICAjIGNoZWNrIGlmIHdlIG5lZWQgdG8gZXhwYW5kIHB5dGhvbiB2ZXJzaW9ucyBmcm9tIGEgZ2xvYiAoaS5lLiBweSosIHB5MyosIHB5MzEqLCBldGMuKQogICAgICAgIHRveGVudiA9IG1hdHJpeF9pdGVtWyJ0b3hlbnYiXQogICAgICAgIGlmIHRveGVudi5zdGFydHN3aXRoKCJweSIpIGFuZCAiKiIgaW4gdG94ZW52LnNwbGl0KCItIilbMF06CiAgICAgICAgICAgIHRveGVudnMgPSBleHBhbmRfcHl0aG9uX3ZlcnNpb25zKHRveGVudikKCiAgICAgICAgICAgIGZvciBleHBhbmRlZF90b3hlbnYsIHB5dGhvbl92ZXJzaW9uIGluIHRveGVudnM6CiAgICAgICAgICAgICAgICBleHBhbmRlZF9tYXRyaXhfaXRlbSA9IGNvcHkobWF0cml4X2l0ZW0pCiAgICAgICAgICAgICAgICBleHBhbmRlZF9tYXRyaXhfaXRlbVsidG94ZW52Il0gPSBleHBhbmRlZF90b3hlbnYKICAgICAgICAgICAgICAgIGV4cGFuZGVkX21hdHJpeF9pdGVtWyJuYW1lIl0gPSBleHBhbmRlZF9tYXRyaXhfaXRlbVsibmFtZSJdLnJlcGxhY2UoCiAgICAgICAgICAgICAgICAgICAgdG94ZW52LCBleHBhbmRlZF90b3hlbnYKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGV4cGFuZGVkX21hdHJpeF9pdGVtWyJweXRob25fdmVyc2lvbiJdID0gcHl0aG9uX3ZlcnNpb24KICAgICAgICAgICAgICAgIG1hdHJpeFsiaW5jbHVkZSJdLmFwcGVuZChleHBhbmRlZF9tYXRyaXhfaXRlbSkKICAgICAgICBlbHNlOgogICAgICAgICAgICBtYXRyaXhbImluY2x1ZGUiXS5hcHBlbmQobWF0cml4X2l0ZW0pCgogICAgIyBPdXRwdXQgbWF0cml4CiAgICBwcmludChqc29uLmR1bXBzKG1hdHJpeCwgaW5kZW50PTIpKQogICAgd2l0aCBvcGVuKG9zLmVudmlyb25bIkdJVEhVQl9PVVRQVVQiXSwgImEiKSBhcyBmOgogICAgICAgIGYud3JpdGUoZiJtYXRyaXg9e2pzb24uZHVtcHMobWF0cml4KX1cbiIpCgoKZGVmIGdldF9tYXRyaXhfaXRlbShlbnYsIGdsb2JhbF9saWJyYXJpZXMsIGdsb2JhbF9zdHJpbmdfcGFyYW1ldGVycywgcnVuc19vbiwgZGVmYXVsdF9weXRob24pOgoKICAgICMgZGVmaW5lIHNwZWMgZm9yIGVhY2ggbWF0cml4IGluY2x1ZGUgKCsgZ2xvYmFsX3N0cmluZ19wYXJhbWV0ZXJzKQogICAgaXRlbSA9IHsKICAgICAgICAib3MiOiBOb25lLAogICAgICAgICJ0b3hlbnYiOiBOb25lLAogICAgICAgICJweXRob25fdmVyc2lvbiI6IE5vbmUsCiAgICAgICAgIm5hbWUiOiBOb25lLAogICAgICAgICJweXRlc3RfZmxhZyI6IE5vbmUsCiAgICAgICAgImxpYnJhcmllc19icmV3IjogTm9uZSwKICAgICAgICAibGlicmFyaWVzX2JyZXdfY2FzayI6IE5vbmUsCiAgICAgICAgImxpYnJhcmllc19hcHQiOiBOb25lLAogICAgICAgICJsaWJyYXJpZXNfY2hvY28iOiBOb25lLAogICAgICAgICJjYWNoZS1wYXRoIjogTm9uZSwKICAgICAgICAiY2FjaGUta2V5IjogTm9uZSwKICAgICAgICAiY2FjaGUtcmVzdG9yZS1rZXlzIjogTm9uZSwKICAgICAgICAiYXJ0aWZhY3QtbmFtZSI6IE5vbmUsCiAgICAgICAgImFydGlmYWN0LXBhdGgiOiBOb25lLAogICAgICAgICJhcnRpZmFjdC1hcmNoaXZlIjogTm9uZSwKICAgICAgICAiYXJ0aWZhY3QtaW5jbHVkZS1oaWRkZW4tZmlsZXMiOiBOb25lLAogICAgICAgICJhcnRpZmFjdC1pZi1uby1maWxlcy1mb3VuZCI6IE5vbmUsCiAgICAgICAgInRpbWVvdXQtbWludXRlcyI6IE5vbmUsCiAgICB9CiAgICBmb3Igc3RyaW5nX3BhcmFtLCBkZWZhdWx0IGluIGdsb2JhbF9zdHJpbmdfcGFyYW1ldGVycy5pdGVtcygpOgogICAgICAgIGVudl92YWx1ZSA9IGVudi5nZXQoc3RyaW5nX3BhcmFtKQogICAgICAgIGl0ZW1bc3RyaW5nX3BhcmFtXSA9IGRlZmF1bHQgaWYgZW52X3ZhbHVlIGlzIE5vbmUgZWxzZSBlbnZfdmFsdWUKCiAgICAjIHNldCBvcyBhbmQgdG94ZW52CiAgICBmb3IgaywgdiBpbiBydW5zX29uLml0ZW1zKCk6CiAgICAgICAgaWYgayBpbiBlbnY6CiAgICAgICAgICAgIHBsYXRmb3JtID0gawogICAgICAgICAgICBpdGVtWyJvcyJdID0gZW52LmdldCgicnVucy1vbiIsIHYpCiAgICAgICAgICAgIGl0ZW1bInRveGVudiJdID0gZW52W2tdCiAgICBhc3NlcnQgaXRlbVsib3MiXSBpcyBub3QgTm9uZSBhbmQgaXRlbVsidG94ZW52Il0gaXMgbm90IE5vbmUKCiAgICAjIHNldCBweXRob25fdmVyc2lvbgogICAgcHl0aG9uX3ZlcnNpb24gPSBlbnYuZ2V0KCJweXRob24tdmVyc2lvbiIpCiAgICBtID0gcmUuc2VhcmNoKCJecHkoMnwzKShbMC05XSt0PykiLCBpdGVtWyJ0b3hlbnYiXSkKICAgIGlmIHB5dGhvbl92ZXJzaW9uIGlzIG5vdCBOb25lOgogICAgICAgIGl0ZW1bInB5dGhvbl92ZXJzaW9uIl0gPSBweXRob25fdmVyc2lvbgogICAgZWxpZiBtIGlzIG5vdCBOb25lOgogICAgICAgIG1ham9yLCBtaW5vciA9IG0uZ3JvdXBzKCkKICAgICAgICBpdGVtWyJweXRob25fdmVyc2lvbiJdID0gZiJ7bWFqb3J9LnttaW5vcn0iCiAgICBlbHNlOgogICAgICAgIGl0ZW1bInB5dGhvbl92ZXJzaW9uIl0gPSBlbnYuZ2V0KCJkZWZhdWx0X3B5dGhvbiIpIG9yIGRlZmF1bHRfcHl0aG9uCgogICAgIyBzZXQgbmFtZQogICAgaXRlbVsibmFtZSJdID0gZW52LmdldCgibmFtZSIpIG9yIGYie2l0ZW1bJ3RveGVudiddfSAoe2l0ZW1bJ29zJ119KSIKCiAgICAjIHNldCBhcnRpZmFjdC1uYW1lIChyZXBsYWNlIGludmFsaWQgcGF0aCBjaGFyYWN0ZXJzKQogICAgaXRlbVsiYXJ0aWZhY3QtbmFtZSJdID0gcmUuc3ViKHIiW1xcIC86PD58Kj9cIiddIiwgIi0iLCBpdGVtWyJuYW1lIl0pCiAgICBpdGVtWyJhcnRpZmFjdC1uYW1lIl0gPSByZS5zdWIociItKyIsICItIiwgaXRlbVsiYXJ0aWZhY3QtbmFtZSJdKQoKICAgICMgc2V0IHB5dGVzdF9mbGFnCiAgICBpdGVtWyJweXRlc3RfZmxhZyJdID0gIiIKICAgIHNlcCA9IHIiXFwiIGlmIHBsYXRmb3JtID09ICJ3aW5kb3dzIiBlbHNlICIvIgogICAgaWYgaXRlbVsicHl0ZXN0Il0gPT0gInRydWUiOgogICAgICAgIGlmICJjb2RlY292IiBpbiBpdGVtLmdldCgiY292ZXJhZ2UiLCAiIik6CiAgICAgICAgICAgICMgTm90ZSB0aGF0IHdlIGRvbid0IGluY2x1ZGUgLS1jb3YgaGVyZSBhcyBpZiBpdCdzIHByb3ZpZGVkIHRvIHB5dGVzdCB0d2ljZSBpdCBicmVha3MgY292IHJlcG9ydGluZy4KICAgICAgICAgICAgIyBMb3RzIG9mIHVzZXJzIG9mIHRoaXMgc3BlY2lmeSAtLWNvdiBpbiB0aGVpciB0b3guaW5pIHNvIGl0J3MgYmVlbiByZW1vdmVkIGZvciBiYWNrd2FyZHMgY29tcGF0aWJpbGl0eS4KICAgICAgICAgICAgIyBodHRwczovL2dpdGh1Yi5jb20vT3BlbkFzdHJvbm9teS9naXRodWItYWN0aW9ucy13b3JrZmxvd3MvaXNzdWVzLzM4MwogICAgICAgICAgICBpdGVtWyJweXRlc3RfZmxhZyJdICs9IHJmIi0tY292LXJlcG9ydD14bWw6JHt7R0lUSFVCX1dPUktTUEFDRX19e3NlcH1jb3ZlcmFnZS54bWwgIgoKICAgICAgICBpZiBpdGVtWyJweXRlc3QtcmVzdWx0cy1zdW1tYXJ5Il0gPT0gInRydWUiOgogICAgICAgICAgICBpdGVtWyJweXRlc3RfZmxhZyJdICs9IHJmIi0tanVuaXR4bWwgJHt7R0lUSFVCX1dPUktTUEFDRX19e3NlcH1yZXN1bHRzLnhtbCAiCgogICAgIyBzZXQgbGlicmFyaWVzCiAgICBlbnZfbGlicmFyaWVzID0gZW52LmdldCgibGlicmFyaWVzIikKICAgIGlmIGlzaW5zdGFuY2UoZW52X2xpYnJhcmllcywgc3RyKSBhbmQgbGVuKGVudl9saWJyYXJpZXMuc3RyaXAoKSkgPT0gMDoKICAgICAgICBlbnZfbGlicmFyaWVzID0ge30gICMgbm8gbGlicmFyaWVzIHJlcXVlc3RlZCBmb3IgZW52aXJvbm1lbnQKICAgIGxpYnJhcmllcyA9IGdsb2JhbF9saWJyYXJpZXMgaWYgZW52X2xpYnJhcmllcyBpcyBOb25lIGVsc2UgZW52X2xpYnJhcmllcwogICAgZm9yIG1hbmFnZXIgaW4gWyJicmV3IiwgImJyZXdfY2FzayIsICJhcHQiLCAiY2hvY28iXToKICAgICAgICBpdGVtW2YibGlicmFyaWVzX3ttYW5hZ2VyfSJdID0gIiAiLmpvaW4obGlicmFyaWVzLmdldChtYW5hZ2VyLCBbXSkpCgogICAgaWYgaXRlbVsiY29uZGEiXToKICAgICAgICB3YXJuaW5ncy53YXJuKCJgY29uZGFgIHBhcmFtZXRlciBpcyBkZXByZWNhdGVkIikKCiAgICAgICAgIyBzZXQgImF1dG8iIGNvbmRhIHZhbHVlCiAgICAgICAgaWYgaXRlbVsiY29uZGEiXSA9PSAiYXV0byI6CiAgICAgICAgICAgIGl0ZW1bImNvbmRhIl0gPSAidHJ1ZSIgaWYgImNvbmRhIiBpbiBpdGVtWyJ0b3hlbnYiXSBlbHNlICJmYWxzZSIKCiAgICAgICAgIyBpbmplY3QgdG94ZGVwcyBmb3IgY29uZGEKICAgICAgICBpZiBpdGVtWyJjb25kYSJdID09ICJ0cnVlIiBhbmQgInRveC1jb25kYSIgbm90IGluIGl0ZW1bInRveGRlcHMiXS5sb3dlcigpOgogICAgICAgICAgICBpdGVtWyJ0b3hkZXBzIl0gPSAoInRveC1jb25kYSAiICsgaXRlbVsidG94ZGVwcyJdKS5zdHJpcCgpCgogICAgIyBtYWtlIHRpbWVvdXQtbWludXRlcyBhIG51bWJlcgogICAgaXRlbVsidGltZW91dC1taW51dGVzIl0gPSBpbnQoaXRlbVsidGltZW91dC1taW51dGVzIl0pCgogICAgIyB2ZXJpZnkgdmFsdWVzCiAgICBhc3NlcnQgaXRlbVsicHl0ZXN0Il0gaW4geyJ0cnVlIiwgImZhbHNlIn0KICAgIGFzc2VydCBpdGVtWyJjb25kYSJdIGluIHsidHJ1ZSIsICJmYWxzZSJ9CiAgICBhc3NlcnQgaXRlbVsiZGlzcGxheSJdIGluIHsidHJ1ZSIsICJmYWxzZSJ9CgogICAgcmV0dXJuIGl0ZW0KCgpkZWYgZXhwYW5kX3B5dGhvbl92ZXJzaW9ucyh0b3hlbnY6IHN0cikgLT4gbGlzdFsoc3RyLCBzdHIpXToKICAgICIiIgogICAgZXhwYW5kIGBweTMqYCBpbnRvIGBweTMxMWAsIGBweTMxMmAsIGBweTMxM2AsIGV0Yy4gYmFzZWQgb24gY3VycmVudGx5LXN1cHBvcnRlZCBQeXRob24gdmVyc2lvbnMKCiAgICA6cGFyYW0gdmVyc2lvbl9nbG9iOiBjYW4gYmUgYHB5KmAsIGBweTMqYCwgYHB5MzAqYCwgYHB5MzEqYCBldGMuCiAgICAiIiIKCiAgICB0b3hlbnZfZmFjdG9ycyA9IHRveGVudi5zcGxpdCgiLSIpCiAgICBweV92ZXJzaW9uX2dsb2IgPSB0b3hlbnZfZmFjdG9yc1swXQogICAgaWYgbm90IHB5X3ZlcnNpb25fZ2xvYi5zdGFydHN3aXRoKCJweSIpOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgIGYnaW5wdXQgIntweV92ZXJzaW9uX2dsb2J9IiBpcyBub3QgYSBQeXRob24gdmVyc2lvbiBUb3ggZmFjdG9yIChtdXN0IHN0YXJ0IHdpdGggYHB5YCknCiAgICAgICAgKQoKICAgIGlmICIqIiBub3QgaW4gcHlfdmVyc2lvbl9nbG9iOgogICAgICAgIHJldHVybiBbcHlfdmVyc2lvbl9nbG9iXQoKICAgIGlmIG5vdCBweV92ZXJzaW9uX2dsb2IuZW5kc3dpdGgoIioiKToKICAgICAgICByYWlzZSBOb3RJbXBsZW1lbnRlZEVycm9yKAogICAgICAgICAgICAiUHl0aG9uIHZlcnNpb24gZ2xvYiBtdXN0IGVuZCB3aXRoIGEgYCpgOyBzdWZmaXhlcyBzdWNoIGFzIGB0YCBhcmUgbm90IHlldCBzdXBwb3J0ZWQiCiAgICAgICAgKQoKICAgIHB5dGhvbl92ZXJzaW9ucyA9IGdldF9zdXBwb3J0ZWRfcHl0aG9uX3ZlcnNpb25zKHBhY2thZ2Vfc291cmNlPSIuIikKCiAgICBtYWpvcl92ZXJzaW9uID0gcHlfdmVyc2lvbl9nbG9iWzJdCiAgICBpZiBtYWpvcl92ZXJzaW9uICE9ICIqIjoKICAgICAgICBweXRob25fdmVyc2lvbnMgPSBbCiAgICAgICAgICAgIHB5dGhvbl92ZXJzaW9uCiAgICAgICAgICAgIGZvciBweXRob25fdmVyc2lvbiBpbiBweXRob25fdmVyc2lvbnMKICAgICAgICAgICAgaWYgcHl0aG9uX3ZlcnNpb24ubWFqb3IgPT0gaW50KG1ham9yX3ZlcnNpb24pCiAgICAgICAgXQoKICAgICAgICBtaW5vcl92ZXJzaW9uID0gcHlfdmVyc2lvbl9nbG9iWzM6XQogICAgICAgIGlmIG1pbm9yX3ZlcnNpb25fc3BlY2lmaWVyIDo9IG1pbm9yX3ZlcnNpb24uc3BsaXQoIioiKVswXSAhPSAiKiI6CiAgICAgICAgICAgIG1pbm9yX3ZlcnNpb25fYmFzZSA9IGludChtaW5vcl92ZXJzaW9uX3NwZWNpZmllcikgKiAxMAogICAgICAgICAgICBweXRob25fdmVyc2lvbnMgPSBbCiAgICAgICAgICAgICAgICBweXRob25fdmVyc2lvbgogICAgICAgICAgICAgICAgZm9yIHB5dGhvbl92ZXJzaW9uIGluIHB5dGhvbl92ZXJzaW9ucwogICAgICAgICAgICAgICAgaWYgbWlub3JfdmVyc2lvbl9iYXNlIDw9IHB5dGhvbl92ZXJzaW9uLm1pbm9yIDwgbWlub3JfdmVyc2lvbl9iYXNlICsgMTAKICAgICAgICAgICAgXQoKICAgIHJldHVybiBbCiAgICAgICAgKAogICAgICAgICAgICBmInB5e3B5dGhvbl92ZXJzaW9uLm1ham9yfXtweXRob25fdmVyc2lvbi5taW5vcn0iCiAgICAgICAgICAgICsgKGYiLXsnLScuam9pbih0b3hlbnZfZmFjdG9yc1sxOl0pfSIgaWYgbGVuKHRveGVudl9mYWN0b3JzKSA+IDEgZWxzZSAiIiksCiAgICAgICAgICAgIHN0cihweXRob25fdmVyc2lvbiksCiAgICAgICAgKQogICAgICAgIGZvciBweXRob25fdmVyc2lvbiBpbiBweXRob25fdmVyc2lvbnMKICAgIF0KCgpkZWYgZ2V0X3N1cHBvcnRlZF9weXRob25fdmVyc2lvbnMoCiAgICBwYWNrYWdlX3NvdXJjZTogUGF0aCA9IE5vbmUsCiAgICBub19lb2FzOiBib29sID0gRmFsc2UsCikgLT4gbGlzdFtWZXJzaW9uXToKICAgIGN1cnJlbnRfcHl0aG9uX3ZlcnNpb25zID0gZ2V0X2N1cnJlbnRfcHl0aG9uX3ZlcnNpb25zKG5vX2VvYXM9bm9fZW9hcykKCiAgICBpZiBub3QgcGFja2FnZV9zb3VyY2U6CiAgICAgICAgc3VwcG9ydGVkX3ZlcnNpb25zID0gY3VycmVudF9weXRob25fdmVyc2lvbnMKICAgIGVsc2U6CiAgICAgICAgdHJ5OgogICAgICAgICAgICBweXByb2plY3RfdG9tbF9maWxlbmFtZSA9IFBhdGgocGFja2FnZV9zb3VyY2UpIC8gInB5cHJvamVjdC50b21sIgogICAgICAgICAgICBpZiBweXByb2plY3RfdG9tbF9maWxlbmFtZS5leGlzdHMoKToKICAgICAgICAgICAgICAgIHdpdGggb3BlbihweXByb2plY3RfdG9tbF9maWxlbmFtZSwgInJiIikgYXMgcHlwcm9qZWN0X3RvbWxfZmlsZToKICAgICAgICAgICAgICAgICAgICBweXByb2plY3RfdG9tbCA9IHRvbWxpLmxvYWQocHlwcm9qZWN0X3RvbWxfZmlsZSkKICAgICAgICAgICAgICAgIGlmICJwcm9qZWN0IiBpbiBweXByb2plY3RfdG9tbDoKICAgICAgICAgICAgICAgICAgICBwcm9qZWN0X21ldGFkYXRhID0gcHlwcm9qZWN0X3RvbWxbInByb2plY3QiXQogICAgICAgICAgICAgICAgICAgIGlmICJyZXF1aXJlcy1weXRob24iIGluIHByb2plY3RfbWV0YWRhdGE6CiAgICAgICAgICAgICAgICAgICAgICAgIHB5dGhvbl92ZXJzaW9uX3JlcXVpcmVtZW50cyA9IFNwZWNpZmllclNldCgKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2plY3RfbWV0YWRhdGFbInJlcXVpcmVzLXB5dGhvbiJdCiAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgICAgICByYWlzZSBLZXlFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJgcHJvamVjdC5yZXF1aXJlcy1weXRob25gIG5vdCBmb3VuZCBpbiBgcHlwcm9qZWN0LnRvbWxgOyBlbnN1cmUgeW91ciBwYWNrYWdlIGNvbmZvcm1zIHRvIFBFUDYyMSIKICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICByYWlzZSBLZXlFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgImBwcm9qZWN0YCBub3QgZm91bmQgaW4gYHB5cHJvamVjdC50b21sYDsgZW5zdXJlIHlvdXIgcGFja2FnZSBjb25mb3JtcyB0byBQRVA2MjEiCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgcmFpc2UgRmlsZU5vdEZvdW5kRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgImNvdWxkIG5vdCBmaW5kIGBweXByb2plY3QudG9tbGAgaW4gdGhlIHByb3ZpZGVkIHBhY2thZ2Ugc291cmNlOyBlbnN1cmUgeW91ciBwYWNrYWdlIGNvbmZvcm1zIHRvIFBFUDYyMSIKICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgIHN1cHBvcnRlZF92ZXJzaW9ucyA9IFsKICAgICAgICAgICAgICAgIHB5dGhvbl92ZXJzaW9uCiAgICAgICAgICAgICAgICBmb3IgcHl0aG9uX3ZlcnNpb24gaW4gY3VycmVudF9weXRob25fdmVyc2lvbnMKICAgICAgICAgICAgICAgIGlmIHB5dGhvbl92ZXJzaW9uIGluIHB5dGhvbl92ZXJzaW9uX3JlcXVpcmVtZW50cwogICAgICAgICAgICBdCiAgICAgICAgZXhjZXB0IChLZXlFcnJvciwgVHlwZUVycm9yLCBGaWxlTm90Rm91bmRFcnJvcikgYXMgZXJyb3I6CiAgICAgICAgICAgIHdhcm5pbmdzLndhcm4oc3RyKGVycm9yKSkKICAgICAgICAgICAgd2FybmluZ3Mud2FybigiZmFsbGluZyBiYWNrIHRvIGN1cnJlbnQgUHl0aG9uIHZlcnNpb25zLi4uIikKICAgICAgICAgICAgc3VwcG9ydGVkX3ZlcnNpb25zID0gY3VycmVudF9weXRob25fdmVyc2lvbnMKCiAgICByZXR1cm4gc29ydGVkKHN1cHBvcnRlZF92ZXJzaW9ucykKCgpkZWYgZ2V0X2N1cnJlbnRfcHl0aG9uX3ZlcnNpb25zKG5vX2VvYXM6IGJvb2wgPSBGYWxzZSkgLT4gbGlzdFtWZXJzaW9uXToKICAgIHVybCA9ICJodHRwczovL2VuZG9mbGlmZS5kYXRlL2FwaS92MS9wcm9kdWN0cy9weXRob24iCiAgICByZXNwb25zZSA9IHJlcXVlc3RzLmdldCgiaHR0cHM6Ly9lbmRvZmxpZmUuZGF0ZS9hcGkvdjEvcHJvZHVjdHMvcHl0aG9uIikKICAgIGlmIHJlc3BvbnNlLnN0YXR1c19jb2RlID09IDIwMDoKICAgICAgICByZXR1cm4gWwogICAgICAgICAgICBWZXJzaW9uKHB5dGhvbl92ZXJzaW9uWyJuYW1lIl0pCiAgICAgICAgICAgIGZvciBweXRob25fdmVyc2lvbiBpbiByZXNwb25zZS5qc29uKClbInJlc3VsdCJdWyJyZWxlYXNlcyJdCiAgICAgICAgICAgIGlmIG5vdCBweXRob25fdmVyc2lvblsiaXNFb2FzIiBpZiBub19lb2FzIGVsc2UgImlzRW9sIl0KICAgICAgICBdCiAgICBlbHNlOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoZiJyZXF1ZXN0IHRvIHt1cmx9IHJldHVybmVkIHN0YXR1cyBjb2RlIHtyZXNwb25zZS5zdGF0dXNfY29kZX0iKQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBsb2FkX3RveF90YXJnZXRzKCkK - run: cat tox_matrix.py - id: set-outputs run: | # zizmor: ignore[template-injection] uv run tox_matrix.py \ - --envs "${{ !inputs.fill && inputs.envs || format('{0}\n{1}', inputs.envs, steps.supported-pythons.outputs.envs) }}" \ + --envs "${{ inputs.envs }}" \ --libraries "${{ inputs.libraries }}" \ --posargs "${{ inputs.posargs }}" --toxdeps "${{ inputs.toxdeps }}" \ --toxargs "${{ inputs.toxargs }}" --pytest "${{ inputs.pytest }}" \ diff --git a/docs/source/tox.rst b/docs/source/tox.rst index 73e1340..25a3270 100644 --- a/docs/source/tox.rst +++ b/docs/source/tox.rst @@ -86,6 +86,13 @@ environment. If the Python version includes a ``t`` suffix, such as ``py313t``, then a free-threaded Python interpreter will be used. +Additionally, the Python version can include glob syntax (i.e. `py3*`) to expand into all supported versions of Python (respecting ``project.requires-python`` in ``pyproject.toml`` as conforming with PEP621). + +For example: +- ``py*`` for all supported versions of Python (from https://endoflife.date) +- ``py3*`` for all supported minor versions of Python 3 +- ``py31*`` for all supported versions of Python between ``3.10`` and ``3.20`` + libraries ^^^^^^^^^ @@ -544,71 +551,6 @@ same repository, or when using a non-standard project layout. envs: | - linux: py312 -fill -^^^^ - -Automatically add tox environments for each Python version currently supported -by your package. The supported versions are determined by reading the -``requires-python`` field from your package's ``pyproject.toml`` file -(conforming to PEP 621) and cross-referencing with currently maintained Python -versions from https://endoflife.date. - -Default is ``false``. - -.. code:: yaml - - uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v2 - with: - fill: true - envs: | - - linux: pep8 - pytest: false - -In the above example, if your package's ``pyproject.toml`` specifies -``requires-python = ">=3.10"``, and Python 3.10, 3.11, 3.12, and 3.13 are -currently maintained, the workflow will automatically add ``py310``, ``py311``, -``py312``, and ``py313`` environments on Linux in addition to the ``pep8`` -environment. - -fill_platforms -^^^^^^^^^^^^^^ - -Platforms to use when generating environments with ``fill``. This is a -comma-separated list of platforms. Default is ``linux`` only. - -.. code:: yaml - - uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v2 - with: - fill: true - fill_platforms: linux,macos,windows - envs: | - - linux: pep8 - pytest: false - -This will create tox environments for each supported Python version on all -three platforms. - -fill_factors -^^^^^^^^^^^^ - -Tox factors to add to the automatically generated environments from ``fill``. -This is a comma-separated list of factors. Default is none. - -.. code:: yaml - - uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v2 - with: - fill: true - fill_factors: test,cov - envs: | - - linux: pep8 - pytest: false - -If your package supports Python 3.11 and 3.12, this will generate environments -like ``py311-test-cov`` and ``py312-test-cov`` instead of just ``py311`` and -``py312``. - Secrets ~~~~~~~ diff --git a/tools/supported_pythons.py b/tools/supported_pythons.py deleted file mode 100644 index b01b5a2..0000000 --- a/tools/supported_pythons.py +++ /dev/null @@ -1,123 +0,0 @@ -# /// script -# requires-python = ">=3.12" -# dependencies = [ -# "click==8.2.1", -# "packaging==25.0", -# "requests==2.32.5", -# "tomli==2.4.0", -# ] -# /// -import os -import warnings -from pathlib import Path - -import click -import requests -import tomli -from packaging.specifiers import SpecifierSet -from packaging.version import Version - - -@click.command() -@click.option("--package-source", default=None) -@click.option("--factors", default=None) -@click.option("--no-eoas", is_flag=True, default=False) -@click.option("--platforms", default=None) -def supported_python_envs_block( - package_source: Path = None, - factors: list[str] = None, - no_eoas: bool = False, - platforms: list[str] = None, -): - """enumerate toxenvs for each Python version supported by package""" - - if platforms is None: - platforms = ["linux"] - elif isinstance(platforms, str): - platforms = platforms.split(",") - - toxenvs = supported_python_toxenvs(package_source, factors, no_eoas) - envs_block = "\\n".join( - f"- {platform}: {toxenv}" for platform in platforms for toxenv in toxenvs - ) - - print(envs_block) - with open(os.environ["GITHUB_OUTPUT"], "a") as f: - f.write(f"envs={envs_block}\n") - - -def supported_python_toxenvs( - package_source: Path = None, - factors: list[str] = None, - no_eoas: bool = False, -) -> list[str]: - if isinstance(factors, str): - factors = factors.split(",") - - return [ - f"py{str(python_version).replace('.', '')}{'-' + '-'.join(factors) if factors is not None and len(factors) > 0 else ''}" - for python_version in supported_pythons(package_source, no_eoas=no_eoas) - ] - - -def supported_pythons( - package_source: Path = None, - no_eoas: bool = False, -) -> list[Version]: - current_python_versions = current_pythons(no_eoas=no_eoas) - - if not package_source: - supported_versions = current_python_versions - else: - try: - pyproject_toml_filename = Path(package_source) / "pyproject.toml" - if pyproject_toml_filename.exists(): - with open(pyproject_toml_filename, "rb") as pyproject_toml_file: - pyproject_toml = tomli.load(pyproject_toml_file) - if "project" in pyproject_toml: - project_metadata = pyproject_toml["project"] - if "requires-python" in project_metadata: - python_version_requirements = SpecifierSet( - project_metadata["requires-python"] - ) - else: - raise KeyError( - "`project.requires-python` not found in `pyproject.toml`; ensure your package conforms to PEP621" - ) - else: - raise KeyError( - "`project` not found in `pyproject.toml`; ensure your package conforms to PEP621" - ) - else: - raise FileNotFoundError( - "could not find `pyproject.toml` in the provided package source; ensure your package conforms to PEP621" - ) - - supported_versions = [ - python_version - for python_version in current_python_versions - if python_version in python_version_requirements - ] - except (KeyError, TypeError, FileNotFoundError) as error: - warnings.warn(str(error)) - warnings.warn("falling back to current Python versions...") - supported_versions = current_python_versions - - return supported_versions - - -def current_pythons(no_eoas: bool = False) -> list[Version]: - url = "https://endoflife.date/api/v1/products/python" - response = requests.get("https://endoflife.date/api/v1/products/python") - if response.status_code == 200: - return [ - Version(python_version["name"]) - for python_version in response.json()["result"]["releases"] - if not python_version["isEoas" if no_eoas else "isEol"] - ] - else: - raise ValueError(f"request to {url} returned status code {response.status_code}") - - -if __name__ == "__main__": - supported_python_envs_block() diff --git a/tools/tox_matrix.py b/tools/tox_matrix.py index 9ceed1a..ba82531 100644 --- a/tools/tox_matrix.py +++ b/tools/tox_matrix.py @@ -2,16 +2,25 @@ # requires-python = "==3.12" # dependencies = [ # "click==8.2.1", +# "packaging==25.0", # "pyyaml==6.0.2", +# "requests==2.32.5", +# "tomli==2.4.0", # ] # /// import json import os import re import warnings +from copy import copy +from pathlib import Path import click +import requests +import tomli import yaml +from packaging.specifiers import SpecifierSet +from packaging.version import Version @click.command() @@ -111,16 +120,30 @@ def load_tox_targets( # Create matrix matrix = {"include": []} for env in envs: - matrix["include"].append( - get_matrix_item( - env, - global_libraries=global_libraries, - global_string_parameters=string_parameters, - runs_on=default_runs_on, - default_python=default_python, - ) + matrix_item = get_matrix_item( + env, + global_libraries=global_libraries, + global_string_parameters=string_parameters, + runs_on=default_runs_on, + default_python=default_python, ) + # check if we need to expand python versions from a glob (i.e. py*, py3*, py31*, etc.) + toxenv = matrix_item["toxenv"] + if toxenv.startswith("py") and "*" in toxenv.split("-")[0]: + toxenvs = expand_python_versions(toxenv) + + for expanded_toxenv, python_version in toxenvs: + expanded_matrix_item = copy(matrix_item) + expanded_matrix_item["toxenv"] = expanded_toxenv + expanded_matrix_item["name"] = expanded_matrix_item["name"].replace( + toxenv, expanded_toxenv + ) + expanded_matrix_item["python_version"] = python_version + matrix["include"].append(expanded_matrix_item) + else: + matrix["include"].append(matrix_item) + # Output matrix print(json.dumps(matrix, indent=2)) with open(os.environ["GITHUB_OUTPUT"], "a") as f: @@ -188,9 +211,7 @@ def get_matrix_item(env, global_libraries, global_string_parameters, runs_on, de # Note that we don't include --cov here as if it's provided to pytest twice it breaks cov reporting. # Lots of users of this specify --cov in their tox.ini so it's been removed for backwards compatibility. # https://github.com/OpenAstronomy/github-actions-workflows/issues/383 - item["pytest_flag"] += ( - rf"--cov-report=xml:${{GITHUB_WORKSPACE}}{sep}coverage.xml " - ) + item["pytest_flag"] += rf"--cov-report=xml:${{GITHUB_WORKSPACE}}{sep}coverage.xml " if item["pytest-results-summary"] == "true": item["pytest_flag"] += rf"--junitxml ${{GITHUB_WORKSPACE}}{sep}results.xml " @@ -225,5 +246,115 @@ def get_matrix_item(env, global_libraries, global_string_parameters, runs_on, de return item +def expand_python_versions(toxenv: str) -> list[(str, str)]: + """ + expand `py3*` into `py311`, `py312`, `py313`, etc. based on currently-supported Python versions + + :param version_glob: can be `py*`, `py3*`, `py30*`, `py31*` etc. + """ + + toxenv_factors = toxenv.split("-") + py_version_glob = toxenv_factors[0] + if not py_version_glob.startswith("py"): + raise ValueError( + f'input "{py_version_glob}" is not a Python version Tox factor (must start with `py`)' + ) + + if "*" not in py_version_glob: + return [py_version_glob] + + if not py_version_glob.endswith("*"): + raise NotImplementedError( + "Python version glob must end with a `*`; suffixes such as `t` are not yet supported" + ) + + python_versions = get_supported_python_versions(package_source=".") + + major_version = py_version_glob[2] + if major_version != "*": + python_versions = [ + python_version + for python_version in python_versions + if python_version.major == int(major_version) + ] + + minor_version = py_version_glob[3:] + if minor_version_specifier := minor_version.split("*")[0] != "*": + minor_version_base = int(minor_version_specifier) * 10 + python_versions = [ + python_version + for python_version in python_versions + if minor_version_base <= python_version.minor < minor_version_base + 10 + ] + + return [ + ( + f"py{python_version.major}{python_version.minor}" + + (f"-{'-'.join(toxenv_factors[1:])}" if len(toxenv_factors) > 1 else ""), + str(python_version), + ) + for python_version in python_versions + ] + + +def get_supported_python_versions( + package_source: Path = None, + no_eoas: bool = False, +) -> list[Version]: + current_python_versions = get_current_python_versions(no_eoas=no_eoas) + + if not package_source: + supported_versions = current_python_versions + else: + try: + pyproject_toml_filename = Path(package_source) / "pyproject.toml" + if pyproject_toml_filename.exists(): + with open(pyproject_toml_filename, "rb") as pyproject_toml_file: + pyproject_toml = tomli.load(pyproject_toml_file) + if "project" in pyproject_toml: + project_metadata = pyproject_toml["project"] + if "requires-python" in project_metadata: + python_version_requirements = SpecifierSet( + project_metadata["requires-python"] + ) + else: + raise KeyError( + "`project.requires-python` not found in `pyproject.toml`; ensure your package conforms to PEP621" + ) + else: + raise KeyError( + "`project` not found in `pyproject.toml`; ensure your package conforms to PEP621" + ) + else: + raise FileNotFoundError( + "could not find `pyproject.toml` in the provided package source; ensure your package conforms to PEP621" + ) + + supported_versions = [ + python_version + for python_version in current_python_versions + if python_version in python_version_requirements + ] + except (KeyError, TypeError, FileNotFoundError) as error: + warnings.warn(str(error)) + warnings.warn("falling back to current Python versions...") + supported_versions = current_python_versions + + return sorted(supported_versions) + + +def get_current_python_versions(no_eoas: bool = False) -> list[Version]: + url = "https://endoflife.date/api/v1/products/python" + response = requests.get("https://endoflife.date/api/v1/products/python") + if response.status_code == 200: + return [ + Version(python_version["name"]) + for python_version in response.json()["result"]["releases"] + if not python_version["isEoas" if no_eoas else "isEol"] + ] + else: + raise ValueError(f"request to {url} returned status code {response.status_code}") + + if __name__ == "__main__": load_tox_targets() diff --git a/update_scripts_in_yml.py b/update_scripts_in_yml.py index fcc8c39..ec45756 100755 --- a/update_scripts_in_yml.py +++ b/update_scripts_in_yml.py @@ -34,4 +34,3 @@ def base64_encode_into(script, yml_file, env_var): base64_encode_into('set_env.py', 'tox.yml', 'SET_ENV_SCRIPT') base64_encode_into('set_env.py', 'publish.yml', 'SET_ENV_SCRIPT') base64_encode_into('set_env.py', 'publish_pure_python.yml', 'SET_ENV_SCRIPT') -base64_encode_into('supported_pythons.py', 'tox.yml', 'SUPPORTED_PYTHONS_SCRIPT')