<reponame>kigero/rules_haxe<gh_stars>0
"""
Haxe utility functions.
"""

load(":providers.bzl", "HaxeLibraryInfo", "HaxeProjectInfo")

def determine_source_root(path):
    """
    Determine the source root for a given path, based on whether the path is in the external directory.

    Args:
        path: The path to check.

    Returns:
        The source root for the path.
    """
    source_root = ""
    parts = path.split("/")
    for idx in range(len(parts)):
        if parts[idx] == "external":
            source_root += "external/{}/".format(parts[idx + 1])
    return source_root

def _determine_classpath(classpaths, path):
    classpath = determine_source_root(path)
    if classpath == "":
        for cp in classpaths:
            cp_idx = path.find(cp)
            if cp_idx > 0:
                classpath = path[0:cp_idx]
    return classpath

def find_direct_sources(ctx):
    """
    Finds the direct sources of the given context.

    Args:
        ctx: The bazel context.

    Returns:
        An array of source files.
    """
    rtrn = []
    if hasattr(ctx.files, "srcs"):
        rtrn += ctx.files.srcs

    if hasattr(ctx.attr, "deps"):
        for dep in ctx.attr.deps:
            haxe_dep = dep[HaxeProjectInfo]
            if haxe_dep == None:
                continue
            if hasattr(haxe_dep, "srcs"):
                rtrn += haxe_dep.srcs

    return rtrn

def find_direct_docsources(ctx):
    """
    Finds the direct document sources of the given context.

    Args:
        ctx: The bazel context.

    Returns:
        An array of document source files.
    """
    rtrn = []
    if hasattr(ctx.files, "doc_srcs"):
        rtrn += ctx.files.doc_srcs

    for dep in ctx.attr.deps:
        haxe_dep = dep[HaxeProjectInfo]
        if haxe_dep == None:
            continue
        if hasattr(haxe_dep, "doc_srcs"):
            rtrn += haxe_dep.doc_srcs

    return rtrn

def find_direct_resources(ctx):
    """
    Finds the direct resources of the given context.

    Args:
        ctx: The bazel context.

    Returns:
        An array of resource files.
    """
    rtrn = []
    if hasattr(ctx.files, "resources"):
        rtrn += ctx.files.resources

    if hasattr(ctx.attr, "deps"):
        for dep in ctx.attr.deps:
            haxe_dep = dep[HaxeProjectInfo]
            if haxe_dep == None:
                continue
            if hasattr(haxe_dep, "resources"):
                rtrn += haxe_dep.resources

    return rtrn

def find_library_name(ctx):
    """
    Determines the library name, taking into account any dependent HaxeProjectInfos.

    Args:
        ctx: The bazel context.

    Returns:
        The specified library name.
    """
    if hasattr(ctx.attr, "library_name") and ctx.attr.library_name != "":
        return ctx.attr.library_name
    elif hasattr(ctx.attr, "executable_name") and ctx.attr.executable_name != "":
        return ctx.attr.executable_name
    elif hasattr(ctx.attr, "deps"):
        for dep in ctx.attr.deps:
            haxe_dep = dep[HaxeProjectInfo]
            if haxe_dep == None:
                continue
            if haxe_dep.library_name != None and haxe_dep.library_name != "":
                return haxe_dep.library_name

    return ctx.attr.name

def find_main_class(ctx):
    """
    Determines the main class, taking into account any dependant HaxeProjectInfos.

    Args:
        ctx: The bazel context.

    Returns:
        The specified main class.
    """
    if hasattr(ctx.attr, "main_class") and ctx.attr.main_class != "":
        return ctx.attr.main_class
    else:
        for dep in ctx.attr.deps:
            haxe_dep = dep[HaxeProjectInfo]
            if haxe_dep == None:
                continue
            if hasattr(haxe_dep, "main_class") and haxe_dep.main_class != "":
                return haxe_dep.main_class

    return None

def create_hxml_map(ctx, toolchain, for_test = False, for_std_build = False):
    """
    Create a dict containing haxe build parameters based on the input attributes from the calling rule.

    Args:
        ctx: Bazel context.
        toolchain: The Haxe toolchain instance.
        for_test: True if build parameters for unit testing should be added, False otherwise.
        for_std_build: True if build parameters for the standard build should be added, False otherwise.

    Returns:
        A dict containing the HXML properties.
    """
    hxml = {}

    package = ctx.label.package + "/" if ctx.label.package != "" else ""
    hxml["package"] = package

    hxml["for_test"] = for_test

    hxml["target"] = ctx.attr.target if hasattr(ctx.attr, "target") else None
    hxml["debug"] = ctx.attr.debug if hasattr(ctx.attr, "debug") else False
    hxml["name"] = "std-{}".format(hxml["target"]) if for_std_build else find_library_name(ctx)

    if for_test:
        hxml["main_class"] = "MainTest"
    elif for_std_build:
        hxml["main_class"] = "StdBuild"
    else:
        hxml["main_class"] = find_main_class(ctx)

    hxml["args"] = list()
    if hasattr(ctx.attr, "extra_args"):
        for arg in ctx.attr.extra_args:
            if not arg in hxml["args"]:
                hxml["args"].append(arg)

    hxml["libs"] = dict()
    if hxml["target"] == "java":
        hxml["libs"]["hxjava"] = toolchain.haxelib_language_versions["hxjava"]
    elif hxml["target"] == "cpp":
        hxml["libs"]["hxcpp"] = toolchain.haxelib_language_versions["hxcpp"]
        if ctx.var["TARGET_CPU"].startswith("x64") and not "-D HXCPP_M64" in hxml["args"]:
            hxml["args"].append("-D HXCPP_M64")

    if hasattr(ctx.attr, "haxelibs"):
        for lib in ctx.attr.haxelibs:
            version = ctx.attr.haxelibs[lib]
            if version != None and version != "":
                if version.lower().find("http") == 0:
                    version = "git:{}".format(version)
                hxml["libs"][lib] = version
            else:
                fail("Explicit versioning is required for haxelibs.")

    hxml["classpaths"] = list()
    hxml["classpaths"].append("src/main/haxe")
    if for_test:
        hxml["classpaths"].append(ctx.var["BINDIR"])
        hxml["classpaths"].append("{}/{}".format(ctx.var["BINDIR"], package))
        hxml["classpaths"].append("src/test/haxe")
        hxml["classpaths"].append("{}src/test/haxe".format(package))
    if hasattr(ctx.attr, "classpaths"):
        for p in ctx.attr.classpaths:
            hxml["classpaths"].append(p)

    hxml["source_files"] = list()
    for src in find_direct_sources(ctx):
        hxml["source_files"].append(src.path)

    hxml["resources"] = dict()
    for resource in find_direct_resources(ctx):
        name = resource.path
        name = name.replace("src/main/resources/", "")
        name = name.replace("src/test/resources/", "")
        parts = name.split("/")
        new_name = ""
        skip = False
        for idx in range(len(parts)):
            if skip:
                skip = False
                continue
            elif parts[idx] == "external":
                new_name = ""
                skip = True
            elif parts[idx] != "":
                if new_name != "":
                    new_name += "/"
                new_name += parts[idx]

        hxml["resources"][resource.path] = new_name

    hxml["c-args"] = list()
    if hxml["target"] == "java":
        if "haxe_java_target_version" in ctx.var:
            hxml["c-args"] += ["-source", ctx.var["haxe_java_target_version"], "-target", ctx.var["haxe_java_target_version"]]

    # Handle Dependencies
    if hasattr(ctx.attr, "deps"):
        for dep in ctx.attr.deps:
            haxe_dep = dep[HaxeLibraryInfo]
            if haxe_dep == None or haxe_dep.hxml == None:
                continue
            dep_hxml = haxe_dep.hxml
            for classpath in dep_hxml["classpaths"]:
                if classpath.startswith("external"):
                    parts = classpath.split("/")
                    new_classpath = ""
                    for idx in range(len(parts)):
                        if parts[idx] == "external":
                            new_classpath = "external"
                        elif parts[idx] != "":
                            new_classpath += "/" + parts[idx]

                    if not new_classpath in hxml["classpaths"]:
                        hxml["classpaths"].append(new_classpath)
                else:
                    calculated_classpath = _determine_classpath(dep_hxml["classpaths"], dep_hxml["source_files"][0]) if len(dep_hxml["source_files"]) != 0 else ""
                    if calculated_classpath == dep_hxml["package"]:
                        calculated_classpath = ""
                    hxml["classpaths"].append("{}{}{}".format(calculated_classpath, dep_hxml["package"], classpath))
            for lib in dep_hxml["libs"]:
                if not lib in hxml["libs"]:
                    hxml["libs"][lib] = dep_hxml["libs"][lib]
            for resource in dep_hxml["resources"]:
                if not resource in hxml["resources"]:
                    hxml["resources"][resource] = dep_hxml["resources"][resource]
            for arg in dep_hxml["args"]:
                if not arg in hxml["args"]:
                    hxml["args"].append(arg)

    is_external = ctx.label.workspace_root.startswith("external")
    hxml["external_dir"] = "external/{}/".format(hxml["name"]) if is_external else ""

    return hxml

def create_build_hxml(ctx, toolchain, hxml, out_file, suffix = "", for_exec = False):
    """
    Create the build.hxml file based on the input hxml dict.

    Any Haxelibs that are specified in the hxml will be installed at this time.

    Args:
        ctx: Bazel context.
        toolchain: The Haxe toolchain instance.
        hxml: A dict containing HXML parameters; should be generated from `_create_hxml_map`.
        out_file: The output file that the build.hxml should be written to.
        suffix: Optional suffix to append to the build parameters.
        for_exec: Whether this build HXML is intended for executing the result of the build; this can ignore some errors
            that aren't an issue during execution.
    """

    # Determine if we're in a dependant build.
    if for_exec or len(hxml["source_files"]) == 0 or len(ctx.files.srcs) == 0:
        is_dependent_build = True
    else:
        is_dependent_build = hxml["source_files"][0].startswith("external")

    # An empty source root seems to cover the use cases that are currently in use; this may need to be revisited, but
    # will require unit test cases!
    source_root = ""

    content = ""

    package = ctx.label.package + "/" if ctx.label.package != "" else ""

    # Target

    hxml["output_dir"] = "{}{}".format(ctx.attr.name, suffix)
    hxml["build_file"] = "{}/{}{}{}/{}".format(ctx.var["BINDIR"], hxml["external_dir"], package, hxml["output_dir"], hxml["name"])
    ext = ""
    if hxml["target"] != "":
        if hxml["target"] == "neko":
            ext = ".n"
            hxml["output_file"] = "{}.n".format(hxml["name"], suffix)
        elif hxml["target"] == "python":
            ext = ".py"
            hxml["output_file"] = "{}.py".format(hxml["name"], suffix)
        elif hxml["target"] == "php":
            hxml["output_file"] = "{}".format(hxml["name"], suffix)
        elif hxml["target"] == "cpp":
            output = "{}/".format(hxml["name"])
            output_file = ""
            if not for_exec:
                output_file += "lib"

            if hxml["main_class"] != None:
                mc = hxml["main_class"]
                if "." in mc:
                    mc = mc[mc.rindex(".") + 1:]

                output_file += "{}".format(mc)
            else:
                output_file += "{}".format(hxml["name"])

            if hxml["debug"] != None:
                output_file += "-debug"

            found_output_file = False
            for arg in hxml["args"]:
                if arg.lower().startswith("-d haxe_output_file"):
                    found_output_file = True
            if not found_output_file:
                hxml["args"].append("-D HAXE_OUTPUT_FILE={}".format(output_file))

            if for_exec:
                output_file += ".exe"
            elif "-D dll_link" in hxml["args"]:
                output_file += ".dll"
            else:
                output_file += ".lib"

            hxml["output_file"] = output + output_file
        elif hxml["target"] == "java":
            output = "{}".format(hxml["name"])
            if hxml["main_class"] != None:
                mc = hxml["main_class"]
                if "." in mc:
                    mc = mc[mc.rindex(".") + 1:]

                output += "/{}".format(mc)
            else:
                output += "/{}".format(hxml["name"])

            if hxml["debug"] != None:
                output += "-Debug"

            hxml["output_file"] = output + ".jar"
        elif hxml["target"] == "js":
            ext = ".js"
            hxml["output_file"] = "{}.js".format(hxml["name"], suffix)

        content += "--{} {}{}\n".format(hxml["target"], hxml["build_file"], ext)

    # Debug
    if hxml["debug"] != None:
        content += "-debug\n"

    # Classpaths
    for classpath in hxml["classpaths"]:
        if not classpath.startswith("external"):
            classpath = "{}{}".format(source_root, classpath)
        content += "-p {}\n".format(classpath)

    # Compiler Args
    for c_arg in hxml["c-args"]:
        content += "--c-arg {}\n".format(c_arg)

    # User Args
    for arg in hxml["args"]:
        content += "{}\n".format(arg)

    # Resources
    for path in hxml["resources"]:
        content += "--resource {}@{}\n".format(path, hxml["resources"][path])

    # Source or Main files
    if hxml["main_class"] != None:
        content += "-m {}\n".format(hxml["main_class"])
    else:
        for path in hxml["source_files"]:
            if is_dependent_build:
                path = path[len(source_root):]
            for classpath in hxml["classpaths"]:
                if path.startswith(classpath):
                    path = path[len(classpath) + 1:]
                    break
            content += path.replace(".hx", "").replace("/", ".") + "\n"

    count = 1
    build_files = list()

    if hxml["for_test"]:
        build_prefix = "{}-build-test.hxml".format(ctx.attr.name)
        path_prefix = "{}-path-test".format(ctx.attr.name)
    else:
        build_prefix = "{}-build.hxml".format(ctx.attr.name)
        path_prefix = "{}-path".format(ctx.attr.name)

    build_file_1 = ctx.actions.declare_file("{}.{}".format(build_prefix, count))
    count += 1
    build_files.append(build_file_1)

    for lib in hxml["libs"]:
        version = hxml["libs"][lib]
        content += "-L {}:{}\n".format(lib, version)

        path_file = toolchain.haxelib_install(
            ctx,
            lib,
            version,
        )
        count += 1
        build_files.append(path_file)

    # Write to a temporary file, then use run_shell to generate the required output file so that all the previous tasks are complete.
    ctx.actions.write(
        output = build_file_1,
        content = content,
    )

    ctx.actions.run_shell(
        outputs = [out_file],
        inputs = build_files,
        command = "mv {} {}".format(build_file_1.path, out_file.path),
    )

def calc_provider_response(ctx, toolchain, hxml, out_dir, launcher_file = None, output_file = None, library_name = None):
    """
    Determine an appropriate provider response based on the input context and the compilation target.

    Args:
        ctx: The bazel context.
        toolchain: The Haxe toolchain.
        hxml: The HXML dictionary.
        out_dir: The output directory.
        launcher_file: The launcher file to run, if there is one.
        output_file: The output file, if there is one.
        library_name: The name of the library, overrides the ctx variables.

    Returns:
        An array of providers.
    """
    runfiles = [out_dir]
    if launcher_file != None:
        runfiles.append(launcher_file)
        runfiles += toolchain.internal.tools

    haxe_deps_lib_direct = []
    haxe_deps_lib_transitive = []
    haxe_deps_proj_direct = []
    haxe_deps_proj_transitive = []
    if hasattr(ctx.attr, "deps"):
        haxe_deps_lib_direct = [dep[HaxeLibraryInfo] for dep in ctx.attr.deps]
        haxe_deps_lib_transitive = [dep[HaxeLibraryInfo].deps for dep in ctx.attr.deps]
        haxe_deps_proj_direct = [dep[HaxeProjectInfo] for dep in ctx.attr.deps]
        haxe_deps_proj_transitive = [dep[HaxeProjectInfo].deps for dep in ctx.attr.deps]

    srcs = []
    if hasattr(ctx.files, "srcs"):
        srcs = ctx.files.srcs

    resources = []
    if hasattr(ctx.files, "resources"):
        resources = ctx.files.resources

    rtrn = [
        DefaultInfo(
            files = depset([out_dir] + find_direct_sources(ctx)),
            runfiles = ctx.runfiles(files = runfiles),
            executable = launcher_file,
        ),
        HaxeLibraryInfo(
            lib = out_dir,
            hxml = hxml,
            deps = depset(
                direct = haxe_deps_lib_direct,
                transitive = haxe_deps_lib_transitive,
            ),
        ),
        HaxeProjectInfo(
            hxml = hxml,
            srcs = srcs,
            resources = resources,
            library_name = library_name if library_name != None else (ctx.attr.executable_name if hasattr(ctx.attr, "executable_name") else ctx.attr.library_name),
            deps = depset(
                direct = haxe_deps_proj_direct,
                transitive = haxe_deps_proj_transitive,
            ),
        ),
    ]

    cpp_files = []

    # Create target-specific responses
    if hxml["target"] == "java":
        java_out = ctx.actions.declare_file("java-deps/{}/{}".format(hxml["output_dir"], hxml["output_file"]))
        ctx.actions.run_shell(
            outputs = [java_out],
            inputs = [out_dir],
            command = "cp {}/{} {}".format(out_dir.path, hxml["output_file"], java_out.path),
            use_default_shell_env = True,
            mnemonic = "CopyJavaTargetToOutput",
        )

        java_deps = []
        if hasattr(ctx.attr, "deps"):
            for dep in ctx.attr.deps:
                if hasattr(dep, "JavaInfo"):
                    java_deps.append(dep[JavaInfo])
        rtrn.append(JavaInfo(
            output_jar = java_out,
            compile_jar = java_out,
            deps = java_deps,
        ))
    elif hxml["target"] == "python":
        rtrn.append(PyInfo(
            transitive_sources = depset([out_dir]),
        ))
    elif hxml["target"] == "cpp":
        # To get includes to be added to a downstream cc_library, they need to be added to the output.  But since we
        # don't have the File objects for each include, a tree result needs to be added.  This tree result needs to be
        # named such that cc_library will accept it - so it needs to end in '.h'.  Copy the includes folder to a new
        # folder with an appropriate name.
        inc = ctx.actions.declare_directory("{}_includes_dir.h".format(ctx.label.name))
        ctx.actions.run_shell(
            outputs = [inc],
            inputs = [out_dir],
            command = "cp -r -t {} {}/{}/include/* {}/{}/HxcppConfig-19.h".format(inc.path, out_dir.path, ctx.label.name, out_dir.path, ctx.label.name),
        )

        # Create an appropriate library object depending on whether the library is static or dynamic.
        library_to_link = None
        if output_file != None:
            if output_file.path.lower().endswith(".dll"):
                # Create a copy of the .lib file associated with the .dll so we have a File reference to it.
                lib_file = ctx.actions.declare_file("{}/{}/lib{}-debug.lib".format(out_dir.path, hxml["name"], hxml["name"]))
                ctx.actions.run_shell(
                    outputs = [lib_file],
                    inputs = [out_dir],
                    command = "cp {}/{}/obj/lib/lib{}-debug.lib {}".format(out_dir.path, hxml["name"], hxml["name"], lib_file.path),
                )

                library_to_link = cc_common.create_library_to_link(
                    actions = ctx.actions,
                    dynamic_library = output_file,
                    interface_library = lib_file,
                    feature_configuration = cc_common.configure_features(ctx = ctx, cc_toolchain = toolchain.haxe_cpp_toolchain),
                    cc_toolchain = toolchain.haxe_cpp_toolchain,
                )

                cpp_files.append(output_file)
                cpp_files.append(lib_file)

            elif output_file.path.lower().endswith(".lib"):
                library_to_link = cc_common.create_library_to_link(
                    actions = ctx.actions,
                    static_library = output_file,
                    feature_configuration = cc_common.configure_features(ctx = ctx, cc_toolchain = toolchain.haxe_cpp_toolchain),
                    cc_toolchain = toolchain.haxe_cpp_toolchain,
                )

                cpp_files.append(output_file)

        # Finally create the compilation context and add it to the response.
        linking_context = None
        if library_to_link != None:
            linking_context = cc_common.create_linking_context(
                linker_inputs = depset([cc_common.create_linker_input(
                    owner = ctx.label,
                    libraries = depset([library_to_link]),
                )]),
            )

        rtrn.append(CcInfo(
            compilation_context = cc_common.create_compilation_context(
                includes = depset([inc.path]),
                headers = depset([inc]),
            ),
            linking_context = linking_context,
        ))

    if output_file != None:
        rtrn.append(OutputGroupInfo(
            output_file = [output_file],
            cpp_files = cpp_files,
        ))

    return rtrn

"""
Haxe utility functions.
"""

load(":providers.bzl", "HaxeLibraryInfo", "HaxeProjectInfo")

def determine_source_root(path):
    """
    Determine the source root for a given path, based on whether the path is in the external directory.

    Args:
        path: The path to check.

    Returns:
        The source root for the path.
    """
    source_root = ""
    parts = path.split("/")
    for idx in range(len(parts)):
        if parts[idx] == "external":
            source_root += "external/{}/".format(parts[idx + 1])
    return source_root

def _determine_classpath(classpaths, path):
    classpath = determine_source_root(path)
    if classpath == "":
        for cp in classpaths:
            cp_idx = path.find(cp)
            if cp_idx > 0:
                classpath = path[0:cp_idx]
    return classpath

def find_direct_sources(ctx):
    """
    Finds the direct sources of the given context.

    Args:
        ctx: The bazel context.

    Returns:
        An array of source files.
    """
    rtrn = []
    if hasattr(ctx.files, "srcs"):
        rtrn += ctx.files.srcs

    if hasattr(ctx.attr, "deps"):
        for dep in ctx.attr.deps:
            haxe_dep = dep[HaxeProjectInfo]
            if haxe_dep == None:
                continue
            if hasattr(haxe_dep, "srcs"):
                rtrn += haxe_dep.srcs

    return rtrn

def find_direct_docsources(ctx):
    """
    Finds the direct document sources of the given context.

    Args:
        ctx: The bazel context.

    Returns:
        An array of document source files.
    """
    rtrn = []
    if hasattr(ctx.files, "doc_srcs"):
        rtrn += ctx.files.doc_srcs

    for dep in ctx.attr.deps:
        haxe_dep = dep[HaxeProjectInfo]
        if haxe_dep == None:
            continue
        if hasattr(haxe_dep, "doc_srcs"):
            rtrn += haxe_dep.doc_srcs

    return rtrn

def find_direct_resources(ctx):
    """
    Finds the direct resources of the given context.

    Args:
        ctx: The bazel context.

    Returns:
        An array of resource files.
    """
    rtrn = []
    if hasattr(ctx.files, "resources"):
        rtrn += ctx.files.resources

    if hasattr(ctx.attr, "deps"):
        for dep in ctx.attr.deps:
            haxe_dep = dep[HaxeProjectInfo]
            if haxe_dep == None:
                continue
            if hasattr(haxe_dep, "resources"):
                rtrn += haxe_dep.resources

    return rtrn

def find_library_name(ctx):
    """
    Determines the library name, taking into account any dependent HaxeProjectInfos.

    Args:
        ctx: The bazel context.

    Returns:
        The specified library name.
    """
    if hasattr(ctx.attr, "library_name") and ctx.attr.library_name != "":
        return ctx.attr.library_name
    elif hasattr(ctx.attr, "executable_name") and ctx.attr.executable_name != "":
        return ctx.attr.executable_name
    elif hasattr(ctx.attr, "deps"):
        for dep in ctx.attr.deps:
            haxe_dep = dep[HaxeProjectInfo]
            if haxe_dep == None:
                continue
            if haxe_dep.library_name != None and haxe_dep.library_name != "":
                return haxe_dep.library_name

    return ctx.attr.name

def find_main_class(ctx):
    """
    Determines the main class, taking into account any dependant HaxeProjectInfos.

    Args:
        ctx: The bazel context.

    Returns:
        The specified main class.
    """
    if hasattr(ctx.attr, "main_class") and ctx.attr.main_class != "":
        return ctx.attr.main_class
    else:
        for dep in ctx.attr.deps:
            haxe_dep = dep[HaxeProjectInfo]
            if haxe_dep == None:
                continue
            if hasattr(haxe_dep, "main_class") and haxe_dep.main_class != "":
                return haxe_dep.main_class

    return None

def create_hxml_map(ctx, toolchain, for_test = False, for_std_build = False):
    """
    Create a dict containing haxe build parameters based on the input attributes from the calling rule.

    Args:
        ctx: Bazel context.
        toolchain: The Haxe toolchain instance.
        for_test: True if build parameters for unit testing should be added, False otherwise.
        for_std_build: True if build parameters for the standard build should be added, False otherwise.

    Returns:
        A dict containing the HXML properties.
    """
    hxml = {}

    package = ctx.label.package + "/" if ctx.label.package != "" else ""
    hxml["package"] = package

    hxml["for_test"] = for_test

    hxml["target"] = ctx.attr.target if hasattr(ctx.attr, "target") else None
    hxml["debug"] = ctx.attr.debug if hasattr(ctx.attr, "debug") else False
    hxml["name"] = "std-{}".format(hxml["target"]) if for_std_build else find_library_name(ctx)

    if for_test:
        hxml["main_class"] = "MainTest"
    elif for_std_build:
        hxml["main_class"] = "StdBuild"
    else:
        hxml["main_class"] = find_main_class(ctx)

    hxml["args"] = list()
    if hasattr(ctx.attr, "extra_args"):
        for arg in ctx.attr.extra_args:
            if not arg in hxml["args"]:
                hxml["args"].append(arg)

    hxml["libs"] = dict()
    if hxml["target"] == "java":
        hxml["libs"]["hxjava"] = toolchain.haxelib_language_versions["hxjava"]
    elif hxml["target"] == "cpp":
        hxml["libs"]["hxcpp"] = toolchain.haxelib_language_versions["hxcpp"]
        if ctx.var["TARGET_CPU"].startswith("x64") and not "-D HXCPP_M64" in hxml["args"]:
            hxml["args"].append("-D HXCPP_M64")

    if hasattr(ctx.attr, "haxelibs"):
        for lib in ctx.attr.haxelibs:
            version = ctx.attr.haxelibs[lib]
            if version != None and version != "":
                if version.lower().find("http") == 0:
                    version = "git:{}".format(version)
                hxml["libs"][lib] = version
            else:
                fail("Explicit versioning is required for haxelibs.")

    hxml["classpaths"] = list()
    hxml["classpaths"].append("src/main/haxe")
    if for_test:
        hxml["classpaths"].append(ctx.var["BINDIR"])
        hxml["classpaths"].append("{}/{}".format(ctx.var["BINDIR"], package))
        hxml["classpaths"].append("src/test/haxe")
        hxml["classpaths"].append("{}src/test/haxe".format(package))
    if hasattr(ctx.attr, "classpaths"):
        for p in ctx.attr.classpaths:
            hxml["classpaths"].append(p)

    hxml["source_files"] = list()
    for src in find_direct_sources(ctx):
        hxml["source_files"].append(src.path)

    hxml["resources"] = dict()
    for resource in find_direct_resources(ctx):
        name = resource.path
        name = name.replace("src/main/resources/", "")
        name = name.replace("src/test/resources/", "")
        parts = name.split("/")
        new_name = ""
        skip = False
        for idx in range(len(parts)):
            if skip:
                skip = False
                continue
            elif parts[idx] == "external":
                new_name = ""
                skip = True
            elif parts[idx] != "":
                if new_name != "":
                    new_name += "/"
                new_name += parts[idx]

        hxml["resources"][resource.path] = new_name

    hxml["c-args"] = list()
    if hxml["target"] == "java":
        if "haxe_java_target_version" in ctx.var:
            hxml["c-args"] += ["-source", ctx.var["haxe_java_target_version"], "-target", ctx.var["haxe_java_target_version"]]

    # Handle Dependencies
    if hasattr(ctx.attr, "deps"):
        for dep in ctx.attr.deps:
            haxe_dep = dep[HaxeLibraryInfo]
            if haxe_dep == None or haxe_dep.hxml == None:
                continue
            dep_hxml = haxe_dep.hxml
            for classpath in dep_hxml["classpaths"]:
                if classpath.startswith("external"):
                    parts = classpath.split("/")
                    new_classpath = ""
                    for idx in range(len(parts)):
                        if parts[idx] == "external":
                            new_classpath = "external"
                        elif parts[idx] != "":
                            new_classpath += "/" + parts[idx]

                    if not new_classpath in hxml["classpaths"]:
                        hxml["classpaths"].append(new_classpath)
                else:
                    calculated_classpath = _determine_classpath(dep_hxml["classpaths"], dep_hxml["source_files"][0]) if len(dep_hxml["source_files"]) != 0 else ""
                    if calculated_classpath == dep_hxml["package"]:
                        calculated_classpath = ""
                    hxml["classpaths"].append("{}{}{}".format(calculated_classpath, dep_hxml["package"], classpath))
            for lib in dep_hxml["libs"]:
                if not lib in hxml["libs"]:
                    hxml["libs"][lib] = dep_hxml["libs"][lib]
            for resource in dep_hxml["resources"]:
                if not resource in hxml["resources"]:
                    hxml["resources"][resource] = dep_hxml["resources"][resource]
            for arg in dep_hxml["args"]:
                if not arg in hxml["args"]:
                    hxml["args"].append(arg)

    is_external = ctx.label.workspace_root.startswith("external")
    hxml["external_dir"] = "external/{}/".format(hxml["name"]) if is_external else ""

    return hxml

def create_build_hxml(ctx, toolchain, hxml, out_file, suffix = "", for_exec = False):
    """
    Create the build.hxml file based on the input hxml dict.

    Any Haxelibs that are specified in the hxml will be installed at this time.

    Args:
        ctx: Bazel context.
        toolchain: The Haxe toolchain instance.
        hxml: A dict containing HXML parameters; should be generated from `_create_hxml_map`.
        out_file: The output file that the build.hxml should be written to.
        suffix: Optional suffix to append to the build parameters.
        for_exec: Whether this build HXML is intended for executing the result of the build; this can ignore some errors
            that aren't an issue during execution.
    """

    # Determine if we're in a dependant build.
    if for_exec or len(hxml["source_files"]) == 0 or len(ctx.files.srcs) == 0:
        is_dependent_build = True
    else:
        is_dependent_build = hxml["source_files"][0].startswith("external")

    # An empty source root seems to cover the use cases that are currently in use; this may need to be revisited, but
    # will require unit test cases!
    source_root = ""

    content = ""

    package = ctx.label.package + "/" if ctx.label.package != "" else ""

    # Target

    hxml["output_dir"] = "{}{}".format(ctx.attr.name, suffix)
    hxml["build_file"] = "{}/{}{}{}/{}".format(ctx.var["BINDIR"], hxml["external_dir"], package, hxml["output_dir"], hxml["name"])
    ext = ""
    if hxml["target"] != "":
        if hxml["target"] == "neko":
            ext = ".n"
            hxml["output_file"] = "{}.n".format(hxml["name"], suffix)
        elif hxml["target"] == "python":
            ext = ".py"
            hxml["output_file"] = "{}.py".format(hxml["name"], suffix)
        elif hxml["target"] == "php":
            hxml["output_file"] = "{}".format(hxml["name"], suffix)
        elif hxml["target"] == "cpp":
            output = "{}/".format(hxml["name"])
            output_file = ""
            if not for_exec:
                output_file += "lib"

            if hxml["main_class"] != None:
                mc = hxml["main_class"]
                if "." in mc:
                    mc = mc[mc.rindex(".") + 1:]

                output_file += "{}".format(mc)
            else:
                output_file += "{}".format(hxml["name"])

            if hxml["debug"] != None:
                output_file += "-debug"

            found_output_file = False
            for arg in hxml["args"]:
                if arg.lower().startswith("-d haxe_output_file"):
                    found_output_file = True
            if not found_output_file:
                hxml["args"].append("-D HAXE_OUTPUT_FILE={}".format(output_file))

            if for_exec:
                output_file += ".exe"
            elif "-D dll_link" in hxml["args"]:
                output_file += ".dll"
            else:
                output_file += ".lib"

            hxml["output_file"] = output + output_file
        elif hxml["target"] == "java":
            output = "{}".format(hxml["name"])
            if hxml["main_class"] != None:
                mc = hxml["main_class"]
                if "." in mc:
                    mc = mc[mc.rindex(".") + 1:]

                output += "/{}".format(mc)
            else:
                output += "/{}".format(hxml["name"])

            if hxml["debug"] != None:
                output += "-Debug"

            hxml["output_file"] = output + ".jar"
        elif hxml["target"] == "js":
            ext = ".js"
            hxml["output_file"] = "{}.js".format(hxml["name"], suffix)

        content += "--{} {}{}\n".format(hxml["target"], hxml["build_file"], ext)

    # Debug
    if hxml["debug"] != None:
        content += "-debug\n"

    # Classpaths
    for classpath in hxml["classpaths"]:
        if not classpath.startswith("external"):
            classpath = "{}{}".format(source_root, classpath)
        content += "-p {}\n".format(classpath)

    # Compiler Args
    for c_arg in hxml["c-args"]:
        content += "--c-arg {}\n".format(c_arg)

    # User Args
    for arg in hxml["args"]:
        content += "{}\n".format(arg)

    # Resources
    for path in hxml["resources"]:
        content += "--resource {}@{}\n".format(path, hxml["resources"][path])

    # Source or Main files
    if hxml["main_class"] != None:
        content += "-m {}\n".format(hxml["main_class"])
    else:
        for path in hxml["source_files"]:
            if is_dependent_build:
                path = path[len(source_root):]
            for classpath in hxml["classpaths"]:
                if path.startswith(classpath):
                    path = path[len(classpath) + 1:]
                    break
            content += path.replace(".hx", "").replace("/", ".") + "\n"

    count = 1
    build_files = list()

    if hxml["for_test"]:
        build_prefix = "{}-build-test.hxml".format(ctx.attr.name)
        path_prefix = "{}-path-test".format(ctx.attr.name)
    else:
        build_prefix = "{}-build.hxml".format(ctx.attr.name)
        path_prefix = "{}-path".format(ctx.attr.name)

    build_file_1 = ctx.actions.declare_file("{}.{}".format(build_prefix, count))
    count += 1
    build_files.append(build_file_1)

    for lib in hxml["libs"]:
        version = hxml["libs"][lib]
        content += "-L {}:{}\n".format(lib, version)

        path_file = toolchain.haxelib_install(
            ctx,
            lib,
            version,
        )
        count += 1
        build_files.append(path_file)

    # Write to a temporary file, then use run_shell to generate the required output file so that all the previous tasks are complete.
    ctx.actions.write(
        output = build_file_1,
        content = content,
    )

    ctx.actions.run_shell(
        outputs = [out_file],
        inputs = build_files,
        command = "mv {} {}".format(build_file_1.path, out_file.path),
    )

def calc_provider_response(ctx, toolchain, hxml, out_dir, launcher_file = None, output_file = None, library_name = None):
    """
    Determine an appropriate provider response based on the input context and the compilation target.

    Args:
        ctx: The bazel context.
        toolchain: The Haxe toolchain.
        hxml: The HXML dictionary.
        out_dir: The output directory.
        launcher_file: The launcher file to run, if there is one.
        output_file: The output file, if there is one.
        library_name: The name of the library, overrides the ctx variables.

    Returns:
        An array of providers.
    """
    runfiles = [out_dir]
    if launcher_file != None:
        runfiles.append(launcher_file)
        runfiles += toolchain.internal.tools

    haxe_deps_lib_direct = []
    haxe_deps_lib_transitive = []
    haxe_deps_proj_direct = []
    haxe_deps_proj_transitive = []
    if hasattr(ctx.attr, "deps"):
        haxe_deps_lib_direct = [dep[HaxeLibraryInfo] for dep in ctx.attr.deps]
        haxe_deps_lib_transitive = [dep[HaxeLibraryInfo].deps for dep in ctx.attr.deps]
        haxe_deps_proj_direct = [dep[HaxeProjectInfo] for dep in ctx.attr.deps]
        haxe_deps_proj_transitive = [dep[HaxeProjectInfo].deps for dep in ctx.attr.deps]

    srcs = []
    if hasattr(ctx.files, "srcs"):
        srcs = ctx.files.srcs

    resources = []
    if hasattr(ctx.files, "resources"):
        resources = ctx.files.resources

    rtrn = [
        DefaultInfo(
            files = depset([out_dir] + find_direct_sources(ctx)),
            runfiles = ctx.runfiles(files = runfiles),
            executable = launcher_file,
        ),
        HaxeLibraryInfo(
            lib = out_dir,
            hxml = hxml,
            deps = depset(
                direct = haxe_deps_lib_direct,
                transitive = haxe_deps_lib_transitive,
            ),
        ),
        HaxeProjectInfo(
            hxml = hxml,
            srcs = srcs,
            resources = resources,
            library_name = library_name if library_name != None else (ctx.attr.executable_name if hasattr(ctx.attr, "executable_name") else ctx.attr.library_name),
            deps = depset(
                direct = haxe_deps_proj_direct,
                transitive = haxe_deps_proj_transitive,
            ),
        ),
    ]

    cpp_files = []

    # Create target-specific responses
    if hxml["target"] == "java":
        java_out = ctx.actions.declare_file("java-deps/{}/{}".format(hxml["output_dir"], hxml["output_file"]))
        ctx.actions.run_shell(
            outputs = [java_out],
            inputs = [out_dir],
            command = "cp {}/{} {}".format(out_dir.path, hxml["output_file"], java_out.path),
            use_default_shell_env = True,
            mnemonic = "CopyJavaTargetToOutput",
        )

        java_deps = []
        if hasattr(ctx.attr, "deps"):
            for dep in ctx.attr.deps:
                if hasattr(dep, "JavaInfo"):
                    java_deps.append(dep[JavaInfo])
        rtrn.append(JavaInfo(
            output_jar = java_out,
            compile_jar = java_out,
            deps = java_deps,
        ))
    elif hxml["target"] == "python":
        rtrn.append(PyInfo(
            transitive_sources = depset([out_dir]),
        ))
    elif hxml["target"] == "cpp":
        # To get includes to be added to a downstream cc_library, they need to be added to the output.  But since we
        # don't have the File objects for each include, a tree result needs to be added.  This tree result needs to be
        # named such that cc_library will accept it - so it needs to end in '.h'.  Copy the includes folder to a new
        # folder with an appropriate name.
        inc = ctx.actions.declare_directory("{}_includes_dir.h".format(ctx.label.name))
        ctx.actions.run_shell(
            outputs = [inc],
            inputs = [out_dir],
            command = "cp -r -t {} {}/{}/include/* {}/{}/HxcppConfig-19.h".format(inc.path, out_dir.path, ctx.label.name, out_dir.path, ctx.label.name),
        )

        # Create an appropriate library object depending on whether the library is static or dynamic.
        library_to_link = None
        if output_file != None:
            if output_file.path.lower().endswith(".dll"):
                # Create a copy of the .lib file associated with the .dll so we have a File reference to it.
                lib_file = ctx.actions.declare_file("{}/{}/lib{}-debug.lib".format(out_dir.path, hxml["name"], hxml["name"]))
                ctx.actions.run_shell(
                    outputs = [lib_file],
                    inputs = [out_dir],
                    command = "cp {}/{}/obj/lib/lib{}-debug.lib {}".format(out_dir.path, hxml["name"], hxml["name"], lib_file.path),
                )

                library_to_link = cc_common.create_library_to_link(
                    actions = ctx.actions,
                    dynamic_library = output_file,
                    interface_library = lib_file,
                    feature_configuration = cc_common.configure_features(ctx = ctx, cc_toolchain = toolchain.haxe_cpp_toolchain),
                    cc_toolchain = toolchain.haxe_cpp_toolchain,
                )

                cpp_files.append(output_file)
                cpp_files.append(lib_file)

            elif output_file.path.lower().endswith(".lib"):
                library_to_link = cc_common.create_library_to_link(
                    actions = ctx.actions,
                    static_library = output_file,
                    feature_configuration = cc_common.configure_features(ctx = ctx, cc_toolchain = toolchain.haxe_cpp_toolchain),
                    cc_toolchain = toolchain.haxe_cpp_toolchain,
                )

                cpp_files.append(output_file)

        # Finally create the compilation context and add it to the response.
        linking_context = None
        if library_to_link != None:
            linking_context = cc_common.create_linking_context(
                linker_inputs = depset([cc_common.create_linker_input(
                    owner = ctx.label,
                    libraries = depset([library_to_link]),
                )]),
            )

        rtrn.append(CcInfo(
            compilation_context = cc_common.create_compilation_context(
                includes = depset([inc.path]),
                headers = depset([inc]),
            ),
            linking_context = linking_context,
        ))

    if output_file != None:
        rtrn.append(OutputGroupInfo(
            output_file = [output_file],
            cpp_files = cpp_files,
        ))

    return rtrn
Haxe utility functions. Determine the source root for a given path, based on whether the path is in the external directory.      Args:         path: The path to check.      Returns:         The source root for the path. Finds the direct sources of the given context.      Args:         ctx: The bazel context.      Returns:         An array of source files. Finds the direct document sources of the given context.      Args:         ctx: The bazel context.      Returns:         An array of document source files. Finds the direct resources of the given context.      Args:         ctx: The bazel context.      Returns:         An array of resource files. Determines the library name, taking into account any dependent HaxeProjectInfos.      Args:         ctx: The bazel context.      Returns:         The specified library name. Determines the main class, taking into account any dependant HaxeProjectInfos.      Args:         ctx: The bazel context.      Returns:         The specified main class. Create a dict containing haxe build parameters based on the input attributes from the calling rule.      Args:         ctx: Bazel context.         toolchain: The Haxe toolchain instance.         for_test: True if build parameters for unit testing should be added, False otherwise.         for_std_build: True if build parameters for the standard build should be added, False otherwise.      Returns:         A dict containing the HXML properties. # Handle Dependencies Create the build.hxml file based on the input hxml dict.      Any Haxelibs that are specified in the hxml will be installed at this time.      Args:         ctx: Bazel context.         toolchain: The Haxe toolchain instance.         hxml: A dict containing HXML parameters; should be generated from `_create_hxml_map`.         out_file: The output file that the build.hxml should be written to.         suffix: Optional suffix to append to the build parameters.         for_exec: Whether this build HXML is intended for executing the result of the build; this can ignore some errors             that aren't an issue during execution. # Determine if we're in a dependant build. # An empty source root seems to cover the use cases that are currently in use; this may need to be revisited, but # will require unit test cases! # Target # Debug # Classpaths # Compiler Args # User Args # Resources # Source or Main files # Write to a temporary file, then use run_shell to generate the required output file so that all the previous tasks are complete. Determine an appropriate provider response based on the input context and the compilation target.      Args:         ctx: The bazel context.         toolchain: The Haxe toolchain.         hxml: The HXML dictionary.         out_dir: The output directory.         launcher_file: The launcher file to run, if there is one.         output_file: The output file, if there is one.         library_name: The name of the library, overrides the ctx variables.      Returns:         An array of providers. # Create target-specific responses # To get includes to be added to a downstream cc_library, they need to be added to the output.  But since we # don't have the File objects for each include, a tree result needs to be added.  This tree result needs to be # named such that cc_library will accept it - so it needs to end in '.h'.  Copy the includes folder to a new # folder with an appropriate name. # Create an appropriate library object depending on whether the library is static or dynamic. # Create a copy of the .lib file associated with the .dll so we have a File reference to it. # Finally create the compilation context and add it to the response.