Пример #1
0
 def input(self,
           *,
           file: Optional[str] = None,
           sh: Optional[str] = None,
           env: Env = None):
     # we want the state *before* running the action
     state = self.hashstate.state()
     super().input(file=file, sh=sh, env=env)
     self.run_shell_queue()
     if file is not None:
         self.load_deps([self.absolute(file)])
         with open(self.absolute(file), "r") as f:
             return f.read()
     else:
         log("RUNNING", sh)
         out = run_shell(
             sh,
             shell=True,
             cwd=self.cwd,
             capture_output=True,
             quiet=True,
             inherit_env=False,
             env=self.normalize(env),
         ).decode("utf-8")
         self.memorize(state, HashState().record(sh, env).state(), out)
         return out
Пример #2
0
    def dep_fetcher(dep, *, initial_load=False):
        if dep not in loaded_deps and in_sandbox:
            if not initial_load:
                raise BuildException(
                    f"New dep {dep} found when rerunning rule, it's likely not deterministic!"
                )
            if not dep.startswith(":"):
                log(f"Loading dependency {dep} into sandbox")
                copy_helper(
                    src_root=os.curdir,
                    dest_root=scratch_path,
                    src_names=[dep],
                    symlink=not rule.do_not_symlink,
                )

        # check that these deps are built! Since they may not have been checked by the PreviewExecution.
        dep_rule = None
        if dep not in build_state.source_files:
            dep_rule = build_state.target_rule_lookup.lookup(build_state, dep)
            if dep_rule not in build_state.ready:
                raise MissingDependency(dep)

        loaded_deps.add(dep)

        return dep_rule
Пример #3
0
def enqueue_deps(build_state: BuildState, rule: Rule,
                 candidate_deps: Collection[str]) -> bool:
    waiting_for_deps = False

    with build_state.scheduling_lock:
        for dep in candidate_deps:
            if dep in build_state.source_files:
                # nothing to do
                continue

            runtime_dep: Rule = build_state.target_rule_lookup.lookup(
                build_state, dep)

            if runtime_dep not in build_state.ready:
                waiting_for_deps = True
                if runtime_dep not in build_state.scheduled_but_not_ready:
                    # enqueue dependency
                    log(f"Enqueueing dependency {runtime_dep}")
                    build_state.scheduled_but_not_ready.add(runtime_dep)
                    build_state.work_queue.put(runtime_dep)
                    build_state.status_monitor.move(total=1)
                else:
                    log(f"Waiting on already queued dependency {runtime_dep}")
                # register task in the already queued / executing dependency
                # so when it finishes we may be triggered
                runtime_dep.runtime_dependents.add(rule)
                rule.pending_rule_dependencies.add(runtime_dep)
    return waiting_for_deps
Пример #4
0
def publish(item, board, helper, channel, web):
    item_type = helper.item_type(item)
    post_process(item, board, helper, web)

    while True:
        try:
            channel.basic_publish(exchange='chan',
                                  routing_key="%s.%s.%s" %
                                  (chan, item_type, board),
                                  body=json.dumps(item,
                                                  separators=(',', ':'),
                                                  ensure_ascii=False,
                                                  sort_keys=True))

            if MONITORING:
                distance = datetime.utcnow() - datetime.utcfromtimestamp(
                    helper.item_mtime(item))
                monitoring.log([{
                    "measurement": chan,
                    "time": str(datetime.utcnow()),
                    "tags": {
                        "board": board
                    },
                    "fields": {
                        "distance": distance.total_seconds()
                    }
                }])
            break
        except Exception as e:
            logger.debug(traceback.format_exc())
            logger.error(str(e))
            channel = connect()
Пример #5
0
 def dep_fetcher(dep):
     if dep.startswith(":"):
         if dep not in direct_lookup:
             raise BuildException(f"Unable to find setup rule {dep}")
         dep_rule = direct_lookup[dep]
         log(f"Looking up setup rule {dep}")
         if dep_rule not in ready:
             raise MissingDependency(dep)
         return dep_rule
Пример #6
0
 def run_shell_queue(self):
     for cmd, cwd, env in self.sh_queue:
         log("RUNNING cmd:", cmd)
         run_shell(
             cmd,
             shell=True,
             cwd=cwd,
             quiet=True,
             capture_output=True,
             inherit_env=False,
             env=env,
         )
     self.sh_queue = []
Пример #7
0
 def input(self, sh: Optional[str] = None, *, env: Env = None):
     # we want the state *before* running the action
     state = self.hashstate.state()
     super().input(sh, env=env)
     if self.out_of_date_deps:
         raise MissingDependency(*self.out_of_date_deps)
     self.run_shell_queue()
     log("RUNNING", sh)
     out = run_shell(
         sh,
         shell=True,
         cwd=os.path.join(self.base, self.cwd),
         capture_output=True,
         quiet=True,
         inherit_env=False,
         env=self.normalize(env),
     ).decode("utf-8")
     self.memorize(state, HashState().record(sh, env).state(), out)
     return out
Пример #8
0
 def load_deps(deps):
     deps = set(deps) - loaded_deps
     # check that these deps are built! Since they have not been checked by the PreviewExecution.
     missing_deps = []
     for dep in deps:
         if dep not in build_state.source_files:
             dep_rule = build_state.target_rule_lookup.lookup(
                 build_state, dep)
             if dep_rule not in build_state.ready:
                 missing_deps.append(dep)
     if missing_deps:
         raise MissingDependency(*missing_deps)
     loaded_deps.update(deps)
     if in_sandbox:
         log(f"Loading dependencies {deps} into sandbox")
         copy_helper(
             src_root=build_state.repo_root,
             dest_root=scratch_path,
             src_names=[dep for dep in deps if not dep.startswith(":")],
             symlink=not rule.do_not_symlink,
         )
Пример #9
0
def enqueue_deps(
    build_state: BuildState,
    rule: Optional[Rule],
    candidate_deps: Collection[str],
    *,
    catch_failure: bool = False,
) -> bool:
    waiting_for_deps = False

    with build_state.scheduling_lock:
        for dep in candidate_deps:
            if dep in build_state.source_files:
                # nothing to do
                continue

            if catch_failure:
                runtime_dep: Rule = build_state.target_rule_lookup.try_lookup(
                    dep)
                if runtime_dep is None:
                    continue
            else:
                runtime_dep: Rule = build_state.target_rule_lookup.lookup(
                    build_state, dep)

            if runtime_dep not in build_state.ready:
                waiting_for_deps = True
                if runtime_dep not in build_state.scheduled_but_not_ready:
                    # enqueue dependency
                    log(f"Enqueueing dependency {runtime_dep}")
                    build_state.scheduled_but_not_ready.add(runtime_dep)
                    build_state.work_queue.put(runtime_dep)
                    build_state.status_monitor.move(total=1)
                else:
                    log(f"Waiting on already queued dependency {runtime_dep}")
                if rule:
                    # register task in the already queued / executing dependency
                    # so when it finishes we may be triggered
                    # if rule is None, these are deferred dependencies, so no need to register anything
                    log(f"Registering {rule} to wait on {runtime_dep}")
                    runtime_dep.runtime_dependents.add(rule)
                    rule.pending_rule_dependencies.add(runtime_dep)
    return waiting_for_deps
Пример #10
0
def worker(build_state: BuildState, index: int):
    scratch_path = Path(build_state.repo_root).joinpath(
        Path(f".scratch_{index}"))
    if scratch_path.exists():
        rmtree(scratch_path, ignore_errors=True)

    _, cache_save = make_cache_memorize(build_state.cache_directory)
    _, cache_loader = make_cache_fetcher(build_state.cache_directory)

    while True:
        if build_state.failure is not None:
            # every thread needs to clear the queue since otherwise some other thread might still be filling it up
            clear_queue(build_state.work_queue)
            return  # some thread has failed, emergency stop
        todo = build_state.work_queue.get()
        if todo is None:
            return

        start_time = time.time()

        try:
            build_state.status_monitor.update(index, "Parsing: " + str(todo))

            log(f"Target {todo} popped from queue by worker {index}")

            # only from caches, will never run a subprocess
            cache_key, deps = get_deps(build_state, todo)

            if cache_key is None:
                # unable to compute cache_key, potentially because not all deps are ready
                log(f"Target {todo} either has unbuilt dependencies, "
                    f"or does not have a cached dynamic dependency resolved")
                deps_ready = not enqueue_deps(build_state, todo, deps)
                if deps_ready:
                    log("Apparently it is missing an input cache in the impl")
                else:
                    log("Apparently it is waiting on unbuilt dependencies")
            else:
                log(f"All the dependencies of target {todo} are ready: {deps}")
                # if the cache_key is ready, *all* the deps must be ready, not just the discoverable deps!
                deps_ready = True

            if deps_ready:
                done = False
                # check if we're already cached!
                if cache_key:
                    if cache_loader(cache_key, todo, build_state.repo_root):
                        log(f"Target {todo} was loaded from the cache")
                        done = True

                if not done:
                    # time to execute! but *not* inside the lock
                    # when we release the lock, stuff may change outside, but
                    # we don't care since *our* dependencies (so far) are all available
                    log(f"Target {todo} is not in the cache, rerunning...")
                    build_state.status_monitor.update(index,
                                                      "Building: " + str(todo))
                    try:
                        # if cache_key is None, we haven't finished evaluating the impl, so we
                        # don't know all the dependencies it could need. Therefore, we must
                        # run it in the working directory, so the impl can find the dependencies it needs
                        # Then, we run it *again*, to verify that the dependencies are accurate
                        in_sandbox = cache_key is not None

                        if not in_sandbox:
                            log(f"We don't know the dependencies of {todo}, "
                                f"so we are running the impl in the root directory to find out!"
                                )
                            cache_key = build(build_state,
                                              todo,
                                              deps,
                                              scratch_path=None)
                            # now, if no exception has thrown, all the deps are available to the deps finder
                            alt_cache_key, deps = get_deps(build_state, todo)
                            # the alt_cache_key *MAY HAVE CHANGED*, because dynamic dependencies
                            # are not used for the input() cache_key [since they are only known afterwards]
                            # however, the alt_cache_key should match the cache_key of the subsequent run
                            try:
                                alt_cache_key = build(
                                    build_state,
                                    todo,
                                    deps,
                                    scratch_path=scratch_path,
                                )
                            except MissingDependency:
                                raise BuildException(
                                    "An internal error has occurred.")
                            except CalledProcessError as e:
                                build_state.status_monitor.stop()
                                print(e.cmd)
                                print(e)
                                raise BuildException(
                                    f"The dependencies for target {todo} are not fully specified, "
                                    f"as it failed to build when provided only with them."
                                )
                            assert (cache_key == alt_cache_key
                                    ), "An internal error has occurred"
                        else:
                            log(f"We know all the dependencies of {todo}, so we can run it in a sandbox"
                                )
                            build(build_state,
                                  todo,
                                  deps,
                                  scratch_path=scratch_path)
                        log(f"Target {todo} has been built fully!")
                        cache_save(cache_key, todo, scratch_path)

                        done = True
                    except MissingDependency as d:
                        log(f"Target {todo} failed to fully build because of the missing dynamic "
                            f"dependencies: {d.paths}, requeuing")
                        scheduled = enqueue_deps(build_state, todo, d.paths)
                        if not scheduled:
                            # due to race conditions, our dependencies are actually all ready now!
                            # no one else will enqueue us, so it is safe to enqueue ourselves
                            build_state.work_queue.put(todo)
                            build_state.status_monitor.move(total=1)

                    if scratch_path.exists():
                        rmtree(scratch_path, ignore_errors=True)

                if done:
                    with build_state.scheduling_lock:
                        build_state.ready.add(todo)
                        # no one will ever add us back, since we are in `ready`
                        build_state.scheduled_but_not_ready.remove(todo)
                        # now it's time to set up our dependents
                        # we need to be inside the lock even if we have no dependents, in case
                        # we *gain* dependents from another thread which could have held the lock!
                        for dependent in todo.runtime_dependents:
                            dependent.pending_rule_dependencies.remove(todo)
                            if not dependent.pending_rule_dependencies:
                                # this guy is ready to go
                                build_state.work_queue.put(dependent)
                                build_state.status_monitor.move(total=1)

            # either way, we're done with this task for now
            build_state.status_monitor.move(curr=1)
            build_state.work_queue.task_done()

            # record timing data
            run_time = time.time() - start_time
            TIMINGS[str(todo)] += run_time
        except Exception as e:
            if not isinstance(e, BuildException):
                suffix = f"\n{Style.RESET_ALL}" + traceback.format_exc()
            else:
                suffix = ""
            build_state.status_monitor.stop()
            build_state.failure = BuildException(
                f"Error while executing rule {todo}: " + str(e) + suffix)
            build_state.work_queue.task_done()
Пример #11
0
def worker(build_state: BuildState, index: int):
    scratch_path = Path(f".scratch_{index}")
    if scratch_path.exists():
        rmtree(scratch_path, ignore_errors=True)

    _, cache_store_files = make_cache_store(build_state.cache_directory)
    _, cache_load_files = make_cache_load(build_state.cache_directory)

    while True:
        if build_state.failure is not None:
            # every thread needs to clear the queue since otherwise some other thread might still be filling it up
            clear_queue(build_state.work_queue)
            return  # some thread has failed, emergency stop
        todo = build_state.work_queue.get()
        if todo is None:
            return

        start_time = time.time()

        try:
            build_state.status_monitor.update(index, "Parsing: " + str(todo))

            log(f"Target {todo} popped from queue by worker {index}")

            # only from caches, will never run a subprocess
            (
                cache_key,
                provided_value,
                deps,
                deferred_deps,
                uses_dynamic_deps,
            ) = get_deps(build_state, todo, skip_cache_key=todo.do_not_cache)

            if uses_dynamic_deps:
                log("Target", todo, "Uses dynamic deps")

            if cache_key is None:
                # unable to compute cache_key, potentially because not all deps are ready
                log(
                    f"Target {todo} either has unbuilt dependencies, "
                    f"or does not have a cached dynamic dependency resolved"
                )
                deps_ready = not enqueue_deps(
                    build_state,
                    todo,
                    deps,
                    catch_failure=uses_dynamic_deps,
                )
                if deps_ready:
                    log(f"Apparently {todo} is missing an input cache in the impl")
                else:
                    log(f"Apparently {todo} is waiting on unbuilt dependencies")
            else:
                log(f"All the dependencies of target {todo} are ready: {deps}")
                # if the cache_key is ready, *all* the deps must be ready, not just the discoverable deps!
                # unless the cache_key is not actually cached, in which case our inputs() could be wrong, so
                # we have to run in the working directory to verify
                deps_ready = True

            log(f"Enqueuing deferred dependencies for {todo}: {deferred_deps}")
            enqueue_deps(
                build_state, None, deferred_deps, catch_failure=uses_dynamic_deps
            )

            if deps_ready:
                done = False
                # check if we're already cached!
                if cache_key and not todo.do_not_cache:
                    try:
                        outputs = cache_load_files(cache_key, todo, os.curdir)
                        log(f"Target {todo} was loaded from the cache")
                        done = True
                    except CacheMiss:
                        pass

                if not done:
                    # time to execute! but *not* inside the lock
                    # when we release the lock, stuff may change outside, but
                    # we don't care since *our* dependencies (so far) are all available
                    log(f"Target {todo} is not in the cache, rerunning...")
                    build_state.status_monitor.update(index, "Building: " + str(todo))
                    # if cache_key is None or we use dynamic dependencies, we haven't finished evaluating
                    # the impl, so we don't know all the dependencies it could need. Therefore, we must
                    # run it in the working directory, so the impl can find the dependencies it needs
                    # Then, we run it *again*, to verify that the dependencies are accurate.
                    # The cache_key could be None due to race conditions, even if we don't use dynamic deps.
                    # Imagine:
                    # - Rule A depends on a provider of rule B, which is not yet built
                    #   (and on C, because of B's provider)
                    # - We determine that rule B is not built, so A depends on B but can't complete impl,
                    #   so A.cache_key = None
                    # - B is built
                    # - We try to enqueue the deps of A. Enqueue_deps responds that B is already built,
                    #   so A.deps_ready=True
                    # - So at this stage, A.cache_key = None, A.uses_dynamic_deps=False, but A.deps is missing C!
                    if uses_dynamic_deps or cache_key is None:
                        log(
                            f"We don't know the dependencies of {todo}, "
                            f"so we are running the impl in the root directory to find out!"
                        )

                        try:
                            _, cache_key = build(
                                build_state,
                                todo,
                                scratch_path=None,
                                skip_cache_key=todo.do_not_cache,
                            )
                        except MissingDependency as d:
                            log(
                                f"Target {todo} failed to fully build because of the missing dynamic "
                                f"dependencies: {d.paths}, requeuing"
                            )
                            scheduled = enqueue_deps(build_state, todo, d.paths)
                            if not scheduled:
                                # due to race conditions, our dependencies are actually all ready now!
                                # no one else will enqueue us, so it is safe to enqueue ourselves
                                build_state.work_queue.put(todo)
                                build_state.status_monitor.move(total=1)
                        else:
                            # now, if no exception has thrown, all the deps are available to the deps finder
                            (
                                alt_cache_key,
                                provided_value,
                                deps,
                                deferred_deps,
                                uses_dynamic_deps,
                            ) = get_deps(
                                build_state, todo, skip_cache_key=todo.do_not_cache
                            )
                            try:
                                provided_value, alt_cache_key_2 = build(
                                    build_state,
                                    todo,
                                    scratch_path=scratch_path,
                                    precomputed_deps=deps,
                                    skip_cache_key=todo.do_not_cache,
                                )
                            except CalledProcessError as e:
                                build_state.status_monitor.stop()
                                print(e.cmd)
                                print(e)
                                raise BuildException(
                                    f"The dependencies for target {todo} are not fully specified, "
                                    f"as it failed to build when provided only with them."
                                )
                            if not todo.do_not_cache:
                                assert (
                                    cache_key == alt_cache_key == alt_cache_key_2
                                ), "An internal error has occurred"
                            done = True
                    else:
                        log(
                            f"We know all the dependencies of {todo}, so we can run it in a sandbox"
                        )
                        provided_value, alt_cache_key = build(
                            build_state,
                            todo,
                            scratch_path=scratch_path,
                            precomputed_deps=deps,
                            skip_cache_key=todo.do_not_cache,
                        )
                        if not todo.do_not_cache:
                            assert (
                                cache_key == alt_cache_key
                            ), "An internal error has occurred"
                        done = True

                    if done:
                        log(f"Target {todo} has been built fully!")
                        if not todo.do_not_cache:
                            outputs = cache_store_files(cache_key, todo, os.curdir)
                        else:
                            outputs = []
                            for out in todo.outputs:
                                if out.endswith("/"):
                                    outputs.extend(
                                        os.path.join(path, filename)
                                        for path, subdirs, files in os.walk(out)
                                        for filename in files
                                    )
                                else:
                                    outputs.append(out)

                        if scratch_path.exists():
                            rmtree(scratch_path, ignore_errors=True)

                if done:
                    waiting_for_deferred = enqueue_deps(
                        build_state, todo, deferred_deps
                    )
                    # We have to wait for deferred dependencies to build because the TransitiveOutputProvider
                    # needs them to complete. However, we can run the build itself before the deferred deps finish.
                    if waiting_for_deferred:
                        log(
                            f"Target {todo} has been built, but is waiting for deferred dependencies"
                        )
                    else:
                        todo.set_provided_value(
                            provided_value, build_state, deps, deferred_deps, outputs
                        )
                        with build_state.scheduling_lock:
                            build_state.ready.add(todo)
                            # no one will ever add us back, since we are in `ready`
                            build_state.scheduled_but_not_ready.remove(todo)
                            # now it's time to set up our dependents
                            # we need to be inside the lock even if we have no dependents, in case
                            # we *gain* dependents from another thread which could have held the lock!
                            for dependent in todo.runtime_dependents:
                                dependent.pending_rule_dependencies.remove(todo)
                                if not dependent.pending_rule_dependencies:
                                    # this guy is ready to go
                                    build_state.work_queue.put(dependent)
                                    build_state.status_monitor.move(total=1)

            # either way, we're done with this task for now
            build_state.status_monitor.move(curr=1)
            build_state.work_queue.task_done()

            # record timing data
            run_time = time.time() - start_time
            TIMINGS[str(todo)] += run_time
        except Exception as e:
            if isinstance(e, BuildException):
                suffix = f"\n{Style.RESET_ALL}"
            else:
                suffix = f"\n{Style.RESET_ALL}" + traceback.format_exc()
            build_state.status_monitor.stop()
            build_state.failure = BuildException(
                f"Error while executing rule {todo}: " + str(e) + suffix
            )
            build_state.work_queue.task_done()
Пример #12
0
def initialize_workspace(
    setup_rule_lookup: TargetLookup,
    setup_target: str,
    state_directory: str,
    quiet: bool,
):
    # we don't need the indirect lookup as we only have rule and source deps
    direct_lookup: Dict[str, Rule] = setup_rule_lookup.direct_lookup

    if setup_target not in direct_lookup:
        raise BuildException(
            f"Unknown or unspecified setup target {setup_target}")

    rebuilt: Set[str] = set()
    ready: Set[str] = set()
    work_queue = [direct_lookup[setup_target]]

    cache_fetcher, _ = make_cache_fetcher(state_directory)
    cache_memorize, _ = make_cache_memorize(state_directory)

    status_monitor = create_status_monitor(1, quiet)
    status_monitor.move(total=1)

    while work_queue:
        todo = work_queue.pop()
        log(f"Popping setup rule {todo} off work queue")
        hashstate = HashState()
        ctx = WorkspaceExecutionContext(hashstate)
        unchecked_rules = []
        for dep in todo.deps:
            hashstate.record(dep)
            if dep.startswith(":"):
                if dep not in direct_lookup:
                    raise BuildException(f"Unable to find setup rule {dep}")
                dep_rule = direct_lookup[dep]
                if dep_rule not in ready:
                    unchecked_rules.append(dep_rule)
                    continue
                ctx.deps[dep] = dep_rule.provided_value
                setattr(ctx.deps, dep[1:], dep_rule.provided_value)
            else:
                try:
                    hashstate.update(hash_file(dep))
                except FileNotFoundError:
                    raise BuildException(f"Source file {dep} not found.")

        if unchecked_rules:
            for dep in unchecked_rules:
                if dep not in work_queue:
                    log(f"Setup rule {todo} is enqueuing {dep}")
                    status_monitor.move(total=1)
                    work_queue.append(dep)
                else:
                    log(f"Setup rule {todo} is waiting on {dep}, which is already enqueued"
                        )
                dep.runtime_dependents.add(todo)
                todo.pending_rule_dependencies.add(dep)
        else:
            # our dependent rules are ready, now we need to see if we need to rerun
            todo.provided_value = todo.impl(ctx)

            if todo.name is None:
                raise BuildException(
                    f"All setup rules must have names, but {todo} does not.")

            try:
                ok = cache_fetcher("workspace", todo.name) == hashstate.state()
                if not ok:
                    log(f"State mismatch for rule {todo}, need to rerun")
            except CacheMiss:
                log(f"State not found for rule {todo}, need to run for first time"
                    )
                ok = False

            for dep in todo.deps:
                if dep.startswith(":"):
                    if direct_lookup[dep] in rebuilt:
                        log(f"Dependency {dep} of setup rule {todo} was rebuilt, so we must rebuild {todo} as well"
                            )
                        ok = True

            for out in todo.outputs:
                if not os.path.exists(out):
                    log(f"Output {out} is missing for setup rule {todo}, forcing rerun"
                        )
                    ok = False
                    break

            if not ok:
                # we need to fully run
                ctx.run_shell_queue()
                rebuilt.add(todo)
                cache_memorize("workspace", todo.name, hashstate.state())

            # either way, now we can trigger our dependents
            ready.add(todo)
            for dep in todo.runtime_dependents:
                dep.pending_rule_dependencies.remove(todo)
                if not dep.pending_rule_dependencies:
                    work_queue.append(dep)
                    status_monitor.move(total=1)

        status_monitor.move(curr=1)
Пример #13
0
def get_deps(build_state: BuildState, rule: Rule):
    """
    Use static dependencies and caches to try and identify as *many*
    needed dependencies as possible, without *any* spurious dependencies.
    """
    hashstate = HashState()
    cache_fetcher, _ = make_cache_fetcher(build_state.cache_directory)
    dep_fetcher = make_dep_fetcher(build_state)

    ctx = PreviewContext(
        build_state.repo_root,
        rule.location,
        hashstate,
        dep_fetcher,
        cache_fetcher,
    )

    log(f"Looking for static dependencies of {rule}")
    for dep in rule.deps:
        if dep not in build_state.source_files:
            dep_rule = build_state.target_rule_lookup.lookup(build_state, dep)
            if dep_rule not in build_state.ready:
                log(f"Static dependency {dep} of {dep_rule} is not ready, skipping impl"
                    )
                # static deps are not yet ready
                break
            ctx.deps[dep] = dep_rule.provided_value
            if dep.startswith(":"):
                setattr(ctx.deps, dep[1:], dep_rule.provided_value)
                continue
        hashstate.update(dep.encode("utf-8"))
        try:
            hashstate.update(dep_fetcher(dep, get_hash=True))
        except MissingDependency:
            # get static deps before running the impl!
            # this means that a source file is *missing*, but the error will be thrown in enqueue_deps
            break
    else:
        ok = False
        try:
            log(f"Running impl of {rule} to discover dynamic dependencies")
            rule.provided_value = rule.impl(ctx)
            log(f"Impl of {rule} completed with discovered deps: {ctx.inputs}")
            for out in rule.outputs:
                # needed so that if we ask for another output, we don't panic if it's not in the cache
                hashstate.record(out)
            ok = True
        except CacheMiss:
            log(f"Cache miss while running impl of {rule}")
            pass  # stops context execution
        except MissingDependency as e:
            log(f"Dependencies {e.paths} were unavailable while running impl of {rule}"
                )
            pass  # dep already added to ctx.inputs
        except Exception as e:
            print(
                "Error occurred during PreviewExecution. This may be normal, if a cached file that has not "
                "yet been reported / processed has been changed. However, it may also be an internal error, so "
                "it is being logged here. If it is an internal error, please contact the maintainer."
            )
            print(repr(e))
        # if `ok`, hash loaded dynamic dependencies
        if ok:
            log(f"Runtime dependencies resolved for {rule}, now checking dynamic dependencies"
                )
            for input_path in ctx.inputs:
                if input_path.startswith(":"):
                    if input_path not in build_state.ready:
                        ok = False
                        log(f"Dynamic rule dependency {input_path} is not yet ready"
                            )
                        break
                else:
                    hashstate.update(input_path.encode("utf-8"))
                    try:
                        data = dep_fetcher(input_path, get_hash=True)
                    except MissingDependency as e:
                        # this dependency was not needed for deps calculation
                        # but is not verified to be up-to-date
                        ok = False
                        log(f"Dynamic dependencies {e.paths} were not needed for the impl, but are not up to date"
                            )
                        break
                    else:
                        hashstate.update(data)
        return (
            hashstate.state() if ok else None,
            ctx.inputs + rule.deps,
        )
    return None, rule.deps
Пример #14
0
def initialize_workspace(
    setup_rule_lookup: TargetLookup,
    setup_targets: List[str],
    state_directory: str,
    quiet: bool,
):
    # we don't need the indirect lookup as we only have rule and source deps
    direct_lookup: Dict[str, Rule] = setup_rule_lookup.direct_lookup
    work_queue = []
    for setup_target in setup_targets:
        if setup_target not in direct_lookup:
            raise BuildException(
                f"Unknown or unspecified setup target {setup_target}")
        work_queue.append(direct_lookup[setup_target])

    rebuilt: Set[str] = set()
    ready: Set[str] = set()

    cache_load_string, _ = make_cache_load(state_directory)
    cache_store_string, _ = make_cache_store(state_directory)

    if work_queue:
        status_monitor = create_status_monitor(1, quiet)
        status_monitor.move(total=len(work_queue))

    def dep_fetcher(dep):
        if dep.startswith(":"):
            if dep not in direct_lookup:
                raise BuildException(f"Unable to find setup rule {dep}")
            dep_rule = direct_lookup[dep]
            log(f"Looking up setup rule {dep}")
            if dep_rule not in ready:
                raise MissingDependency(dep)
            return dep_rule

    while work_queue:
        todo = work_queue.pop()
        log(f"Popping setup rule {todo} off work queue")
        try:
            if todo.name is None:
                raise BuildException(
                    f"All setup rules must have names, but {todo} does not.")

            hashstate = HashState()
            ctx = WorkspaceExecutionContext(hashstate, dep_fetcher)
            unchecked_rules = []

            try:
                todo.set_provided_value(
                    todo.impl(ctx),
                    None,
                    ctx.inputs,
                    ctx.deferred_inputs,
                    [],  # todo: implement output providers for setup rules
                )
                if ctx.out_of_date_deps:
                    raise MissingDependency(*ctx.out_of_date_deps)
            except MissingDependency as e:
                unchecked_rules = [direct_lookup[x] for x in e.paths]

            if unchecked_rules:
                for dep in unchecked_rules:
                    if dep not in work_queue:
                        log(f"Setup rule {todo} is enqueuing {dep}")
                        status_monitor.move(total=1)
                        work_queue.append(dep)
                    else:
                        log(f"Setup rule {todo} is waiting on {dep}, which is already enqueued"
                            )
                    dep.runtime_dependents.add(todo)
                    todo.pending_rule_dependencies.add(dep)
            else:
                log(f"Setup rule {todo} ran with inputs {ctx.inputs + ctx.deferred_inputs}"
                    )
                for dep in ctx.inputs + ctx.deferred_inputs:
                    if dep.startswith(":"):
                        continue
                    try:
                        hashstate.record(dep)
                        hashstate.update(hash_file(dep))
                    except FileNotFoundError:
                        raise BuildException(f"Source file {dep} not found.")

                try:
                    ok = cache_load_string("workspace",
                                           todo.name) == hashstate.state()
                    if not ok:
                        log(f"State mismatch for rule {todo}, need to rerun")
                except CacheMiss:
                    log(f"State not found for rule {todo}, need to run for first time"
                        )
                    ok = False

                for dep in ctx.inputs + ctx.deferred_inputs:
                    if dep.startswith(":"):
                        if direct_lookup[dep] in rebuilt:
                            log(f"Dependency {dep} of setup rule {todo} was rebuilt, so we must rebuild {todo} as well"
                                )
                            ok = False

                for out in todo.outputs:
                    if not os.path.exists(out):
                        log(f"Output {out} is missing for setup rule {todo}, forcing rerun"
                            )
                        ok = False
                        break

                if not ok:
                    # we need to fully run
                    log(f"Fully running setup rule {todo}")
                    ctx.run_shell_queue()
                    rebuilt.add(todo)
                    cache_store_string("workspace", todo.name,
                                       hashstate.state())

                # either way, now we can trigger our dependents
                ready.add(todo)
                for dep in todo.runtime_dependents:
                    dep.pending_rule_dependencies.remove(todo)
                    if not dep.pending_rule_dependencies:
                        work_queue.append(dep)
                        status_monitor.move(total=1)

            status_monitor.move(curr=1)
        except Exception as e:
            if not isinstance(e, BuildException):
                suffix = f"\n{Style.RESET_ALL}" + traceback.format_exc()
            else:
                suffix = ""
            status_monitor.stop()
            raise BuildException(f"Error while executing rule {todo}: " +
                                 str(e) + suffix)
Пример #15
0
def get_deps(build_state: BuildState, rule: Rule, *, skip_cache_key: bool):
    """
    Use static dependencies and caches to try and identify as *many*
    needed dependencies as possible, without *any* spurious dependencies.
    """
    hashstate = HashState()
    cache_load_string, _ = make_cache_load(build_state.cache_directory)
    dep_fetcher = make_dep_fetcher(build_state)

    ctx = PreviewContext(
        rule.location,
        build_state.macros,
        hashstate,
        dep_fetcher,
        cache_load_string,
    )

    ok = False
    provided_value = None
    try:
        log(f"Running impl of {rule} to discover dependencies")
        if not skip_cache_key:
            for out in rule.outputs:
                # needed so that if we ask for another output, we don't panic if it's not in the cache
                hashstate.record(out)
        provided_value = rule.impl(ctx)
        log(f"Impl of {rule} completed with deps: {ctx.inputs}")
        ok = True
    except CacheMiss:
        log(f"Cache miss while running impl of {rule}")
        pass  # stops context execution
    except MissingDependency as e:
        log(f"Dependencies {e.paths} were unavailable while running impl of {rule}"
            )
        pass  # dep already added to ctx.inputs
    except Exception as e:
        print(
            "Error occurred during PreviewExecution. This may be normal, if a cached file that has not "
            "yet been reported / processed has been changed. However, it may also be an internal error, so "
            "it is being logged here. If it is an internal error, please contact the maintainer."
        )
        print(repr(e))
        if not ctx.uses_dynamic_inputs:
            raise
    # if `ok`, hash loaded dynamic dependencies
    if ok:
        log(f"Inputs and dependencies resolved for {rule}")
        for input_path in ctx.inputs:
            if input_path.startswith(":"):
                input_dep = build_state.target_rule_lookup.try_lookup(
                    input_path)
                if input_dep is None or input_dep not in build_state.ready:
                    ok = False
                    log(f"Rule dependency {input_path} is not yet ready (or does not exist)"
                        )
                    break
            else:
                if not skip_cache_key:
                    hashstate.update(input_path.encode("utf-8"))
                try:
                    data = dep_fetcher(input_path,
                                       "rule" if skip_cache_key else "hash")
                except MissingDependency as e:
                    # this dependency was not needed for deps calculation
                    # but is not verified to be up-to-date
                    ok = False
                    log(f"Dependencies {e.paths} were not needed for the impl, but are not up to date"
                        )
                    break
                else:
                    if not skip_cache_key:
                        hashstate.update(data)
        hashstate.record("done")

    return (
        hashstate.state() if ok else None,
        provided_value,
        ctx.inputs,
        ctx.deferred_inputs,
        ctx.uses_dynamic_inputs,
    )