|
| 1 | +# Copyright (C) Ivan Kravets <[email protected]> |
| 2 | +# See LICENSE for details. |
| 3 | + |
| 4 | +from glob import glob |
| 5 | +from os import environ, makedirs, remove |
| 6 | +from os.path import basename, isdir, isfile, join |
| 7 | +from shutil import copyfile, copytree, rmtree |
| 8 | +from tempfile import mkdtemp |
| 9 | + |
| 10 | +import click |
| 11 | + |
| 12 | +from platformio import app |
| 13 | +from platformio.commands.init import cli as cmd_init |
| 14 | +from platformio.commands.run import cli as cmd_run |
| 15 | +from platformio.exception import CIBuildEnvsEmpty |
| 16 | +from platformio.util import get_boards |
| 17 | + |
| 18 | + |
| 19 | +def validate_path(ctx, param, value): # pylint: disable=W0613 |
| 20 | + invalid_path = None |
| 21 | + for p in value: |
| 22 | + if not glob(p): |
| 23 | + invalid_path = p |
| 24 | + break |
| 25 | + try: |
| 26 | + assert invalid_path is None |
| 27 | + return value |
| 28 | + except AssertionError: |
| 29 | + raise click.BadParameter("Found invalid path: %s" % invalid_path) |
| 30 | + |
| 31 | + |
| 32 | +def validate_boards(ctx, param, value): # pylint: disable=W0613 |
| 33 | + unknown_boards = set(value) - set(get_boards().keys()) |
| 34 | + try: |
| 35 | + assert not unknown_boards |
| 36 | + return value |
| 37 | + except AssertionError: |
| 38 | + raise click.BadParameter( |
| 39 | + "%s. Please search for the board types using " |
| 40 | + "`platformio boards` command" % ", ".join(unknown_boards)) |
| 41 | + |
| 42 | + |
| 43 | +@click.command("ci", short_help="Continuous Integration") |
| 44 | +@click.argument("src", nargs=-1, callback=validate_path) |
| 45 | +@click.option("--lib", "-l", multiple=True, callback=validate_path) |
| 46 | +@click.option("--exclude", multiple=True) |
| 47 | +@click.option("--board", "-b", multiple=True, callback=validate_boards) |
| 48 | +@click.option("--build-dir", default=mkdtemp, |
| 49 | + type=click.Path(exists=True, file_okay=False, dir_okay=True, |
| 50 | + writable=True, resolve_path=True)) |
| 51 | +@click.option("--keep-build-dir", is_flag=True) |
| 52 | +@click.option("--project-conf", |
| 53 | + type=click.Path(exists=True, file_okay=True, dir_okay=False, |
| 54 | + readable=True, resolve_path=True)) |
| 55 | +@click.pass_context |
| 56 | +def cli(ctx, src, lib, exclude, board, # pylint: disable=R0913 |
| 57 | + build_dir, keep_build_dir, project_conf): |
| 58 | + |
| 59 | + if not src: |
| 60 | + src = environ.get("PLATFORMIO_CI_SRC", "").split(":") |
| 61 | + if not src: |
| 62 | + raise click.BadParameter("Missing argument 'src'") |
| 63 | + |
| 64 | + try: |
| 65 | + app.set_session_var("force_option", True) |
| 66 | + _clean_dir(build_dir) |
| 67 | + |
| 68 | + for dir_name, patterns in dict(lib=lib, src=src).iteritems(): |
| 69 | + if not patterns: |
| 70 | + continue |
| 71 | + contents = [] |
| 72 | + for p in patterns: |
| 73 | + contents += glob(p) |
| 74 | + _copy_contents(join(build_dir, dir_name), contents) |
| 75 | + |
| 76 | + if project_conf and isfile(project_conf): |
| 77 | + copyfile(project_conf, join(build_dir, "platformio.ini")) |
| 78 | + elif not board: |
| 79 | + raise CIBuildEnvsEmpty() |
| 80 | + |
| 81 | + if exclude: |
| 82 | + _exclude_contents(build_dir, exclude) |
| 83 | + |
| 84 | + # initialise project |
| 85 | + ctx.invoke(cmd_init, project_dir=build_dir, board=board, |
| 86 | + disable_auto_uploading=True) |
| 87 | + |
| 88 | + # process project |
| 89 | + ctx.invoke(cmd_run, project_dir=build_dir) |
| 90 | + finally: |
| 91 | + if not keep_build_dir: |
| 92 | + rmtree(build_dir) |
| 93 | + |
| 94 | + |
| 95 | +def _clean_dir(dirpath): |
| 96 | + rmtree(dirpath) |
| 97 | + makedirs(dirpath) |
| 98 | + |
| 99 | + |
| 100 | +def _copy_contents(dst_dir, contents): |
| 101 | + items = { |
| 102 | + "dirs": set(), |
| 103 | + "files": set() |
| 104 | + } |
| 105 | + |
| 106 | + for path in contents: |
| 107 | + if isdir(path): |
| 108 | + items['dirs'].add(path) |
| 109 | + elif isfile(path): |
| 110 | + items['files'].add(path) |
| 111 | + |
| 112 | + dst_dir_name = basename(dst_dir) |
| 113 | + |
| 114 | + if dst_dir_name == "src" and len(items['dirs']) == 1: |
| 115 | + copytree(list(items['dirs']).pop(), dst_dir) |
| 116 | + else: |
| 117 | + makedirs(dst_dir) |
| 118 | + for d in items['dirs']: |
| 119 | + copytree(d, join(dst_dir, basename(d))) |
| 120 | + |
| 121 | + if not items['files']: |
| 122 | + return |
| 123 | + |
| 124 | + if dst_dir_name == "lib": |
| 125 | + dst_dir = join(dst_dir, mkdtemp(dir=dst_dir)) |
| 126 | + |
| 127 | + for f in items['files']: |
| 128 | + copyfile(f, join(dst_dir, basename(f))) |
| 129 | + |
| 130 | + |
| 131 | +def _exclude_contents(dst_dir, patterns): |
| 132 | + contents = [] |
| 133 | + for p in patterns: |
| 134 | + contents += glob(join(dst_dir, p)) |
| 135 | + for path in contents: |
| 136 | + if isdir(path): |
| 137 | + rmtree(path) |
| 138 | + elif isfile(path): |
| 139 | + remove(path) |
0 commit comments