Skip to content

Commit

Permalink
Add primer CI tool 🏴 (#1402)
Browse files Browse the repository at this point in the history
* Add primer CI tool 💩
- Run in PATH `black` binary on configured projects
- Can set wether we expect changes or not per project
- Can set what python versions are supported for a project
- if `long_checkout` True project will not be ran on CI

Will add to CI after I finish unit tests to avoid silly bugs I'm sure I have 🤪

Tests:
- Manual Run - Will add unit tests if people think it will be useful
- Output:

```shell
(b) cooper-mbp1:black cooper$ time /tmp/b/bin/black-primer -k -w /tmp/cooper_primer_1
[2020-05-10 08:48:25,696] INFO: 4 projects to run black over (lib.py:212)
[2020-05-10 08:48:25,697] INFO: Skipping aioexabgp as it's disabled via config (lib.py:166)
[2020-05-10 08:48:25,699] INFO: Skipping bandersnatch as it's disabled via config (lib.py:166)
[2020-05-10 08:48:28,676] INFO: Analyzing results (lib.py:225)
-- primer results 📊 --

2 / 4 succeeded (50.0%) ✅
0 / 4 FAILED (0.0%) 💩
 - 2 projects Disabled by config
 - 0 projects skipped due to Python Version
 - 0 skipped due to long checkout

real	0m3.304s
user	0m9.529s
sys	0m1.019s
```

- ls of /tmp/cooper_primer_1
```
(b) cooper-mbp1:black cooper$ ls -lh /tmp/cooper_primer_1
total 0
drwxr-xr-x  21 cooper  wheel   672B May 10 08:48 attrs
drwxr-xr-x  14 cooper  wheel   448B May 10 08:48 flake8-bugbear
```

* Address mypy 3.6 type errors
- Don't use asyncio.run() ... go back to the past :P
- Refactor results into a named tuple of two dicts to avoid typing nightmare
- Fix some variable names
- Fix bug with rebase logic in git_checkout_or_rebase

* Prettier the JSON config file for primer

* Delete projects when finished, move dir to be timestamped + shallow copy

* Re-enable disabled projects post @JelleZijlstra's docstring fix

* Workaround for future annotations until someone tells me the correct fix
  • Loading branch information
cooperlees committed May 17, 2020
1 parent 12e857f commit b50a527
Show file tree
Hide file tree
Showing 4 changed files with 431 additions and 1 deletion.
3 changes: 2 additions & 1 deletion setup.py
Expand Up @@ -62,7 +62,7 @@ def get_long_description() -> str:
license="MIT",
py_modules=["_black_version"],
ext_modules=ext_modules,
packages=["blackd", "black", "blib2to3", "blib2to3.pgen2"],
packages=["blackd", "black", "blib2to3", "blib2to3.pgen2", "black_primer"],
package_dir={"": "src"},
package_data={"blib2to3": ["*.txt"], "black": ["py.typed"]},
python_requires=">=3.6",
Expand Down Expand Up @@ -102,6 +102,7 @@ def get_long_description() -> str:
"console_scripts": [
"black=black:patched_main",
"blackd=blackd:patched_main [d]",
"black-primer=black_primer.cli:main",
]
},
)
135 changes: 135 additions & 0 deletions src/black_primer/cli.py
@@ -0,0 +1,135 @@
#!/usr/bin/env python3

import asyncio
import logging
import sys
from datetime import datetime
from os import cpu_count
from pathlib import Path
from shutil import rmtree, which
from tempfile import gettempdir
from typing import Any, Union

import click

from black_primer import lib


DEFAULT_CONFIG = Path(__file__).parent / "primer.json"
_timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
DEFAULT_WORKDIR = Path(gettempdir()) / f"primer.{_timestamp}"
LOG = logging.getLogger(__name__)


def _handle_debug(
ctx: click.core.Context,
param: Union[click.core.Option, click.core.Parameter],
debug: Union[bool, int, str],
) -> Union[bool, int, str]:
"""Turn on debugging if asked otherwise INFO default"""
log_level = logging.DEBUG if debug else logging.INFO
logging.basicConfig(
format="[%(asctime)s] %(levelname)s: %(message)s (%(filename)s:%(lineno)d)",
level=log_level,
)
return debug


async def async_main(
config: str,
debug: bool,
keep: bool,
long_checkouts: bool,
rebase: bool,
workdir: str,
workers: int,
) -> int:
work_path = Path(workdir)
if not work_path.exists():
LOG.debug(f"Creating {work_path}")
work_path.mkdir()

if not which("black"):
LOG.error(f"Can not find 'black' executable in PATH. No point in running")
return -1

try:
ret_val = await lib.process_queue(
config, work_path, workers, keep, long_checkouts, rebase
)
return int(ret_val)
finally:
if not keep and work_path.exists():
LOG.debug(f"Removing {work_path}")
rmtree(work_path)

return -1


@click.command(context_settings={"help_option_names": ["-h", "--help"]})
@click.option(
"-c",
"--config",
default=str(DEFAULT_CONFIG),
type=click.Path(exists=True),
show_default=True,
help="JSON config file path",
)
@click.option(
"--debug",
is_flag=True,
callback=_handle_debug,
show_default=True,
help="Turn on debug logging",
)
@click.option(
"-k",
"--keep",
is_flag=True,
show_default=True,
help="Keep workdir + repos post run",
)
@click.option(
"-L",
"--long-checkouts",
is_flag=True,
show_default=True,
help="Pull big projects to test",
)
@click.option(
"-R",
"--rebase",
is_flag=True,
show_default=True,
help="Rebase project if already checked out",
)
@click.option(
"-w",
"--workdir",
default=str(DEFAULT_WORKDIR),
type=click.Path(exists=False),
show_default=True,
help="Directory Path for repo checkouts",
)
@click.option(
"-W",
"--workers",
default=int((cpu_count() or 4) / 2) or 1,
type=int,
show_default=True,
help="Number of parallel worker coroutines",
)
@click.pass_context
def main(ctx: click.core.Context, **kwargs: Any) -> None:
"""primer - prime projects for blackening ... 🏴"""
LOG.debug(f"Starting {sys.argv[0]}")
# TODO: Change to asyncio.run when black >= 3.7 only
loop = asyncio.get_event_loop()
try:
ctx.exit(loop.run_until_complete(async_main(**kwargs)))
finally:
loop.close()


if __name__ == "__main__":
main()

0 comments on commit b50a527

Please sign in to comment.