Esempio n. 1
0
def clone_maven_repo(target, source):
    """Clones a Maven local repository deeply.

    Clones the entire source Maven local repository into the specified target
    directory. The clone is entirely isolated from the source repository, ie.
    new artifacts downloaded into the clone are not visible in the source
    repository.

    Args:
        target: Where to clone the source Maven repository.
        source: Source Maven repository to clone.
    """
    assert os.path.exists(source), ("Source Maven local repository %r does not exists.", source)

    assert not os.path.exists(target), ("Target Maven local repository %r already exists.", target)
    base.make_dir(target)
    assert os.path.exists(target), ("Could not create target directory %r" % target)

    for name in os.listdir(source):
        source_path = os.path.join(source, name)
        if os.path.isfile(source_path):
            os.symlink(src=source_path, dst=os.path.join(target, name))
        elif os.path.isdir(source_path):
            clone_maven_repo(target=os.path.join(target, name), source=source_path)
        else:
            logging.error("Unable to handle file: %s", source_path)
Esempio n. 2
0
def backport_updates_from_cloned_repo(clone, source):
    """Back-port updates in a cloned Maven repository to its source.

    Contributes new artifacts from the cloned repository back to the source
    repository, in a way that is thread-safe.
    This operation destroys the cloned repository.

    Args:
        clone: Maven repository to back-port from.
        source: Maven repository source of the clone.
    """
    assert os.path.exists(clone), ("Source Maven local repository %r does not exists.", clone)

    base.make_dir(source)

    for name in os.listdir(clone):
        clone_path = os.path.join(clone, name)
        source_path = os.path.join(source, name)
        if os.path.islink(clone_path):
            # Skip all symlinks
            continue
        elif os.path.isfile(clone_path):
            # Copy file instead of move?
            logging.debug("Back-porting %s as %s", clone_path, source_path)
            os.rename(src=clone_path, dst=source_path)
        elif os.path.isdir(clone_path):
            backport_updates_from_cloned_repo(source=source_path, clone=clone_path)
        else:
            logging.error("Unable to handle file: %s", clone_path)
Esempio n. 3
0
    def Get(self, group, artifact, version, type, classifier=None):
        """Retrieves an artifact locally.

        Deprecated, this method is going away: use get(artifact) instead.

        If the artifact is a snapshot (version ends with '-SNAPSHOT'),
        all remotes are checked for a newer version.

        Args:
            group: Artifact group ID.
            artifact: Artifact ID.
            version: Artifact version.
            type: Artifact type, aka. packaging ('jar', 'pom', etc).
            classifier: Optional classifier (eg. 'test' or 'release').
        Returns:
            The path of the artifact in the local repository.
            None if the artifact cannot be found.
        """
        coordinate = dict(
            group=group,
            artifact=artifact,
            version=version,
            type=type,
            classifier=classifier,
        )
        logging.log(LOG_LEVEL.DEBUG_VERBOSE,
                    "Getting artifact with coordinate %r", coordinate)
        path = self.local.GetPath(**coordinate)
        parsed = urllib.request.urlparse(self.local.get_url(**coordinate))
        assert (parsed.scheme == "file")
        local_path = parsed.path
        md5_path = "%s.md5" % local_path
        sha1_path = "%s.sha1" % local_path

        # Artifact is a snapshot, resolve it first if allowed:
        if version.endswith("-SNAPSHOT") and self._remote_snapshots:
            # Find the most recent snapshot version from all the remote repositories:

            def ScanRemotes():
                for remote in self.remotes:
                    resolved = remote.resolve(**coordinate)
                    if resolved is None: continue
                    # Allow for snapshots resolved in a local repository:
                    # if resolved["snapshot_version"] is None: continue
                    yield (resolved, remote)

            # Snapshot on remote repositories are expected to have a snapshot_version:
            best_remote = None
            best_version = dict(snapshot_version="", **coordinate)

            # Snapshot built locally into a local repository has no snapshot_version.
            # This lists the local repositories where unversioned snapshots are found:
            local_repos = list()

            for (resolved, remote) in ScanRemotes():
                if resolved["snapshot_version"] is None:
                    local_repos.append(remote)
                elif best_version["snapshot_version"] < resolved[
                        "snapshot_version"]:
                    best_remote = remote
                    best_version = resolved

            if (best_remote is None) and (len(local_repos) == 0):
                raise ArtifactNotFoundError(
                    "Artifact %s:%s:%s:%s:%s not found in remote repositories"
                    % (coordinate["group"], coordinate["artifact"],
                       coordinate["classifier"], coordinate["type"],
                       coordinate["version"]))
            elif len(local_repos) == 0:
                logging.debug("Artifact resolved to %s in remote %s",
                              best_version, best_remote)
            elif best_remote is None:
                assert (len(local_repos) == 1), \
                    ("Multiple snapshot local copies of %r" % coordinate)
                local_repo = local_repos[0]
                parsed = urllib.request.urlparse(
                    local_repo.get_url(**coordinate))
                assert (parsed.scheme == "file")
                local_path = parsed.path
                return local_path
            else:
                raise Error(
                    "Multiple snapshot copies in local repositories and remote repositories: %r"
                    % coordinate)

            (http_reply, md5, sha1) = best_remote.open(**best_version)
            try:
                # Do we have this snapshot artifact locally already:
                if (os.path.exists(local_path) and os.path.exists(md5_path)
                        and os.path.exists(sha1_path)
                        and md5 == self.local.read_md5_file(path)
                        and sha1 == self.local.read_sha1_file(path)
                        and (get_file_fingerprints(local_path)
                             == (self.local.read_md5_file(path),
                                 self.local.read_sha1_file(path)))):
                    logging.log(LOG_LEVEL.DEBUG_VERBOSE,
                                "Snapshot artifact found locally: %r",
                                local_path)
                    return local_path
            finally:
                http_reply.close()
            logging.log(LOG_LEVEL.DEBUG_VERBOSE,
                        "Snapshot artifact not found locally: %r", coordinate)
            remotes = (best_remote, )

        # Artifact is a snapshot but we do not allow remote snapshots:
        elif version.endswith("-SNAPSHOT") and not self._remote_snapshots:
            logging.log(LOG_LEVEL.DEBUG_VERBOSE,
                        "Restricting snapshot artifact to local FS: %r",
                        local_path)
            if os.path.exists(local_path):
                logging.log(LOG_LEVEL.DEBUG_VERBOSE,
                            "Local snapshot artifact found: %r", local_path)
                return local_path
            else:
                logging.debug("Local snapshot artifact not found: %r",
                              local_path)
                return None

        else:
            # Do we have this non-snapshot artifact locally already?
            if (os.path.exists(local_path) and
                (os.path.getsize(local_path) > 0)  # FIXME quick workaround
                    and os.path.exists(md5_path) and os.path.exists(sha1_path)
                    and (get_file_fingerprints(local_path)
                         == (self.local.read_md5_file(path),
                             self.local.read_sha1_file(path)))):
                # Yes, artifact found locally, with matching checksums:
                logging.log(
                    LOG_LEVEL.DEBUG_VERBOSE,
                    "Artifact found locally with matching checksums: %r",
                    local_path)
                return local_path
            else:
                # Look for the artifact in the configured remotes:
                logging.debug("Artifact not found locally: %r", local_path)
                remotes = self.remotes

        # Artifact does not exist locally.
        # Try each remote repository on after another,
        # pick the first that contains the artifact we are looking for:
        for remote in remotes:
            try:
                open_result = remote.open(**coordinate)
                if open_result is None:
                    continue

                (http_reply, md5, sha1) = open_result
                try:
                    base.make_dir(os.path.dirname(local_path))
                    (actual_md5, actual_sha1) = RemoteRepository.read_to_file(
                        http_reply=http_reply,
                        output_path=local_path,
                    )

                    if md5 is None:
                        logging.debug("No MD5 sum for %r from %s", local_path,
                                      remote.path)
                        md5 = actual_md5
                    elif md5 != actual_md5:
                        logging.error(
                            "MD5 mismatch for %r from %r: expected %r, got %r",
                            local_path, remote.path, md5, actual_md5)

                    if sha1 is None:
                        logging.debug("No SHA1 sum for %r from %s", local_path,
                                      remote.path)
                        sha1 = actual_sha1
                    elif sha1 != actual_sha1:
                        logging.error(
                            "SHA1 mismatch for %r from %r: expected %r, got %r",
                            local_path, remote.path, sha1, actual_sha1)

                    if (md5 == actual_md5) and (sha1 == actual_sha1):
                        logging.debug("Writing MD5 sum for %r", local_path)
                        with open(md5_path, "w") as f:
                            f.write(md5)
                        logging.debug("Writing SHA1 sum for %r", local_path)
                        with open(sha1_path, "w") as f:
                            f.write(sha1)
                        return local_path
                    else:
                        logging.warning("Checksum invalid for %r", local_path)
                        os.remove(local_path)
                finally:
                    http_reply.close()

            except urllib.error.HTTPError as err:
                logging.error("Error on remote %r: %r", remote,
                              err.readall().decode())

            except urllib.error.URLError as err:
                if isinstance(err.reason, FileNotFoundError):
                    logging.debug("File not found: %r", err.reason.filename)
                else:
                    logging.error("Error on remote %r: %r", remote, err)

        # Artifact is nowhere to be found:
        return None
Esempio n. 4
0
    def __init__(
        self,
        path,
        maven_repository=None,
    ):
        """Initializes a new workspace object.

        Args:
            path: Root path of the workspace.
            maven_repository: Optional explicit Maven repository where to search for artifacts.
        """
        self._path = os.path.abspath(path)
        assert os.path.exists(self.path), \
            "Workspace root directory does not exist: {!r}".format(self.path)

        assert os.path.isdir(self.config_dir), \
            "Workspace configuration directory missing: {!r}".format(self.config_dir)

        self._build_defs = build_defs.BuildDefs()

        # Process workspace configuration file:
        conf_file = os.path.join(self.config_dir, "conf.py")
        if os.path.exists(conf_file):
            logging.info("Loading configuration %r", conf_file)
            with open(conf_file, mode="rt", encoding="UTF-8") as f:
                conf_py = f.read()
            self._build_defs.eval(conf_py)

        # Process master BUILD file:
        build_file = os.path.join(self.path, "BUILD")
        if os.path.exists(build_file):
            logging.info("Loading build file %r", build_file)
            with open(build_file, mode="rt", encoding="UTF-8") as f:
                build_py = f.read()
            self._build_defs.eval(build_py)

        else:
            logging.info("BUILD file missing: %r", build_file)

        self._config = record.Record(self._build_defs.exec_locals)
        self.config.definitions = self._build_defs.definitions

        logging.debug("Using configuration: %r", self._config)

        # ------------------------------------------------------------------------------------------

        base.make_dir(self.output_dir)
        base.make_dir(self.temp_dir)
        base.make_dir(self.maven_local_repo)
        base.make_dir(os.path.join(self.temp_dir, "workflow"))
        logging.debug("Generating artifacts in: %s", self.maven_local_repo)

        # ------------------------------------------------------------------------------------------

        self._git = git_wrapper.Git(self.path)

        if maven_repository is None:
            remotes = self.config.get("maven_repositories", tuple())
            maven_repository = maven_repo.MavenRepository(
                local=self.maven_local_repo,
                remotes=remotes,
            )
            logging.debug("Using Maven repositories:\n%s",
                          base.add_margin("\n".join(remotes), "\t- "))
        self._maven_repo = maven_repository

        # Build toolkit:
        self._tools = build_tools.BuildTools(workspace=self)
        self.tools.validate()

        # Workflow generated after the build graph:
        self._workflow = None
Esempio n. 5
0
    def run(self, args):
        # Generate a unique ID for this build.
        build_id = "build-%d" % base.now_ms()

        # Determine which workspace to build in:
        workspace = self.workspace
        if self.flags.clone_workspace:
            base.make_dir(FLAGS.build_workspace_dir)
            temp_workspace = os.path.join(tempfile.mkdtemp(dir=FLAGS.build_workspace_dir), build_id)
            workspace = self.workspace.Clone(temp_workspace)

        targets = []
        if (self.flags.targets is not None) and (len(self.flags.targets) > 0):
            targets.extend(self.flags.targets.split(","))
        targets.extend(args)
        targets = frozenset(targets)
        self._targets = targets

        http_monitor = None
        if (self.flags.http_monitor is not None) and (len(self.flags.http_monitor) > 0):
            (interface, port) = base.parse_host_port(self.flags.http_monitor)
            try:
                http_monitor = workflow.WorkflowHTTPMonitor(interface=interface, port=port)
                http_monitor.start()
            except OSError as os_error:
                if (os_error.errno == 48) or (os_error.errno == 98):
                    raise Error(
                        "Address {!s}:{!s} already in use. Specify a different address "
                        "for the workflow http monitor with the --http-monitor flag."
                        .format(interface, port)
                    )
                else:
                    raise

        # Pass flags through the workspace config object.
        workspace.config.enable_incremental_testing = self.flags.enable_incremental_testing
        workspace.config.separate_test_logs = self.flags.separate_test_logs
        workspace.config.test_log_prefix = self.flags.test_log_prefix

        # Run the build:
        try:
            flow = workspace.make_workflow(force_build=self.flags.force_build)
            self._flow = flow

            self.adjust_workflow()
            flow = workspace.process(
                nworkers=self.flags.nworkers,
                http_monitor=http_monitor,
            )

            # TODO(DEV-429): refactor these flags, they are unwieldy
            if self.flags.output_dot is not None:
                with open(self.flags.output_dot, "wt", encoding="UTF-8") as dot_file:
                    dot_file.write(flow.dump_as_dot())

            if self.flags.output_svg is not None:
                with open(self.flags.output_svg, "wt", encoding="UTF-8") as svg_file:
                    svg_file.write(flow.dump_as_svg())

            if self.flags.output_table is not None:
                with open(self.flags.output_table, "wt", encoding="UTF-8") as table_file:
                    table_file.write(flow.dump_state_as_table())

            return len(flow.failed_tasks)  # 0 means success, non 0 means failure

        finally:
            if self.flags.clone_workspace and self.flags.delete_cloned_workspace:
                logging.info("Deleting cloned workspace %s", temp_workspace)
                shutil.rmtree(path=temp_workspace)
Esempio n. 6
0
    def run(self, args):
        # Generate a unique ID for this build.
        build_id = "build-%d" % base.now_ms()

        # Determine which workspace to build in:
        workspace = self.workspace
        if self.flags.clone_workspace:
            base.make_dir(FLAGS.build_workspace_dir)
            temp_workspace = os.path.join(
                tempfile.mkdtemp(dir=FLAGS.build_workspace_dir), build_id)
            workspace = self.workspace.Clone(temp_workspace)

        targets = []
        if (self.flags.targets is not None) and (len(self.flags.targets) > 0):
            targets.extend(self.flags.targets.split(","))
        targets.extend(args)
        targets = frozenset(targets)
        self._targets = targets

        http_monitor = None
        if (self.flags.http_monitor
                is not None) and (len(self.flags.http_monitor) > 0):
            (interface, port) = base.parse_host_port(self.flags.http_monitor)
            try:
                http_monitor = workflow.WorkflowHTTPMonitor(
                    interface=interface, port=port)
                http_monitor.start()
            except OSError as os_error:
                if (os_error.errno == 48) or (os_error.errno == 98):
                    raise Error(
                        "Address {!s}:{!s} already in use. Specify a different address "
                        "for the workflow http monitor with the --http-monitor flag."
                        .format(interface, port))
                else:
                    raise

        # Pass flags through the workspace config object.
        workspace.config.enable_incremental_testing = self.flags.enable_incremental_testing
        workspace.config.separate_test_logs = self.flags.separate_test_logs
        workspace.config.test_log_prefix = self.flags.test_log_prefix

        # Run the build:
        try:
            flow = workspace.make_workflow(force_build=self.flags.force_build)
            self._flow = flow

            self.adjust_workflow()
            flow = workspace.process(
                nworkers=self.flags.nworkers,
                http_monitor=http_monitor,
            )

            # TODO(DEV-429): refactor these flags, they are unwieldy
            if self.flags.output_dot is not None:
                with open(self.flags.output_dot, "wt",
                          encoding="UTF-8") as dot_file:
                    dot_file.write(flow.dump_as_dot())

            if self.flags.output_svg is not None:
                with open(self.flags.output_svg, "wt",
                          encoding="UTF-8") as svg_file:
                    svg_file.write(flow.dump_as_svg())

            if self.flags.output_table is not None:
                with open(self.flags.output_table, "wt",
                          encoding="UTF-8") as table_file:
                    table_file.write(flow.dump_state_as_table())

            return len(
                flow.failed_tasks)  # 0 means success, non 0 means failure

        finally:
            if self.flags.clone_workspace and self.flags.delete_cloned_workspace:
                logging.info("Deleting cloned workspace %s", temp_workspace)
                shutil.rmtree(path=temp_workspace)
Esempio n. 7
0
    def Get(self, group, artifact, version, type, classifier=None):
        """Retrieves an artifact locally.

        Deprecated, this method is going away: use get(artifact) instead.

        If the artifact is a snapshot (version ends with '-SNAPSHOT'),
        all remotes are checked for a newer version.

        Args:
            group: Artifact group ID.
            artifact: Artifact ID.
            version: Artifact version.
            type: Artifact type, aka. packaging ('jar', 'pom', etc).
            classifier: Optional classifier (eg. 'test' or 'release').
        Returns:
            The path of the artifact in the local repository.
            None if the artifact cannot be found.
        """
        coordinate = dict(group=group, artifact=artifact, version=version, type=type, classifier=classifier)
        logging.log(LOG_LEVEL.DEBUG_VERBOSE, "Getting artifact with coordinate %r", coordinate)
        path = self.local.GetPath(**coordinate)
        parsed = urllib.request.urlparse(self.local.get_url(**coordinate))
        assert parsed.scheme == "file"
        local_path = parsed.path
        md5_path = "%s.md5" % local_path
        sha1_path = "%s.sha1" % local_path

        # Artifact is a snapshot, resolve it first if allowed:
        if version.endswith("-SNAPSHOT") and self._remote_snapshots:
            # Find the most recent snapshot version from all the remote repositories:

            def ScanRemotes():
                for remote in self.remotes:
                    resolved = remote.resolve(**coordinate)
                    if resolved is None:
                        continue
                    # Allow for snapshots resolved in a local repository:
                    # if resolved["snapshot_version"] is None: continue
                    yield (resolved, remote)

            # Snapshot on remote repositories are expected to have a snapshot_version:
            best_remote = None
            best_version = dict(snapshot_version="", **coordinate)

            # Snapshot built locally into a local repository has no snapshot_version.
            # This lists the local repositories where unversioned snapshots are found:
            local_repos = list()

            for (resolved, remote) in ScanRemotes():
                if resolved["snapshot_version"] is None:
                    local_repos.append(remote)
                elif best_version["snapshot_version"] < resolved["snapshot_version"]:
                    best_remote = remote
                    best_version = resolved

            if (best_remote is None) and (len(local_repos) == 0):
                raise ArtifactNotFoundError(
                    "Artifact %s:%s:%s:%s:%s not found in remote repositories"
                    % (
                        coordinate["group"],
                        coordinate["artifact"],
                        coordinate["classifier"],
                        coordinate["type"],
                        coordinate["version"],
                    )
                )
            elif len(local_repos) == 0:
                logging.debug("Artifact resolved to %s in remote %s", best_version, best_remote)
            elif best_remote is None:
                assert len(local_repos) == 1, "Multiple snapshot local copies of %r" % coordinate
                local_repo = local_repos[0]
                parsed = urllib.request.urlparse(local_repo.get_url(**coordinate))
                assert parsed.scheme == "file"
                local_path = parsed.path
                return local_path
            else:
                raise Error("Multiple snapshot copies in local repositories and remote repositories: %r" % coordinate)

            (http_reply, md5, sha1) = best_remote.open(**best_version)
            try:
                # Do we have this snapshot artifact locally already:
                if (
                    os.path.exists(local_path)
                    and os.path.exists(md5_path)
                    and os.path.exists(sha1_path)
                    and md5 == self.local.read_md5_file(path)
                    and sha1 == self.local.read_sha1_file(path)
                    and (
                        get_file_fingerprints(local_path)
                        == (self.local.read_md5_file(path), self.local.read_sha1_file(path))
                    )
                ):
                    logging.log(LOG_LEVEL.DEBUG_VERBOSE, "Snapshot artifact found locally: %r", local_path)
                    return local_path
            finally:
                http_reply.close()
            logging.log(LOG_LEVEL.DEBUG_VERBOSE, "Snapshot artifact not found locally: %r", coordinate)
            remotes = (best_remote,)

        # Artifact is a snapshot but we do not allow remote snapshots:
        elif version.endswith("-SNAPSHOT") and not self._remote_snapshots:
            logging.log(LOG_LEVEL.DEBUG_VERBOSE, "Restricting snapshot artifact to local FS: %r", local_path)
            if os.path.exists(local_path):
                logging.log(LOG_LEVEL.DEBUG_VERBOSE, "Local snapshot artifact found: %r", local_path)
                return local_path
            else:
                logging.debug("Local snapshot artifact not found: %r", local_path)
                return None

        else:
            # Do we have this non-snapshot artifact locally already?
            if (
                os.path.exists(local_path)
                and (os.path.getsize(local_path) > 0)  # FIXME quick workaround
                and os.path.exists(md5_path)
                and os.path.exists(sha1_path)
                and (
                    get_file_fingerprints(local_path)
                    == (self.local.read_md5_file(path), self.local.read_sha1_file(path))
                )
            ):
                # Yes, artifact found locally, with matching checksums:
                logging.log(LOG_LEVEL.DEBUG_VERBOSE, "Artifact found locally with matching checksums: %r", local_path)
                return local_path
            else:
                # Look for the artifact in the configured remotes:
                logging.debug("Artifact not found locally: %r", local_path)
                remotes = self.remotes

        # Artifact does not exist locally.
        # Try each remote repository on after another,
        # pick the first that contains the artifact we are looking for:
        for remote in remotes:
            try:
                open_result = remote.open(**coordinate)
                if open_result is None:
                    continue

                (http_reply, md5, sha1) = open_result
                try:
                    base.make_dir(os.path.dirname(local_path))
                    (actual_md5, actual_sha1) = RemoteRepository.read_to_file(
                        http_reply=http_reply, output_path=local_path
                    )

                    if md5 is None:
                        logging.debug("No MD5 sum for %r from %s", local_path, remote.path)
                        md5 = actual_md5
                    elif md5 != actual_md5:
                        logging.error(
                            "MD5 mismatch for %r from %r: expected %r, got %r", local_path, remote.path, md5, actual_md5
                        )

                    if sha1 is None:
                        logging.debug("No SHA1 sum for %r from %s", local_path, remote.path)
                        sha1 = actual_sha1
                    elif sha1 != actual_sha1:
                        logging.error(
                            "SHA1 mismatch for %r from %r: expected %r, got %r",
                            local_path,
                            remote.path,
                            sha1,
                            actual_sha1,
                        )

                    if (md5 == actual_md5) and (sha1 == actual_sha1):
                        logging.debug("Writing MD5 sum for %r", local_path)
                        with open(md5_path, "w") as f:
                            f.write(md5)
                        logging.debug("Writing SHA1 sum for %r", local_path)
                        with open(sha1_path, "w") as f:
                            f.write(sha1)
                        return local_path
                    else:
                        logging.warning("Checksum invalid for %r", local_path)
                        os.remove(local_path)
                finally:
                    http_reply.close()

            except urllib.error.HTTPError as err:
                logging.error("Error on remote %r: %r", remote, err.readall().decode())

            except urllib.error.URLError as err:
                if isinstance(err.reason, FileNotFoundError):
                    logging.debug("File not found: %r", err.reason.filename)
                else:
                    logging.error("Error on remote %r: %r", remote, err)

        # Artifact is nowhere to be found:
        return None
Esempio n. 8
0
    def __init__(
        self,
        path,
        maven_repository=None,
    ):
        """Initializes a new workspace object.

        Args:
            path: Root path of the workspace.
            maven_repository: Optional explicit Maven repository where to search for artifacts.
        """
        self._path = os.path.abspath(path)
        assert os.path.exists(self.path), \
            "Workspace root directory does not exist: {!r}".format(self.path)

        assert os.path.isdir(self.config_dir), \
            "Workspace configuration directory missing: {!r}".format(self.config_dir)

        self._build_defs = build_defs.BuildDefs()

        # Process workspace configuration file:
        conf_file = os.path.join(self.config_dir, "conf.py")
        if os.path.exists(conf_file):
            logging.info("Loading configuration %r", conf_file)
            with open(conf_file, mode="rt", encoding="UTF-8") as f:
                conf_py = f.read()
            self._build_defs.eval(conf_py)

        # Process master BUILD file:
        build_file = os.path.join(self.path, "BUILD")
        if os.path.exists(build_file):
            logging.info("Loading build file %r", build_file)
            with open(build_file, mode="rt", encoding="UTF-8") as f:
                build_py = f.read()
            self._build_defs.eval(build_py)

        else:
            logging.info("BUILD file missing: %r", build_file)

        self._config = record.Record(self._build_defs.exec_locals)
        self.config.definitions = self._build_defs.definitions

        logging.debug("Using configuration: %r", self._config)

        # ------------------------------------------------------------------------------------------

        base.make_dir(self.output_dir)
        base.make_dir(self.temp_dir)
        base.make_dir(self.maven_local_repo)
        base.make_dir(os.path.join(self.temp_dir, "workflow"))
        logging.debug("Generating artifacts in: %s", self.maven_local_repo)

        # ------------------------------------------------------------------------------------------

        self._git = git_wrapper.Git(self.path)

        if maven_repository is None:
            remotes = self.config.get("maven_repositories", tuple())
            maven_repository = maven_repo.MavenRepository(
                local=self.maven_local_repo,
                remotes=remotes,
            )
            logging.debug("Using Maven repositories:\n%s",
                          base.add_margin("\n".join(remotes), "\t- "))
        self._maven_repo = maven_repository

        # Build toolkit:
        self._tools = build_tools.BuildTools(workspace=self)
        self.tools.validate()

        # Workflow generated after the build graph:
        self._workflow = None