From afc7722a0b3d5c66110e8cb5bfb115a89471eb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Fri, 15 Nov 2019 15:48:02 +0100 Subject: [PATCH 01/18] Add eachdist.py to simplify build. --- .flake8 | 2 + .gitignore | 1 + .isort.cfg | 2 +- CONTRIBUTING.md | 9 +- dev-requirements.txt | 10 + eachdist.ini | 13 + .../tests/__init__.py | 0 pyproject.toml | 7 +- scripts/eachdist.py | 330 ++++++++++++++++++ tox.ini | 70 +--- 10 files changed, 385 insertions(+), 59 deletions(-) create mode 100644 dev-requirements.txt create mode 100644 eachdist.ini delete mode 100644 ext/opentelemetry-ext-azure-monitor/tests/__init__.py create mode 100644 scripts/eachdist.py diff --git a/.flake8 b/.flake8 index a0924f947d6..13a8b4e71e1 100644 --- a/.flake8 +++ b/.flake8 @@ -10,6 +10,8 @@ exclude = .svn .tox CVS + .venv*/ + venv*/ target __pycache__ ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/gen/ diff --git a/.gitignore b/.gitignore index 473ef20a4a8..42e6d0bf043 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ lib lib64 __pycache__ venv*/ +.venv*/ # Installer logs pip-log.txt diff --git a/.isort.cfg b/.isort.cfg index 96011ae93de..aeca31213c4 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -13,4 +13,4 @@ line_length=79 ; docs: https://github.com/timothycrosley/isort#multi-line-output-modes multi_line_output=3 skip=target -skip_glob=ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/gen/* +skip_glob=ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/gen/*,.venv*/*,venv*/* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 74cc3458742..d0fa6db97d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,14 @@ during their normal contribution hours. ## Development -This project uses [`tox`](https://tox.readthedocs.io) to automate some aspects +To quickly get up and running, you can use the `scripts/eachdist.py` tool that +ships with this project. First create a virtualenv and activate it. +Then run `python scripts/eachdist.py develop` to install all required packages +as well as the project's packages themselves (in `--editable` mode). +You can then run `scripts/eachdist.py test` to test everything or +`scripts/eachdist.py lint` to lint everything (fixing anything that is auto-fixable). + +Additionally, this project uses [`tox`](https://tox.readthedocs.io) to automate some aspects of development, including testing against multiple Python versions. You can run: diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 00000000000..a27df1ff9a5 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,10 @@ +pylint~=2.3 +flake8~=3.7 +isort~=4.3 +black>=19.3b0,==19.* +mypy~=0.740 +sphinx~=2.1 +sphinx-rtd-theme~=0.4 +sphinx-autodoc-typehints~=1.10.2 +#pytest>=5.2 # Not available for Python 3.4, so just use latest +pytest-cov>=2.8 \ No newline at end of file diff --git a/eachdist.ini b/eachdist.ini new file mode 100644 index 00000000000..00ccdc2168e --- /dev/null +++ b/eachdist.ini @@ -0,0 +1,13 @@ +# These will be sorted first in that order. +# All packages that are depended upon by others should be listed here. +[DEFAULT] +sortfirst= + opentelemetry-api + opentelemetry-sdk + ext/* + +[lintroots] +subglob=*.py,tests/,test/,src/* + +[testroots] +subglob=tests/,test/ \ No newline at end of file diff --git a/ext/opentelemetry-ext-azure-monitor/tests/__init__.py b/ext/opentelemetry-ext-azure-monitor/tests/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/pyproject.toml b/pyproject.toml index cb3c2eb5467..bcbe4f75115 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,16 +8,13 @@ exclude = ''' | \.hg | \.mypy_cache | \.tox - | \.venv + | \.venv.* + | venv.* | \.vscode | _build - | buck-out - | target | build | dist | ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/gen # generated files )/ - | foo.py # also separately exclude a file named foo.py in - # the root of the project ) ''' diff --git a/scripts/eachdist.py b/scripts/eachdist.py new file mode 100644 index 00000000000..aea08f2c8c9 --- /dev/null +++ b/scripts/eachdist.py @@ -0,0 +1,330 @@ +#!/usr/bin/env python3 + +import argparse +import shlex +import shutil +import subprocess +import sys +from configparser import ConfigParser +from itertools import chain +from pathlib import Path + +DEFAULT_ALLSEP = " " +DEFAULT_ALLFMT = "{rel}" + + +def parse_args(args=None): + parser = argparse.ArgumentParser( + description="Do something for each or all distributions in the repository." + ) + parser.set_defaults(parser=parser) + parser.add_argument("--dry-run", action="store_true") + subparsers = parser.add_subparsers() + + excparser = subparsers.add_parser( + "exec", help="Run an executable for each or all distributions." + ) + excparser.set_defaults(func=execute_args) + excparser.add_argument("format") + excparser.add_argument("--all", nargs="?", const=DEFAULT_ALLFMT) + excparser.add_argument("--allsep") + excparser.add_argument( + "--allowexitcode", type=int, action="append", default=[0] + ) + excparser.add_argument( + "--mode", + "-m", + default="DEFAULT", + help="Section of config file to use for target selection configuration.", + ) + + instparser = subparsers.add_parser( + "install", help="Install all distributions." + ) + + def setup_instparser(instparser): + instparser.set_defaults(func=install_args) + instparser.add_argument("pipargs", nargs=argparse.REMAINDER) + + setup_instparser(instparser) + instparser.add_argument("--editable", "-e", action="store_true") + instparser.add_argument("--with-dev-deps", action="store_true") + + devparser = subparsers.add_parser( + "develop", + help="Install all distributions editable + dev dependencies.", + ) + setup_instparser(devparser) + devparser.set_defaults(editable=True, with_dev_deps=True) + + lintparser = subparsers.add_parser( + "lint", help="Lint everything, autofixing if possible." + ) + lintparser.add_argument("--check-only", action="store_true") + lintparser.set_defaults(func=lint_args) + + testparser = subparsers.add_parser( + "test", + help="Test everything (run pytest yourself for more complex operations).", + ) + testparser.set_defaults(func=test_args) + testparser.add_argument("pytestargs", nargs=argparse.REMAINDER) + + return parser.parse_args(args) + + +def find_projectroot(search_start=Path(".")): + root = search_start.resolve() + for root in chain((root,), root.parents): + if any((root / marker).exists() for marker in (".git", "tox.ini")): + return root + return None + + +def find_targets_unordered(rootpath): + for subdir in rootpath.iterdir(): + if not subdir.is_dir(): + continue + if subdir.name.startswith(".") or subdir.name.startswith("venv"): + continue + if any( + (subdir / marker).exists() + for marker in ("setup.py", "pyproject.toml") + ): + yield subdir + else: + yield from find_targets_unordered(subdir) + + +def getlistcfg(strval): + return [ + val.strip() + for line in strval.split("\n") + for val in line.split(",") + if val.strip() + ] + + +def find_targets(mode, rootpath): + if not rootpath: + sys.exit("Could not find a root directory.") + + cfg = ConfigParser() + cfg.read(str(rootpath / "eachdist.ini")) + mcfg = cfg[mode] + + targets = list(find_targets_unordered(rootpath)) + if "sortfirst" in mcfg: + sortfirst = getlistcfg(mcfg["sortfirst"]) + + def keyfunc(path): + path = path.relative_to(rootpath) + for i, pattern in enumerate(sortfirst): + if path.match(pattern): + return i + return float("inf") + + targets.sort(key=keyfunc) + + subglobs = getlistcfg(mcfg.get("subglob", "")) + if subglobs: + targets = [ + newentry + for newentry in ( + target / subdir + for target in targets + for subglob in subglobs + # We need to special-case the dot, because glob fails to parse that with an IndexError. + for subdir in ( + (target,) if subglob == "." else target.glob(subglob) + ) + ) + if ".egg-info" not in str(newentry) and newentry.exists() + ] + + return targets + + +def runsubprocess(dry_run, params, *args, **kwargs): + cmdstr = join_args(params) + if dry_run: + print(cmdstr) + return None + + print(">>>", cmdstr, file=sys.stderr) + + # This is a workaround for subprocess.run(['python']) leaving the virtualenv on Win32. + # The cause for this is that when running the python.exe in a virtualenv, + # the wrapper executable launches the global python as a subprocess and the search sequence + # for CreateProcessW which subprocess.run and Popen use is a follows + # (https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw): + # > 1. The directory from which the application loaded. + # This will be the directory of the global python.exe, not the venv directory, due to the suprocess mechanism. + # > 6. The directories that are listed in the PATH environment variable. + # Only this would find the "correct" python.exe. + + params = list(params) + executable = shutil.which(params[0]) # On Win32, pytho + if executable: + params[0] = executable + try: + return subprocess.run(params, *args, **kwargs) + except OSError as exc: + raise ValueError( + "Failed executing " + repr(params) + ": " + str(exc) + ) from exc + + +def execute_args(args): + if args.allsep and not args.all: + args.parser.error("--allsep specified but not --all.") + + if args.all and not args.allsep: + args.allsep = DEFAULT_ALLSEP + + rootpath = find_projectroot() + targets = find_targets(args.mode, rootpath) + if not targets: + sys.exit("Error: No targets selected (root: {})".format(rootpath)) + + def fmt_for_path(fmt, path): + return fmt.format( + path.as_posix(), + rel=path.relative_to(rootpath).as_posix(), + raw=path, + rawrel=path.relative_to(rootpath), + ) + + def _runcmd(cmd): + result = runsubprocess( + args.dry_run, shlex.split(cmd), cwd=rootpath, check=False + ) + if result is not None and result.returncode not in args.allowexitcode: + print( + "'{}' failed with code {}".format(cmd, result.returncode), + file=sys.stderr, + ) + sys.exit(result.returncode) + + if args.all: + allstr = args.allsep.join( + fmt_for_path(args.all, path) for path in targets + ) + cmd = args.format.format(allstr) + _runcmd(cmd) + else: + for target in targets: + cmd = fmt_for_path(args.format, target) + _runcmd(cmd) + + +def clean_remainder_args(remainder_args): + if remainder_args and remainder_args[0] == "--": + del remainder_args[0] + + +def join_args(arglist): + return " ".join(map(shlex.quote, arglist)) + + +def install_args(args): + clean_remainder_args(args.pipargs) + + if args.with_dev_deps: + runsubprocess( + args.dry_run, + [ + "python", + "-m", + "pip", + "install", + "--upgrade", + "pip", + "setuptools", + "wheel", + ] + + args.pipargs, + check=True, + ) + + allfmt = "-e 'file://{}'" if args.editable else "'file://{}'" + execute_args( + parse_subargs( + args, + ( + "exec", + "python -m pip install {} " + join_args(args.pipargs), + "--all", + allfmt, + ), + ) + ) + if args.with_dev_deps: + rootpath = find_projectroot() + runsubprocess( + args.dry_run, + [ + "python", + "-m", + "pip", + "install", + "--upgrade", + "-r", + str(rootpath / "dev-requirements.txt"), + ] + + args.pipargs, + check=True, + ) + + +def parse_subargs(parentargs, args): + subargs = parse_args(args) + subargs.dry_run = parentargs.dry_run or subargs.dry_run + return subargs + + +def lint_args(args): + rootdir = str(find_projectroot()) + + runsubprocess( + args.dry_run, + ("black", rootdir) + + (("--diff", "--check") if args.check_only else ()), + check=True, + ) + runsubprocess( + args.dry_run, + ("isort", "--recursive", rootdir) + + (("--diff", "--check-only") if args.check_only else ()), + check=True, + ) + runsubprocess(args.dry_run, ("flake8", rootdir), check=True) + execute_args( + parse_subargs( + args, ("exec", "pylint {}", "--all", "--mode", "lintroots",), + ) + ) + + +def test_args(args): + clean_remainder_args(args.pytestargs) + execute_args( + parse_subargs( + args, + ( + "exec", + "pytest {} " + join_args(args.pytestargs), + "--mode", + "testroots", + ), + ) + ) + + +def main(): + args = parse_args() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/tox.ini b/tox.ini index e0e076fe77d..b23d8ffe237 100644 --- a/tox.ini +++ b/tox.ini @@ -20,9 +20,10 @@ python = [testenv] deps = + -c dev-requirements.txt test: pytest coverage: pytest-cov - mypy,mypyinstalled: mypy~=0.740 + mypy,mypyinstalled: mypy setenv = mypy: MYPYPATH={toxinidir}/opentelemetry-api/src/ @@ -56,14 +57,7 @@ commands_pre = ; In order to get a healthy coverage report, ; we have to install packages in editable mode. - coverage: pip install -e {toxinidir}/opentelemetry-api - coverage: pip install -e {toxinidir}/opentelemetry-sdk - coverage: pip install -e {toxinidir}/ext/opentelemetry-ext-azure-monitor - coverage: pip install -e {toxinidir}/ext/opentelemetry-ext-http-requests - coverage: pip install -e {toxinidir}/ext/opentelemetry-ext-jaeger - coverage: pip install -e {toxinidir}/ext/opentelemetry-ext-opentracing-shim - coverage: pip install -e {toxinidir}/ext/opentelemetry-ext-wsgi - coverage: pip install -e {toxinidir}/examples/opentelemetry-example-app + coverage: scripts/eachdist.py install --editable ; Using file:// here because otherwise tox invokes just "pip install ; opentelemetry-api", leading to an error @@ -85,52 +79,24 @@ commands = [testenv:lint] basepython: python3.7 deps = - pylint~=2.3 - flake8~=3.7 - isort~=4.3 - black>=19.3b0,==19.* + -c dev-requirements.txt + pylint + flake8 + isort + black commands_pre = - pip install -e {toxinidir}/opentelemetry-api - pip install -e {toxinidir}/opentelemetry-sdk - pip install -e {toxinidir}/ext/opentelemetry-ext-azure-monitor - pip install -e {toxinidir}/ext/opentelemetry-ext-http-requests - pip install -e {toxinidir}/ext/opentelemetry-ext-jaeger - pip install -e {toxinidir}/ext/opentelemetry-ext-pymongo - pip install -e {toxinidir}/ext/opentelemetry-ext-wsgi - pip install -e {toxinidir}/examples/opentelemetry-example-app - pip install -e {toxinidir}/ext/opentelemetry-ext-opentracing-shim + python scripts/eachdist.py install --editable commands = -; Prefer putting everything in one pylint command to profit from duplication -; warnings. - black --check --diff . - pylint opentelemetry-api/src/opentelemetry \ - opentelemetry-api/tests/ \ - opentelemetry-sdk/src/opentelemetry \ - opentelemetry-sdk/tests/ \ - ext/opentelemetry-ext-azure-monitor/examples/ \ - ext/opentelemetry-ext-azure-monitor/src/ \ - ext/opentelemetry-ext-azure-monitor/tests/ \ - ext/opentelemetry-ext-http-requests/src/ \ - ext/opentelemetry-ext-http-requests/tests/ \ - ext/opentelemetry-ext-jaeger/src/opentelemetry \ - ext/opentelemetry-ext-jaeger/tests/ \ - ext/opentelemetry-ext-opentracing-shim/src/ \ - ext/opentelemetry-ext-opentracing-shim/tests/ \ - ext/opentelemetry-ext-pymongo/src/opentelemetry \ - ext/opentelemetry-ext-pymongo/tests/ \ - ext/opentelemetry-ext-wsgi/tests/ \ - examples/opentelemetry-example-app/src/opentelemetry_example_app/ \ - examples/opentelemetry-example-app/tests/ - flake8 . - isort --check-only --diff --recursive . + python scripts/eachdist.py lint --check-only [testenv:docs] deps = - sphinx~=2.1 - sphinx-rtd-theme~=0.4 - sphinx-autodoc-typehints~=1.10.2 + -c dev-requirements.txt + sphinx + sphinx-rtd-theme + sphinx-autodoc-typehints changedir = docs @@ -147,10 +113,10 @@ deps = requests~=2.7 commands_pre = - pip install -e {toxinidir}/opentelemetry-api - pip install -e {toxinidir}/opentelemetry-sdk - pip install -e {toxinidir}/ext/opentelemetry-ext-http-requests - pip install -e {toxinidir}/ext/opentelemetry-ext-wsgi + pip install -e {toxinidir}/opentelemetry-api \ + -e {toxinidir}/opentelemetry-sdk \ + -e {toxinidir}/ext/opentelemetry-ext-http-requests \ + -e {toxinidir}/ext/opentelemetry-ext-wsgi commands = {toxinidir}/scripts/tracecontext-integration-test.sh From 56f2fb368dfdc290db2739833044a5c9be4cb5ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Fri, 15 Nov 2019 16:04:59 +0100 Subject: [PATCH 02/18] Fix coverage build. --- dev-requirements.txt | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index a27df1ff9a5..16d7d09e463 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -6,5 +6,5 @@ mypy~=0.740 sphinx~=2.1 sphinx-rtd-theme~=0.4 sphinx-autodoc-typehints~=1.10.2 -#pytest>=5.2 # Not available for Python 3.4, so just use latest +pytest!=5.2.3 pytest-cov>=2.8 \ No newline at end of file diff --git a/tox.ini b/tox.ini index b23d8ffe237..d4544b81619 100644 --- a/tox.ini +++ b/tox.ini @@ -57,7 +57,7 @@ commands_pre = ; In order to get a healthy coverage report, ; we have to install packages in editable mode. - coverage: scripts/eachdist.py install --editable + coverage: {toxinidir}/scripts/eachdist.py install --editable ; Using file:// here because otherwise tox invokes just "pip install ; opentelemetry-api", leading to an error From 4efd2af920f4b5fc9f5edf893066425c81c03989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Fri, 15 Nov 2019 16:13:40 +0100 Subject: [PATCH 03/18] Mark eachdist.py executable. --- scripts/eachdist.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/eachdist.py diff --git a/scripts/eachdist.py b/scripts/eachdist.py old mode 100644 new mode 100755 From e5dcf6310447da9c14317f4344196242bbeac158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Fri, 15 Nov 2019 16:45:38 +0100 Subject: [PATCH 04/18] Fix eachdist.py for Python 3.4 & 3.5 --- scripts/eachdist.py | 25 +++++++++++++++++++++++-- tox.ini | 2 +- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/scripts/eachdist.py b/scripts/eachdist.py index aea08f2c8c9..6cfee1ce3f1 100755 --- a/scripts/eachdist.py +++ b/scripts/eachdist.py @@ -5,13 +5,29 @@ import shutil import subprocess import sys +from collections import namedtuple from configparser import ConfigParser from itertools import chain -from pathlib import Path +from pathlib import Path, PurePath DEFAULT_ALLSEP = " " DEFAULT_ALLFMT = "{rel}" +try: + subprocess_run = subprocess.run +except AttributeError: # Py < 3.5 compat + CompletedProcess = namedtuple("CompletedProcess", "returncode") + + def subprocess_run(*args, **kwargs): + check = kwargs.pop("check", False) + if check: + subprocess.check_call(*args, **kwargs) + return CompletedProcess(returncode=0) + else: + return CompletedProcess( + returncode=subprocess.call(*args, **kwargs) + ) + def parse_args(args=None): parser = argparse.ArgumentParser( @@ -151,6 +167,11 @@ def runsubprocess(dry_run, params, *args, **kwargs): print(cmdstr) return None + # Py < 3.6 compat. + cwd = kwargs.get("cwd") + if cwd and isinstance(cwd, PurePath): + kwargs["cwd"] = str(cwd) + print(">>>", cmdstr, file=sys.stderr) # This is a workaround for subprocess.run(['python']) leaving the virtualenv on Win32. @@ -168,7 +189,7 @@ def runsubprocess(dry_run, params, *args, **kwargs): if executable: params[0] = executable try: - return subprocess.run(params, *args, **kwargs) + return subprocess_run(params, *args, **kwargs) except OSError as exc: raise ValueError( "Failed executing " + repr(params) + ": " + str(exc) diff --git a/tox.ini b/tox.ini index d4544b81619..d595797278c 100644 --- a/tox.ini +++ b/tox.ini @@ -57,7 +57,7 @@ commands_pre = ; In order to get a healthy coverage report, ; we have to install packages in editable mode. - coverage: {toxinidir}/scripts/eachdist.py install --editable + coverage: python {toxinidir}/scripts/eachdist.py install --editable ; Using file:// here because otherwise tox invokes just "pip install ; opentelemetry-api", leading to an error From c38b2fbb201e389b7c3ffcab711866f20c9e65d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Fri, 15 Nov 2019 16:58:59 +0100 Subject: [PATCH 05/18] Fix lint. --- scripts/eachdist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/eachdist.py b/scripts/eachdist.py index 6cfee1ce3f1..16a442348ba 100755 --- a/scripts/eachdist.py +++ b/scripts/eachdist.py @@ -15,7 +15,7 @@ try: subprocess_run = subprocess.run -except AttributeError: # Py < 3.5 compat +except AttributeError: # Py < 3.5 compat CompletedProcess = namedtuple("CompletedProcess", "returncode") def subprocess_run(*args, **kwargs): From e62f5bcf37b3e888754ffb72357769c1b7c67487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Fri, 15 Nov 2019 17:13:20 +0100 Subject: [PATCH 06/18] Fix linting missing files. Excluding `build` for black isn't a great idea when travis is checking out under `/home/travis/build/open-telemetry`. --- pyproject.toml | 2 -- scripts/eachdist.py | 16 ++++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bcbe4f75115..cb0d3b8bbd6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,8 +11,6 @@ exclude = ''' | \.venv.* | venv.* | \.vscode - | _build - | build | dist | ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/gen # generated files )/ diff --git a/scripts/eachdist.py b/scripts/eachdist.py index 16a442348ba..c13699b6bba 100755 --- a/scripts/eachdist.py +++ b/scripts/eachdist.py @@ -23,10 +23,7 @@ def subprocess_run(*args, **kwargs): if check: subprocess.check_call(*args, **kwargs) return CompletedProcess(returncode=0) - else: - return CompletedProcess( - returncode=subprocess.call(*args, **kwargs) - ) + return CompletedProcess(returncode=subprocess.call(*args, **kwargs)) def parse_args(args=None): @@ -135,9 +132,9 @@ def find_targets(mode, rootpath): def keyfunc(path): path = path.relative_to(rootpath) - for i, pattern in enumerate(sortfirst): + for idx, pattern in enumerate(sortfirst): if path.match(pattern): - return i + return idx return float("inf") targets.sort(key=keyfunc) @@ -172,6 +169,8 @@ def runsubprocess(dry_run, params, *args, **kwargs): if cwd and isinstance(cwd, PurePath): kwargs["cwd"] = str(cwd) + check = kwargs.pop("check") # Enforce specifying check + print(">>>", cmdstr, file=sys.stderr) # This is a workaround for subprocess.run(['python']) leaving the virtualenv on Win32. @@ -189,7 +188,7 @@ def runsubprocess(dry_run, params, *args, **kwargs): if executable: params[0] = executable try: - return subprocess_run(params, *args, **kwargs) + return subprocess_run(params, *args, check=check, **kwargs) except OSError as exc: raise ValueError( "Failed executing " + repr(params) + ": " + str(exc) @@ -322,7 +321,8 @@ def lint_args(args): runsubprocess(args.dry_run, ("flake8", rootdir), check=True) execute_args( parse_subargs( - args, ("exec", "pylint {}", "--all", "--mode", "lintroots",), + args, + ("exec", "pylint scripts/ {}", "--all", "--mode", "lintroots",), ) ) From 2524efee1f43f0777198972bfdf82fbc1d506e44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Fri, 15 Nov 2019 17:31:35 +0100 Subject: [PATCH 07/18] Fix black ignoring files on travis. First, black already parsers .gitignore, so no need for the exclude option in pyproject.toml (apart from checked in generated files). The problem with this is that .gitignore has `build/` in it already and black does not take into account the root of the git repository, but instead seems to apply the ignore patterns to the path that results from the command line arguments (be that relative or absolute). --- pyproject.toml | 11 +---------- scripts/eachdist.py | 7 ++++--- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cb0d3b8bbd6..5d19c298827 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,16 +3,7 @@ line-length = 79 exclude = ''' ( /( - \.eggs # exclude a few common directories in the - | \.git # root of the project - | \.hg - | \.mypy_cache - | \.tox - | \.venv.* - | venv.* - | \.vscode - | dist - | ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/gen # generated files + ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/gen # generated files )/ ) ''' diff --git a/scripts/eachdist.py b/scripts/eachdist.py index c13699b6bba..e5a085c8279 100755 --- a/scripts/eachdist.py +++ b/scripts/eachdist.py @@ -308,14 +308,15 @@ def lint_args(args): runsubprocess( args.dry_run, - ("black", rootdir) - + (("--diff", "--check") if args.check_only else ()), + ("black", ".") + (("--diff", "--check") if args.check_only else ()), + cwd=rootdir, check=True, ) runsubprocess( args.dry_run, - ("isort", "--recursive", rootdir) + ("isort", "--recursive", ".") + (("--diff", "--check-only") if args.check_only else ()), + cwd=rootdir, check=True, ) runsubprocess(args.dry_run, ("flake8", rootdir), check=True) From ea07bd63f440483840f02ee336986f917afd4062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Fri, 22 Nov 2019 16:49:05 +0100 Subject: [PATCH 08/18] Add extraroots to cover missing py files. --- eachdist.ini | 2 ++ scripts/eachdist.py | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/eachdist.ini b/eachdist.ini index 00ccdc2168e..ed1b58787bd 100644 --- a/eachdist.ini +++ b/eachdist.ini @@ -7,7 +7,9 @@ sortfirst= ext/* [lintroots] +extraroots=examples/*,scripts/ subglob=*.py,tests/,test/,src/* [testroots] +extraroots=examples/*,tests/ subglob=tests/,test/ \ No newline at end of file diff --git a/scripts/eachdist.py b/scripts/eachdist.py index e5a085c8279..cb320559eea 100755 --- a/scripts/eachdist.py +++ b/scripts/eachdist.py @@ -13,6 +13,15 @@ DEFAULT_ALLSEP = " " DEFAULT_ALLFMT = "{rel}" + +def unique(elems): + seen = set() + for elem in elems: + if elem not in seen: + yield elem + seen.add(elem) + + try: subprocess_run = subprocess.run except AttributeError: # Py < 3.5 compat @@ -127,6 +136,12 @@ def find_targets(mode, rootpath): mcfg = cfg[mode] targets = list(find_targets_unordered(rootpath)) + if "extraroots" in mcfg: + targets += [ + path + for extraglob in getlistcfg(mcfg["extraroots"]) + for path in rootpath.glob(extraglob) + ] if "sortfirst" in mcfg: sortfirst = getlistcfg(mcfg["sortfirst"]) @@ -155,7 +170,7 @@ def keyfunc(path): if ".egg-info" not in str(newentry) and newentry.exists() ] - return targets + return list(unique(targets)) def runsubprocess(dry_run, params, *args, **kwargs): @@ -322,8 +337,7 @@ def lint_args(args): runsubprocess(args.dry_run, ("flake8", rootdir), check=True) execute_args( parse_subargs( - args, - ("exec", "pylint scripts/ {}", "--all", "--mode", "lintroots",), + args, ("exec", "pylint {}", "--all", "--mode", "lintroots",), ) ) From ef7dd3377d61bc9150bcc967beb7f5d653030905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Fri, 22 Nov 2019 17:07:54 +0100 Subject: [PATCH 09/18] eachdist.py: Add --eager-upgrades, default w/ develop. --- scripts/eachdist.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/eachdist.py b/scripts/eachdist.py index cb320559eea..95ac3b1f8c8 100755 --- a/scripts/eachdist.py +++ b/scripts/eachdist.py @@ -71,13 +71,14 @@ def setup_instparser(instparser): setup_instparser(instparser) instparser.add_argument("--editable", "-e", action="store_true") instparser.add_argument("--with-dev-deps", action="store_true") + instparser.add_argument("--eager-upgrades", action="store_true") devparser = subparsers.add_parser( "develop", help="Install all distributions editable + dev dependencies.", ) setup_instparser(devparser) - devparser.set_defaults(editable=True, with_dev_deps=True) + devparser.set_defaults(editable=True, with_dev_deps=True, eager_upgrades=True) lintparser = subparsers.add_parser( "lint", help="Lint everything, autofixing if possible." @@ -264,6 +265,8 @@ def join_args(arglist): def install_args(args): clean_remainder_args(args.pipargs) + if args.eager_upgrades: + args.pipargs += ["--upgrade-strategy=eager"] if args.with_dev_deps: runsubprocess( From 51f5d8d0b406cf9bed80d57b9ff99873350226ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Fri, 22 Nov 2019 17:18:47 +0100 Subject: [PATCH 10/18] Fix lint. --- scripts/eachdist.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/eachdist.py b/scripts/eachdist.py index 95ac3b1f8c8..b428aec8f2c 100755 --- a/scripts/eachdist.py +++ b/scripts/eachdist.py @@ -78,7 +78,9 @@ def setup_instparser(instparser): help="Install all distributions editable + dev dependencies.", ) setup_instparser(devparser) - devparser.set_defaults(editable=True, with_dev_deps=True, eager_upgrades=True) + devparser.set_defaults( + editable=True, with_dev_deps=True, eager_upgrades=True + ) lintparser = subparsers.add_parser( "lint", help="Lint everything, autofixing if possible." From 83120d68f7709f3a29edbcd0b0667dcc931d64db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Mon, 25 Nov 2019 09:23:04 +0100 Subject: [PATCH 11/18] Make sure testutil & wsgi are installed before dependents. --- eachdist.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/eachdist.ini b/eachdist.ini index ed1b58787bd..57820361be8 100644 --- a/eachdist.ini +++ b/eachdist.ini @@ -4,6 +4,8 @@ sortfirst= opentelemetry-api opentelemetry-sdk + ext/opentelemetry-ext-testutil + ext/opentelemetry-ext-wsgi ext/* [lintroots] @@ -12,4 +14,4 @@ subglob=*.py,tests/,test/,src/* [testroots] extraroots=examples/*,tests/ -subglob=tests/,test/ \ No newline at end of file +subglob=tests/,test/ From 66e9c8afafae671d58083878f89ced314b9da1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Mon, 25 Nov 2019 11:05:02 +0100 Subject: [PATCH 12/18] Fix dependencies for ext-testutil. --- eachdist.ini | 1 - ext/opentelemetry-ext-testutil/setup.cfg | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/eachdist.ini b/eachdist.ini index 57820361be8..a7a49380ad2 100644 --- a/eachdist.ini +++ b/eachdist.ini @@ -4,7 +4,6 @@ sortfirst= opentelemetry-api opentelemetry-sdk - ext/opentelemetry-ext-testutil ext/opentelemetry-ext-wsgi ext/* diff --git a/ext/opentelemetry-ext-testutil/setup.cfg b/ext/opentelemetry-ext-testutil/setup.cfg index 170e949cf67..520df8bf974 100644 --- a/ext/opentelemetry-ext-testutil/setup.cfg +++ b/ext/opentelemetry-ext-testutil/setup.cfg @@ -38,7 +38,7 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-ext-wsgi + opentelemetry-api [options.extras_require] test = flask~=1.0 From cf7e685d7453d8c71bdce23ab744d31b60aa0b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Tue, 26 Nov 2019 18:14:52 +0100 Subject: [PATCH 13/18] Fix wrong dependency order for tracecontext in tox.ini. --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index a547e781ff2..eddc1bc89fc 100644 --- a/tox.ini +++ b/tox.ini @@ -134,8 +134,8 @@ commands_pre = pip install -e {toxinidir}/opentelemetry-api \ -e {toxinidir}/opentelemetry-sdk \ -e {toxinidir}/ext/opentelemetry-ext-http-requests \ - -e {toxinidir}/ext/opentelemetry-ext-flask \ - -e {toxinidir}/ext/opentelemetry-ext-wsgi + -e {toxinidir}/ext/opentelemetry-ext-wsgi \ + -e {toxinidir}/ext/opentelemetry-ext-flask commands = {toxinidir}/scripts/tracecontext-integration-test.sh From c5a2aa69b85fd4895ba0063936ec5a544c599c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Thu, 5 Dec 2019 17:37:15 +0100 Subject: [PATCH 14/18] Fix mypy version being ignored. --- dev-requirements.txt | 4 ++-- tox.ini | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 16d7d09e463..6ce54793067 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -2,9 +2,9 @@ pylint~=2.3 flake8~=3.7 isort~=4.3 black>=19.3b0,==19.* -mypy~=0.740 +mypy==0.740 sphinx~=2.1 sphinx-rtd-theme~=0.4 sphinx-autodoc-typehints~=1.10.2 pytest!=5.2.3 -pytest-cov>=2.8 \ No newline at end of file +pytest-cov>=2.8 diff --git a/tox.ini b/tox.ini index 4509155aff0..81d7fdc9ace 100644 --- a/tox.ini +++ b/tox.ini @@ -25,7 +25,7 @@ deps = -c dev-requirements.txt test: pytest coverage: pytest-cov - mypy,mypyinstalled: mypy==0.740 + mypy,mypyinstalled: mypy setenv = mypy: MYPYPATH={toxinidir}/opentelemetry-api/src/ From 4052f5fd5e85a7b7712c5888748e612be0f9c563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Fri, 6 Dec 2019 16:12:30 +0100 Subject: [PATCH 15/18] Fix error message when calling without command. --- scripts/eachdist.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/eachdist.py b/scripts/eachdist.py index b428aec8f2c..0765989b614 100755 --- a/scripts/eachdist.py +++ b/scripts/eachdist.py @@ -41,7 +41,8 @@ def parse_args(args=None): ) parser.set_defaults(parser=parser) parser.add_argument("--dry-run", action="store_true") - subparsers = parser.add_subparsers() + subparsers = parser.add_subparsers(metavar="command") + subparsers.required = True excparser = subparsers.add_parser( "exec", help="Run an executable for each or all distributions." From 487a600cf94af085f3d47b285895b975565de902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Fri, 6 Dec 2019 17:54:08 +0100 Subject: [PATCH 16/18] eachdist.py: Document exec, improve doc in general. --- scripts/eachdist.py | 162 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 149 insertions(+), 13 deletions(-) diff --git a/scripts/eachdist.py b/scripts/eachdist.py index 0765989b614..2beaa508cd4 100755 --- a/scripts/eachdist.py +++ b/scripts/eachdist.py @@ -7,6 +7,7 @@ import sys from collections import namedtuple from configparser import ConfigParser +from inspect import cleandoc from itertools import chain from pathlib import Path, PurePath @@ -35,30 +36,161 @@ def subprocess_run(*args, **kwargs): return CompletedProcess(returncode=subprocess.call(*args, **kwargs)) -def parse_args(args=None): - parser = argparse.ArgumentParser( - description="Do something for each or all distributions in the repository." +def extraargs_help(calledcmd): + return cleandoc( + """ + Additional arguments to pass on to {}. + + This is collected from any trailing arguments passed to `%(prog)s`. + Use an initial `--` to separate them from regular arguments. + """.format( + calledcmd + ) ) + + +def parse_args(args=None): + parser = argparse.ArgumentParser(description="Development helper script.") parser.set_defaults(parser=parser) - parser.add_argument("--dry-run", action="store_true") - subparsers = parser.add_subparsers(metavar="command") + parser.add_argument( + "--dry-run", + action="store_true", + help="Only display what would be done, don't actually do anything.", + ) + subparsers = parser.add_subparsers(metavar="COMMAND") subparsers.required = True excparser = subparsers.add_parser( - "exec", help="Run an executable for each or all distributions." + "exec", + help="Run a command for each or all targets.", + formatter_class=argparse.RawTextHelpFormatter, + description=cleandoc( + """Run a command according to the `format` argument for each or all targets. + + This is an advanced command that is used internally by other commands. + + For example, to install all distributions in this repository + editable, you could use: + + scripts/eachdist.py exec "python -m pip install -e {}" + + This will run pip for all distributions which is quite slow. It gets + a bit faster if we only invoke pip once but with all the paths + gathered together, which can be achieved by using `--all`: + + scripts/eachdist.py exec "python -m pip install {}" --all "-e {}" + + The sortfirst option in the DEFAULT section of eachdist.ini makes + sure that dependencies are installed before their dependents. + + Search for usages of `parse_subargs` in the source code of this script + to see more examples. + + This command first collects target paths and then executes + commands according to `format` and `--all`. + + Target paths are initially all Python distribution root paths + (as determined by the existence of setup.py, etc. files). + They are then augmented according to the section of the + `PROJECT_ROOT/eachdist.ini` config file specified by the `--mode` option. + + The following config options are available (and processed in that order): + + - `extraroots`: List of project root-relative glob expressions. + The resulting paths will be added. + - `sortfirst`: List of glob expressions. + Any matching paths will be put to the front of the path list, + in the same order they appear in this option. If more than one + glob matches, ordering is according to the first. + - `subglob`: List of glob expressions. Each path added so far is removed + and replaced with the result of all glob expressions relative to it (in + order of the glob expressions). + + After all this, any duplicate paths are removed (the first occurrence remains). + """ + ), ) excparser.set_defaults(func=execute_args) - excparser.add_argument("format") - excparser.add_argument("--all", nargs="?", const=DEFAULT_ALLFMT) - excparser.add_argument("--allsep") excparser.add_argument( - "--allowexitcode", type=int, action="append", default=[0] + "format", + help=cleandoc( + """Format string for the command to execute. + + The available replacements depend on whether `--all` is specified. + If `--all` was specified, there is only a single replacement, + `{}`, that is replaced with the string that is generated from + joining all targets formatted with `--all` to a single string + with the value of `--allsep` as separator. + + If `--all` was not specified, the following replacements are available: + + - `{}`: the absolute path to the current target in POSIX format + (with forward slashes) + - `{rel}`: like `{}` but relative to the project root. + - `{raw}`: the absolute path to the current target in native format + (thus exactly the same as `{}` on Unix but with backslashes on Windows). + - `{rawrel}`: like `{rel}` but relative to the project root. + + The resulting string is then split according to POSIX shell rules + (so you can use quotation marks or backslashes to handle arguments + containing spaces). + + The first token is the name of the executable to run, the remaining + tokens are the arguments. + + Note that a shell is *not* involved by default. + You can add bash/sh/cmd/powershell yourself to the format if you want. + + If `--all` was specified, the resulting command is simply executed once. + Otherwise, the command is executed for each found target. In both cases, + the project root is the working directory. + """ + ), + ) + excparser.add_argument( + "--all", + nargs="?", + const=DEFAULT_ALLFMT, + metavar="ALLFORMAT", + help=cleandoc( + """Instead of running the command for each target, join all target + paths together to run a single command. + + This option optionally takes a format string to apply to each path. The + available replacements are the ones that would be available for `format` + if `--all` was not specified. + + Default ALLFORMAT if this flag is specified: `%(const)s`. + """ + ), + ) + excparser.add_argument( + "--allsep", + help=cleandoc( + """Separator string for the strings resulting from `--all`. + Only valid if `--all` is specified. + """ + ), + ) + excparser.add_argument( + "--allowexitcode", + type=int, + action="append", + default=[0], + help=cleandoc( + """The given command exit code is treated as success and does not abort execution. + Can be specified multiple times. + """ + ), ) excparser.add_argument( "--mode", "-m", default="DEFAULT", - help="Section of config file to use for target selection configuration.", + help=cleandoc( + """Section of config file to use for target selection configuration. + See description of exec for available options.""" + ), ) instparser = subparsers.add_parser( @@ -67,7 +199,9 @@ def parse_args(args=None): def setup_instparser(instparser): instparser.set_defaults(func=install_args) - instparser.add_argument("pipargs", nargs=argparse.REMAINDER) + instparser.add_argument( + "pipargs", nargs=argparse.REMAINDER, help=extraargs_help("pip") + ) setup_instparser(instparser) instparser.add_argument("--editable", "-e", action="store_true") @@ -94,7 +228,9 @@ def setup_instparser(instparser): help="Test everything (run pytest yourself for more complex operations).", ) testparser.set_defaults(func=test_args) - testparser.add_argument("pytestargs", nargs=argparse.REMAINDER) + testparser.add_argument( + "pytestargs", nargs=argparse.REMAINDER, help=extraargs_help("pytest") + ) return parser.parse_args(args) From 3c203be2fac039e2cfd52353356b6fabaa2bd7c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Fri, 6 Dec 2019 17:59:09 +0100 Subject: [PATCH 17/18] Typo. --- scripts/eachdist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/eachdist.py b/scripts/eachdist.py index 2beaa508cd4..8d41315fc7a 100755 --- a/scripts/eachdist.py +++ b/scripts/eachdist.py @@ -129,7 +129,7 @@ def parse_args(args=None): - `{rel}`: like `{}` but relative to the project root. - `{raw}`: the absolute path to the current target in native format (thus exactly the same as `{}` on Unix but with backslashes on Windows). - - `{rawrel}`: like `{rel}` but relative to the project root. + - `{rawrel}`: like `{raw}` but relative to the project root. The resulting string is then split according to POSIX shell rules (so you can use quotation marks or backslashes to handle arguments From ac4174b5303f14e33f3aef84896f74cf5167ad6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Mon, 9 Dec 2019 11:07:14 +0100 Subject: [PATCH 18/18] Lint jaeger examples. See https://github.com/dynatrace-oss-contrib/opentelemetry-python/pull/2#discussion_r355340497. --- eachdist.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eachdist.ini b/eachdist.ini index a7a49380ad2..8baab0c2e18 100644 --- a/eachdist.ini +++ b/eachdist.ini @@ -9,7 +9,7 @@ sortfirst= [lintroots] extraroots=examples/*,scripts/ -subglob=*.py,tests/,test/,src/* +subglob=*.py,tests/,test/,src/*,examples/* [testroots] extraroots=examples/*,tests/