<commit_before># Copyright Google Inc. All Rights Reserved.
#
# Use of this source code is governed by an MIT-style license that can be
# found in the LICENSE file at https://angular.io/license
"""Run Angular's AOT template compiler
"""

load(
    ":external.bzl",
    "COMMON_ATTRIBUTES",
    "COMMON_OUTPUTS",
    "DEFAULT_API_EXTRACTOR",
    "DEFAULT_NG_COMPILER",
    "DEFAULT_NG_XI18N",
    "DEPS_ASPECTS",
    "NodeModuleSources",
    "TsConfigInfo",
    "collect_node_modules_aspect",
    "compile_ts",
    "ts_providers_dict_to_struct",
    "tsc_wrapped_tsconfig",
)

_FLAT_DTS_FILE_SUFFIX = ".bundle.d.ts"
_R3_SYMBOLS_DTS_FILE = "src/r3_symbols.d.ts"

def compile_strategy(ctx):
    """Detect which strategy should be used to implement ng_module.

    Depending on the value of the 'compile' define flag, ng_module
    can be implemented in various ways. This function reads the configuration passed by the user and
    determines which mode is active.

    Args:
      ctx: skylark rule execution context

    Returns:
      one of 'legacy' or 'aot' depending on the configuration in ctx
    """

    strategy = "legacy"
    if "compile" in ctx.var:
        strategy = ctx.var["compile"]

    # Enable Angular targets extracted by Kythe Angular indexer to be compiled with the Ivy compiler architecture.
    # TODO(ayazhafiz): remove once Ivy has landed as the default in g3.
    if ctx.var.get("GROK_ELLIPSIS_BUILD", None) != None:
        strategy = "aot"

    if strategy not in ["legacy", "aot"]:
        fail("Unknown --define=compile value '%s'" % strategy)

    return strategy

def _compiler_name(ctx):
    """Selects a user-visible name depending on the current compilation strategy.

    Args:
      ctx: skylark rule execution context

    Returns:
      the name of the current compiler to be displayed in build output
    """

    strategy = compile_strategy(ctx)
    if strategy == "legacy":
        return "ngc"
    elif strategy == "aot":
        return "ngtsc"
    else:
        fail("unreachable")

def _enable_ivy_value(ctx):
    """Determines the value of the enableIvy option in the generated tsconfig.

    Args:
      ctx: skylark rule execution context

    Returns:
      the value of enableIvy that needs to be set in angularCompilerOptions in the generated tsconfig
    """

    strategy = compile_strategy(ctx)
    if strategy == "legacy":
        return False
    elif strategy == "aot":
        return True
    else:
        fail("unreachable")

def _is_legacy_ngc(ctx):
    """Determines whether Angular outputs will be produced by the current compilation strategy.

    Args:
      ctx: skylark rule execution context

    Returns:
      true iff the current compilation strategy will produce View Engine compilation outputs (such as
      factory files), false otherwise
    """

    strategy = compile_strategy(ctx)
    return strategy == "legacy"

def _basename_of(ctx, file):
    ext_len = len(".ts")
    if file.short_path.endswith(".ng.html"):
        ext_len = len(".ng.html")
    elif file.short_path.endswith(".html"):
        ext_len = len(".html")
    return file.short_path[len(ctx.label.package) + 1:-ext_len]

# Return true if run with bazel (the open-sourced version of blaze), false if
# run with blaze.
def _is_bazel():
    return not hasattr(native, "genmpm")

def _flat_module_out_file(ctx):
    """Provide a default for the flat_module_out_file attribute.

    We cannot use the default="" parameter of ctx.attr because the value is calculated
    from other attributes (name)

    Args:
      ctx: skylark rule execution context

    Returns:
      a basename used for the flat module out (no extension)
    """
    if hasattr(ctx.attr, "flat_module_out_file") and ctx.attr.flat_module_out_file:
        return ctx.attr.flat_module_out_file
    return "%s_public_index" % ctx.label.name

def _should_produce_dts_bundle(ctx):
    """Should we produce dts bundles.

    We only produce flatten dts outs when we expect the ng_module is meant to be published,
    based on the value of the bundle_dts attribute.

    Args:
      ctx: skylark rule execution context

    Returns:
      true when we should produce bundled dts.
    """

    # At the moment we cannot use this with ngtsc compiler since it emits
    # import * as ___ from local modules which is not supported
    # see: https://github.com/Microsoft/web-build-tools/issues/1029
    return _is_legacy_ngc(ctx) and hasattr(ctx.attr, "bundle_dts") and ctx.attr.bundle_dts

def _should_produce_r3_symbols_bundle(ctx):
    """Should we produce r3_symbols bundle.

    NGCC relies on having r3_symbols file. This file is located in @angular/core
    And should only be included when bundling core in legacy mode.

    Args:
      ctx: skylark rule execution context

    Returns:
      true when we should produce r3_symbols dts.
    """

    # iif we are compiling @angular/core with ngc we should add this addition dts bundle
    # because ngcc relies on having this file.
    # see: https://github.com/angular/angular/blob/84406e4d6d93b28b23efbb1701bc5ae1084da67b/packages/compiler-cli/src/ngcc/src/packages/entry_point_bundle.ts#L56
    # todo: alan-agius4: remove when ngcc doesn't need this anymore
    return _is_legacy_ngc(ctx) and ctx.attr.module_name == "@angular/core"

def _should_produce_flat_module_outs(ctx):
    """Should we produce flat module outputs.

    We only produce flat module outs when we expect the ng_module is meant to be published,
    based on the presence of the module_name attribute.

    Args:
      ctx: skylark rule execution context

    Returns:
      true iff we should run the bundle_index_host to produce flat module metadata and bundle index
    """
    return _is_bazel() and ctx.attr.module_name

# Calculate the expected output of the template compiler for every source in
# in the library. Most of these will be produced as empty files but it is
# unknown, without parsing, which will be empty.
def _expected_outs(ctx):
    is_legacy_ngc = _is_legacy_ngc(ctx)

    devmode_js_files = []
    closure_js_files = []
    declaration_files = []
    summary_files = []
    metadata_files = []

    factory_basename_set = depset([_basename_of(ctx, src) for src in ctx.files.factories])

    for src in ctx.files.srcs + ctx.files.assets:
        package_prefix = ctx.label.package + "/" if ctx.label.package else ""

        # Strip external repository name from path if src is from external repository
        # If src is from external repository, it's short_path will be ../<external_repo_name>/...
        short_path = src.short_path if src.short_path[0:2] != ".." else "/".join(src.short_path.split("/")[2:])

        if short_path.endswith(".ts") and not short_path.endswith(".d.ts"):
            basename = short_path[len(package_prefix):-len(".ts")]
            if (len(factory_basename_set.to_list()) == 0 or basename in factory_basename_set.to_list()):
                devmode_js = [
                    ".ngfactory.js",
                    ".ngsummary.js",
                    ".js",
                ]

                # Only ngc produces .json files, they're not needed in Ivy.
                if is_legacy_ngc:
                    summaries = [".ngsummary.json"]
                    metadata = [".metadata.json"]
                else:
                    summaries = []
                    metadata = []
            else:
                devmode_js = [".js"]
                if not _is_bazel():
                    devmode_js += [".ngfactory.js"]
                summaries = []
                metadata = []
        elif is_legacy_ngc and short_path.endswith(".css"):
            basename = short_path[len(package_prefix):-len(".css")]
            devmode_js = [
                ".css.shim.ngstyle.js",
                ".css.ngstyle.js",
            ]
            summaries = []
            metadata = []
        else:
            continue

        filter_summaries = ctx.attr.filter_summaries
        closure_js = [f.replace(".js", ".mjs") for f in devmode_js if not filter_summaries or not f.endswith(".ngsummary.js")]
        declarations = [f.replace(".js", ".d.ts") for f in devmode_js]

        devmode_js_files += [ctx.actions.declare_file(basename + ext) for ext in devmode_js]
        closure_js_files += [ctx.actions.declare_file(basename + ext) for ext in closure_js]
        declaration_files += [ctx.actions.declare_file(basename + ext) for ext in declarations]
        summary_files += [ctx.actions.declare_file(basename + ext) for ext in summaries]
        if not _is_bazel():
            metadata_files += [ctx.actions.declare_file(basename + ext) for ext in metadata]

    dts_bundles = None
    if _should_produce_dts_bundle(ctx):
        # We need to add a suffix to bundle as it might collide with the flat module dts.
        # The flat module dts out contains several other exports
        # https://github.com/angular/angular/blob/84406e4d6d93b28b23efbb1701bc5ae1084da67b/packages/compiler-cli/src/metadata/index_writer.ts#L18
        # the file name will be like 'core.bundle.d.ts'
        dts_bundles = [ctx.actions.declare_file(ctx.label.name + _FLAT_DTS_FILE_SUFFIX)]

        if _should_produce_r3_symbols_bundle(ctx):
            dts_bundles.append(ctx.actions.declare_file(_R3_SYMBOLS_DTS_FILE.replace(".d.ts", _FLAT_DTS_FILE_SUFFIX)))

    # We do this just when producing a flat module index for a publishable ng_module
    if _should_produce_flat_module_outs(ctx):
        flat_module_out = _flat_module_out_file(ctx)
        devmode_js_files.append(ctx.actions.declare_file("%s.js" % flat_module_out))
        closure_js_files.append(ctx.actions.declare_file("%s.mjs" % flat_module_out))
        bundle_index_typings = ctx.actions.declare_file("%s.d.ts" % flat_module_out)
        declaration_files.append(bundle_index_typings)
        if is_legacy_ngc:
            metadata_files.append(ctx.actions.declare_file("%s.metadata.json" % flat_module_out))
    else:
        bundle_index_typings = None

    # TODO(alxhub): i18n is only produced by the legacy compiler currently. This should be re-enabled
    # when ngtsc can extract messages
    if is_legacy_ngc and _is_bazel():
        i18n_messages_files = [ctx.actions.declare_file(ctx.label.name + "_ngc_messages.xmb")]
    elif is_legacy_ngc:
        # write the xmb file to blaze-genfiles since that path appears in the translation console keys
        i18n_messages_files = [ctx.new_file(ctx.genfiles_dir, ctx.label.name + "_ngc_messages.xmb")]
    else:
        i18n_messages_files = []

    return struct(
        closure_js = closure_js_files,
        devmode_js = devmode_js_files,
        declarations = declaration_files,
        summaries = summary_files,
        metadata = metadata_files,
        dts_bundles = dts_bundles,
        bundle_index_typings = bundle_index_typings,
        i18n_messages = i18n_messages_files,
    )

def _ngc_tsconfig(ctx, files, srcs, **kwargs):
    outs = _expected_outs(ctx)
    is_legacy_ngc = _is_legacy_ngc(ctx)
    if "devmode_manifest" in kwargs:
        expected_outs = outs.devmode_js + outs.declarations + outs.summaries + outs.metadata
    else:
        expected_outs = outs.closure_js

    angular_compiler_options = {
        "enableResourceInlining": ctx.attr.inline_resources,
        "generateCodeForLibraries": False,
        "allowEmptyCodegenFiles": True,
        # Summaries are only enabled if Angular outputs are to be produced.
        "enableSummariesForJit": is_legacy_ngc,
        "enableIvy": _enable_ivy_value(ctx),
        "fullTemplateTypeCheck": ctx.attr.type_check,
        # TODO(alxhub/arick): template type-checking for Ivy needs to be tested in g3 before it can
        # be enabled here.
        "ivyTemplateTypeCheck": False,
        # In Google3 we still want to use the symbol factory re-exports in order to
        # not break existing apps inside Google. Unlike Bazel, Google3 does not only
        # enforce strict dependencies of source files, but also for generated files
        # (such as the factory files). Therefore in order to avoid that generated files
        # introduce new module dependencies (which aren't explicitly declared), we need
        # to enable external symbol re-exports by default when running with Blaze.
        "createExternalSymbolFactoryReexports": (not _is_bazel()),
        # FIXME: wrong place to de-dupe
        "expectedOut": depset([o.path for o in expected_outs]).to_list(),
        "_useHostForImportGeneration": (not _is_bazel()),
    }

    if _should_produce_flat_module_outs(ctx):
        angular_compiler_options["flatModuleId"] = ctx.attr.module_name
        angular_compiler_options["flatModuleOutFile"] = _flat_module_out_file(ctx)
        angular_compiler_options["flatModulePrivateSymbolPrefix"] = "_".join(
            [ctx.workspace_name] + ctx.label.package.split("/") + [ctx.label.name, ""],
        )

    return dict(tsc_wrapped_tsconfig(ctx, files, srcs, **kwargs), **{
        "angularCompilerOptions": angular_compiler_options,
    })

def _collect_summaries_aspect_impl(target, ctx):
    results = depset(target.angular.summaries if hasattr(target, "angular") else [])

    # If we are visiting empty-srcs ts_library, this is a re-export
    srcs = ctx.rule.attr.srcs if hasattr(ctx.rule.attr, "srcs") else []

    # "re-export" rules should expose all the files of their deps
    if not srcs and hasattr(ctx.rule.attr, "deps"):
        for dep in ctx.rule.attr.deps:
            if (hasattr(dep, "angular")):
                results = depset(dep.angular.summaries, transitive = [results])

    return struct(collect_summaries_aspect_result = results)

_collect_summaries_aspect = aspect(
    implementation = _collect_summaries_aspect_impl,
    attr_aspects = ["deps"],
)

# Extra options passed to Node when running ngc.
_EXTRA_NODE_OPTIONS_FLAGS = [
    # Expose the v8 garbage collection API to JS.
    "--node_options=--expose-gc",
    # Show ~full stack traces, instead of cutting off after 10 items.
    "--node_options=--stack-trace-limit=100",
    # Give 4 GB RAM to node to allow bigger google3 modules to compile.
    "--node_options=--max-old-space-size=4096",
]

def ngc_compile_action(
        ctx,
        label,
        inputs,
        outputs,
        messages_out,
        tsconfig_file,
        node_opts,
        locale = None,
        i18n_args = [],
        dts_bundles_out = None,
        compile_mode = "prodmode"):
    """Helper function to create the ngc action.

    This is exposed for google3 to wire up i18n replay rules, and is not intended
    as part of the public API.

    Args:
      ctx: skylark context
      label: the label of the ng_module being compiled
      inputs: passed to the ngc action's inputs
      outputs: passed to the ngc action's outputs
      messages_out: produced xmb files
      tsconfig_file: tsconfig file with settings used for the compilation
      node_opts: list of strings, extra nodejs options.
      locale: i18n locale, or None
      i18n_args: additional command-line arguments to ngc
      dts_bundles_out: produced flattened dts file

    Returns:
      the parameters of the compilation which will be used to replay the ngc action for i18N.
    """

    is_legacy_ngc = _is_legacy_ngc(ctx)

    mnemonic = "AngularTemplateCompile"
    progress_message = "Compiling Angular templates (%s - %s) %s" % (_compiler_name(ctx), compile_mode, label)

    if locale:
        mnemonic = "AngularI18NMerging"
        supports_workers = "0"
        progress_message = ("Recompiling Angular templates (ngc - %s) %s for locale %s" %
                            (compile_mode, label, locale))
    else:
        supports_workers = str(int(ctx.attr._supports_workers))

    arguments = (list(_EXTRA_NODE_OPTIONS_FLAGS) +
                 ["--node_options=%s" % opt for opt in node_opts])

    # One at-sign makes this a params-file, enabling the worker strategy.
    # Two at-signs escapes the argument so it's passed through to ngc
    # rather than the contents getting expanded.
    if supports_workers == "1":
        arguments += ["@@" + tsconfig_file.path]
    else:
        arguments += ["-p", tsconfig_file.path]

    arguments += i18n_args

    ctx.actions.run(
        progress_message = progress_message,
        mnemonic = mnemonic,
        inputs = inputs,
        outputs = outputs,
        arguments = arguments,
        executable = ctx.executable.compiler,
        execution_requirements = {
            "supports-workers": supports_workers,
        },
    )

    if is_legacy_ngc and messages_out != None:
        # The base path is bin_dir because of the way the ngc
        # compiler host is configured. Under Blaze, we need to explicitly
        # point to genfiles/ to redirect the output.
        # See _expected_outs above, where the output path for the message file
        # is conditional on whether we are in Bazel.
        message_file_path = messages_out[0].short_path if _is_bazel() else "../genfiles/" + messages_out[0].short_path
        ctx.actions.run(
            inputs = inputs,
            outputs = messages_out,
            executable = ctx.executable.ng_xi18n,
            arguments = (_EXTRA_NODE_OPTIONS_FLAGS +
                         [tsconfig_file.path] +
                         [message_file_path]),
            progress_message = "Extracting Angular 2 messages (ng_xi18n)",
            mnemonic = "Angular2MessageExtractor",
        )

    if dts_bundles_out != None:
        # combine the inputs and outputs and filter .d.ts and json files
        filter_inputs = [f for f in inputs.to_list() + outputs if f.path.endswith(".d.ts") or f.path.endswith(".json")]

        if _should_produce_flat_module_outs(ctx):
            dts_entry_points = ["%s.d.ts" % _flat_module_out_file(ctx)]
        else:
            dts_entry_points = [ctx.attr.entry_point.label.name.replace(".ts", ".d.ts")]

        if _should_produce_r3_symbols_bundle(ctx):
            dts_entry_points.append(_R3_SYMBOLS_DTS_FILE)

        ctx.actions.run(
            progress_message = "Bundling DTS (%s) %s" % (compile_mode, str(ctx.label)),
            mnemonic = "APIExtractor",
            executable = ctx.executable.api_extractor,
            inputs = filter_inputs,
            outputs = dts_bundles_out,
            arguments = [
                tsconfig_file.path,
                ",".join(["/".join([ctx.bin_dir.path, ctx.label.package, f]) for f in dts_entry_points]),
                ",".join([f.path for f in dts_bundles_out]),
            ],
        )

    if not locale and not ctx.attr.no_i18n:
        return struct(
            label = label,
            tsconfig = tsconfig_file,
            inputs = inputs,
            outputs = outputs,
            compiler = ctx.executable.compiler,
        )

    return None

def _filter_ts_inputs(all_inputs):
    # The compiler only needs to see TypeScript sources from the npm dependencies,
    # but may need to look at package.json and ngsummary.json files as well.
    return [
        f
        for f in all_inputs
        if f.path.endswith(".js") or f.path.endswith(".ts") or f.path.endswith(".json")
    ]

def _compile_action(
        ctx,
        inputs,
        outputs,
        dts_bundles_out,
        messages_out,
        tsconfig_file,
        node_opts,
        compile_mode):
    # Give the Angular compiler all the user-listed assets
    file_inputs = list(ctx.files.assets)

    if (type(inputs) == type([])):
        file_inputs.extend(inputs)
    else:
        # inputs ought to be a list, but allow depset as well
        # so that this can change independently of rules_typescript
        # TODO(alexeagle): remove this case after update (July 2019)
        file_inputs.extend(inputs.to_list())

    if hasattr(ctx.attr, "node_modules"):
        file_inputs.extend(_filter_ts_inputs(ctx.files.node_modules))

    # If the user supplies a tsconfig.json file, the Angular compiler needs to read it
    if hasattr(ctx.attr, "tsconfig") and ctx.file.tsconfig:
        file_inputs.append(ctx.file.tsconfig)
        if TsConfigInfo in ctx.attr.tsconfig:
            file_inputs += ctx.attr.tsconfig[TsConfigInfo].deps

    # Also include files from npm fine grained deps as action_inputs.
    # These deps are identified by the NodeModuleSources provider.
    for d in ctx.attr.deps:
        if NodeModuleSources in d:
            # Note: we can't avoid calling .to_list() on sources
            file_inputs.extend(_filter_ts_inputs(d[NodeModuleSources].sources.to_list()))

    # Collect the inputs and summary files from our deps
    action_inputs = depset(
        file_inputs,
        transitive = [
            dep.collect_summaries_aspect_result
            for dep in ctx.attr.deps
            if hasattr(dep, "collect_summaries_aspect_result")
        ],
    )

    return ngc_compile_action(ctx, ctx.label, action_inputs, outputs, messages_out, tsconfig_file, node_opts, None, [], dts_bundles_out, compile_mode)

def _prodmode_compile_action(ctx, inputs, outputs, tsconfig_file, node_opts):
    outs = _expected_outs(ctx)
    return _compile_action(ctx, inputs, outputs + outs.closure_js, None, outs.i18n_messages, tsconfig_file, node_opts, "prodmode")

def _devmode_compile_action(ctx, inputs, outputs, tsconfig_file, node_opts):
    outs = _expected_outs(ctx)
    compile_action_outputs = outputs + outs.devmode_js + outs.declarations + outs.summaries + outs.metadata
    _compile_action(ctx, inputs, compile_action_outputs, outs.dts_bundles, None, tsconfig_file, node_opts, "devmode")

def _ts_expected_outs(ctx, label, srcs_files = []):
    # rules_typescript expects a function with two or more arguments, but our
    # implementation doesn't use the label(and **kwargs).
    _ignored = [label, srcs_files]
    return _expected_outs(ctx)

def ng_module_impl(ctx, ts_compile_actions):
    """Implementation function for the ng_module rule.

    This is exposed so that google3 can have its own entry point that re-uses this
    and is not meant as a public API.

    Args:
      ctx: the skylark rule context
      ts_compile_actions: generates all the actions to run an ngc compilation

    Returns:
      the result of the ng_module rule as a dict, suitable for
      conversion by ts_providers_dict_to_struct
    """

    is_legacy_ngc = _is_legacy_ngc(ctx)

    providers = ts_compile_actions(
        ctx,
        is_library = True,
        deps = ctx.attr.deps,
        compile_action = _prodmode_compile_action,
        devmode_compile_action = _devmode_compile_action,
        tsc_wrapped_tsconfig = _ngc_tsconfig,
        outputs = _ts_expected_outs,
    )

    outs = _expected_outs(ctx)

    if is_legacy_ngc:
        providers["angular"] = {
            "summaries": outs.summaries,
            "metadata": outs.metadata,
        }
        providers["ngc_messages"] = outs.i18n_messages

    if is_legacy_ngc and _should_produce_flat_module_outs(ctx):
        if len(outs.metadata) > 1:
            fail("expecting exactly one metadata output for " + str(ctx.label))

        providers["angular"]["flat_module_metadata"] = struct(
            module_name = ctx.attr.module_name,
            metadata_file = outs.metadata[0],
            typings_file = outs.bundle_index_typings,
            flat_module_out_file = _flat_module_out_file(ctx),
        )

    if outs.dts_bundles != None:
        providers["dts_bundles"] = outs.dts_bundles

    return providers

def _ng_module_impl(ctx):
    return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts))<commit_msg>build: add JSEcmaScripModuleInfo provider to ng_module (#32889)<commit_after># Copyright Google Inc. All Rights Reserved.
#
# Use of this source code is governed by an MIT-style license that can be
# found in the LICENSE file at https://angular.io/license
"""Run Angular's AOT template compiler
"""

load(
    ":external.bzl",
    "COMMON_ATTRIBUTES",
    "COMMON_OUTPUTS",
    "DEFAULT_API_EXTRACTOR",
    "DEFAULT_NG_COMPILER",
    "DEFAULT_NG_XI18N",
    "DEPS_ASPECTS",
    "NodeModuleSources",
    "TsConfigInfo",
    "collect_node_modules_aspect",
    "compile_ts",
    "ts_providers_dict_to_struct",
    "tsc_wrapped_tsconfig",
)
load("@build_bazel_rules_nodejs//:providers.bzl", "transitive_js_ecma_script_module_info")

_FLAT_DTS_FILE_SUFFIX = ".bundle.d.ts"
_R3_SYMBOLS_DTS_FILE = "src/r3_symbols.d.ts"

def compile_strategy(ctx):
    """Detect which strategy should be used to implement ng_module.

    Depending on the value of the 'compile' define flag, ng_module
    can be implemented in various ways. This function reads the configuration passed by the user and
    determines which mode is active.

    Args:
      ctx: skylark rule execution context

    Returns:
      one of 'legacy' or 'aot' depending on the configuration in ctx
    """

    strategy = "legacy"
    if "compile" in ctx.var:
        strategy = ctx.var["compile"]

    # Enable Angular targets extracted by Kythe Angular indexer to be compiled with the Ivy compiler architecture.
    # TODO(ayazhafiz): remove once Ivy has landed as the default in g3.
    if ctx.var.get("GROK_ELLIPSIS_BUILD", None) != None:
        strategy = "aot"

    if strategy not in ["legacy", "aot"]:
        fail("Unknown --define=compile value '%s'" % strategy)

    return strategy

def _compiler_name(ctx):
    """Selects a user-visible name depending on the current compilation strategy.

    Args:
      ctx: skylark rule execution context

    Returns:
      the name of the current compiler to be displayed in build output
    """

    strategy = compile_strategy(ctx)
    if strategy == "legacy":
        return "ngc"
    elif strategy == "aot":
        return "ngtsc"
    else:
        fail("unreachable")

def _enable_ivy_value(ctx):
    """Determines the value of the enableIvy option in the generated tsconfig.

    Args:
      ctx: skylark rule execution context

    Returns:
      the value of enableIvy that needs to be set in angularCompilerOptions in the generated tsconfig
    """

    strategy = compile_strategy(ctx)
    if strategy == "legacy":
        return False
    elif strategy == "aot":
        return True
    else:
        fail("unreachable")

def _is_legacy_ngc(ctx):
    """Determines whether Angular outputs will be produced by the current compilation strategy.

    Args:
      ctx: skylark rule execution context

    Returns:
      true iff the current compilation strategy will produce View Engine compilation outputs (such as
      factory files), false otherwise
    """

    strategy = compile_strategy(ctx)
    return strategy == "legacy"

def _basename_of(ctx, file):
    ext_len = len(".ts")
    if file.short_path.endswith(".ng.html"):
        ext_len = len(".ng.html")
    elif file.short_path.endswith(".html"):
        ext_len = len(".html")
    return file.short_path[len(ctx.label.package) + 1:-ext_len]

# Return true if run with bazel (the open-sourced version of blaze), false if
# run with blaze.
def _is_bazel():
    return not hasattr(native, "genmpm")

def _flat_module_out_file(ctx):
    """Provide a default for the flat_module_out_file attribute.

    We cannot use the default="" parameter of ctx.attr because the value is calculated
    from other attributes (name)

    Args:
      ctx: skylark rule execution context

    Returns:
      a basename used for the flat module out (no extension)
    """
    if hasattr(ctx.attr, "flat_module_out_file") and ctx.attr.flat_module_out_file:
        return ctx.attr.flat_module_out_file
    return "%s_public_index" % ctx.label.name

def _should_produce_dts_bundle(ctx):
    """Should we produce dts bundles.

    We only produce flatten dts outs when we expect the ng_module is meant to be published,
    based on the value of the bundle_dts attribute.

    Args:
      ctx: skylark rule execution context

    Returns:
      true when we should produce bundled dts.
    """

    # At the moment we cannot use this with ngtsc compiler since it emits
    # import * as ___ from local modules which is not supported
    # see: https://github.com/Microsoft/web-build-tools/issues/1029
    return _is_legacy_ngc(ctx) and hasattr(ctx.attr, "bundle_dts") and ctx.attr.bundle_dts

def _should_produce_r3_symbols_bundle(ctx):
    """Should we produce r3_symbols bundle.

    NGCC relies on having r3_symbols file. This file is located in @angular/core
    And should only be included when bundling core in legacy mode.

    Args:
      ctx: skylark rule execution context

    Returns:
      true when we should produce r3_symbols dts.
    """

    # iif we are compiling @angular/core with ngc we should add this addition dts bundle
    # because ngcc relies on having this file.
    # see: https://github.com/angular/angular/blob/84406e4d6d93b28b23efbb1701bc5ae1084da67b/packages/compiler-cli/src/ngcc/src/packages/entry_point_bundle.ts#L56
    # todo: alan-agius4: remove when ngcc doesn't need this anymore
    return _is_legacy_ngc(ctx) and ctx.attr.module_name == "@angular/core"

def _should_produce_flat_module_outs(ctx):
    """Should we produce flat module outputs.

    We only produce flat module outs when we expect the ng_module is meant to be published,
    based on the presence of the module_name attribute.

    Args:
      ctx: skylark rule execution context

    Returns:
      true iff we should run the bundle_index_host to produce flat module metadata and bundle index
    """
    return _is_bazel() and ctx.attr.module_name

# Calculate the expected output of the template compiler for every source in
# in the library. Most of these will be produced as empty files but it is
# unknown, without parsing, which will be empty.
def _expected_outs(ctx):
    is_legacy_ngc = _is_legacy_ngc(ctx)

    devmode_js_files = []
    closure_js_files = []
    declaration_files = []
    summary_files = []
    metadata_files = []

    factory_basename_set = depset([_basename_of(ctx, src) for src in ctx.files.factories])

    for src in ctx.files.srcs + ctx.files.assets:
        package_prefix = ctx.label.package + "/" if ctx.label.package else ""

        # Strip external repository name from path if src is from external repository
        # If src is from external repository, it's short_path will be ../<external_repo_name>/...
        short_path = src.short_path if src.short_path[0:2] != ".." else "/".join(src.short_path.split("/")[2:])

        if short_path.endswith(".ts") and not short_path.endswith(".d.ts"):
            basename = short_path[len(package_prefix):-len(".ts")]
            if (len(factory_basename_set.to_list()) == 0 or basename in factory_basename_set.to_list()):
                devmode_js = [
                    ".ngfactory.js",
                    ".ngsummary.js",
                    ".js",
                ]

                # Only ngc produces .json files, they're not needed in Ivy.
                if is_legacy_ngc:
                    summaries = [".ngsummary.json"]
                    metadata = [".metadata.json"]
                else:
                    summaries = []
                    metadata = []
            else:
                devmode_js = [".js"]
                if not _is_bazel():
                    devmode_js += [".ngfactory.js"]
                summaries = []
                metadata = []
        elif is_legacy_ngc and short_path.endswith(".css"):
            basename = short_path[len(package_prefix):-len(".css")]
            devmode_js = [
                ".css.shim.ngstyle.js",
                ".css.ngstyle.js",
            ]
            summaries = []
            metadata = []
        else:
            continue

        filter_summaries = ctx.attr.filter_summaries
        closure_js = [f.replace(".js", ".mjs") for f in devmode_js if not filter_summaries or not f.endswith(".ngsummary.js")]
        declarations = [f.replace(".js", ".d.ts") for f in devmode_js]

        devmode_js_files += [ctx.actions.declare_file(basename + ext) for ext in devmode_js]
        closure_js_files += [ctx.actions.declare_file(basename + ext) for ext in closure_js]
        declaration_files += [ctx.actions.declare_file(basename + ext) for ext in declarations]
        summary_files += [ctx.actions.declare_file(basename + ext) for ext in summaries]
        if not _is_bazel():
            metadata_files += [ctx.actions.declare_file(basename + ext) for ext in metadata]

    dts_bundles = None
    if _should_produce_dts_bundle(ctx):
        # We need to add a suffix to bundle as it might collide with the flat module dts.
        # The flat module dts out contains several other exports
        # https://github.com/angular/angular/blob/84406e4d6d93b28b23efbb1701bc5ae1084da67b/packages/compiler-cli/src/metadata/index_writer.ts#L18
        # the file name will be like 'core.bundle.d.ts'
        dts_bundles = [ctx.actions.declare_file(ctx.label.name + _FLAT_DTS_FILE_SUFFIX)]

        if _should_produce_r3_symbols_bundle(ctx):
            dts_bundles.append(ctx.actions.declare_file(_R3_SYMBOLS_DTS_FILE.replace(".d.ts", _FLAT_DTS_FILE_SUFFIX)))

    # We do this just when producing a flat module index for a publishable ng_module
    if _should_produce_flat_module_outs(ctx):
        flat_module_out = _flat_module_out_file(ctx)
        devmode_js_files.append(ctx.actions.declare_file("%s.js" % flat_module_out))
        closure_js_files.append(ctx.actions.declare_file("%s.mjs" % flat_module_out))
        bundle_index_typings = ctx.actions.declare_file("%s.d.ts" % flat_module_out)
        declaration_files.append(bundle_index_typings)
        if is_legacy_ngc:
            metadata_files.append(ctx.actions.declare_file("%s.metadata.json" % flat_module_out))
    else:
        bundle_index_typings = None

    # TODO(alxhub): i18n is only produced by the legacy compiler currently. This should be re-enabled
    # when ngtsc can extract messages
    if is_legacy_ngc and _is_bazel():
        i18n_messages_files = [ctx.actions.declare_file(ctx.label.name + "_ngc_messages.xmb")]
    elif is_legacy_ngc:
        # write the xmb file to blaze-genfiles since that path appears in the translation console keys
        i18n_messages_files = [ctx.new_file(ctx.genfiles_dir, ctx.label.name + "_ngc_messages.xmb")]
    else:
        i18n_messages_files = []

    return struct(
        closure_js = closure_js_files,
        devmode_js = devmode_js_files,
        declarations = declaration_files,
        summaries = summary_files,
        metadata = metadata_files,
        dts_bundles = dts_bundles,
        bundle_index_typings = bundle_index_typings,
        i18n_messages = i18n_messages_files,
    )

def _ngc_tsconfig(ctx, files, srcs, **kwargs):
    outs = _expected_outs(ctx)
    is_legacy_ngc = _is_legacy_ngc(ctx)
    if "devmode_manifest" in kwargs:
        expected_outs = outs.devmode_js + outs.declarations + outs.summaries + outs.metadata
    else:
        expected_outs = outs.closure_js

    angular_compiler_options = {
        "enableResourceInlining": ctx.attr.inline_resources,
        "generateCodeForLibraries": False,
        "allowEmptyCodegenFiles": True,
        # Summaries are only enabled if Angular outputs are to be produced.
        "enableSummariesForJit": is_legacy_ngc,
        "enableIvy": _enable_ivy_value(ctx),
        "fullTemplateTypeCheck": ctx.attr.type_check,
        # TODO(alxhub/arick): template type-checking for Ivy needs to be tested in g3 before it can
        # be enabled here.
        "ivyTemplateTypeCheck": False,
        # In Google3 we still want to use the symbol factory re-exports in order to
        # not break existing apps inside Google. Unlike Bazel, Google3 does not only
        # enforce strict dependencies of source files, but also for generated files
        # (such as the factory files). Therefore in order to avoid that generated files
        # introduce new module dependencies (which aren't explicitly declared), we need
        # to enable external symbol re-exports by default when running with Blaze.
        "createExternalSymbolFactoryReexports": (not _is_bazel()),
        # FIXME: wrong place to de-dupe
        "expectedOut": depset([o.path for o in expected_outs]).to_list(),
        "_useHostForImportGeneration": (not _is_bazel()),
    }

    if _should_produce_flat_module_outs(ctx):
        angular_compiler_options["flatModuleId"] = ctx.attr.module_name
        angular_compiler_options["flatModuleOutFile"] = _flat_module_out_file(ctx)
        angular_compiler_options["flatModulePrivateSymbolPrefix"] = "_".join(
            [ctx.workspace_name] + ctx.label.package.split("/") + [ctx.label.name, ""],
        )

    return dict(tsc_wrapped_tsconfig(ctx, files, srcs, **kwargs), **{
        "angularCompilerOptions": angular_compiler_options,
    })

def _collect_summaries_aspect_impl(target, ctx):
    results = depset(target.angular.summaries if hasattr(target, "angular") else [])

    # If we are visiting empty-srcs ts_library, this is a re-export
    srcs = ctx.rule.attr.srcs if hasattr(ctx.rule.attr, "srcs") else []

    # "re-export" rules should expose all the files of their deps
    if not srcs and hasattr(ctx.rule.attr, "deps"):
        for dep in ctx.rule.attr.deps:
            if (hasattr(dep, "angular")):
                results = depset(dep.angular.summaries, transitive = [results])

    return struct(collect_summaries_aspect_result = results)

_collect_summaries_aspect = aspect(
    implementation = _collect_summaries_aspect_impl,
    attr_aspects = ["deps"],
)

# Extra options passed to Node when running ngc.
_EXTRA_NODE_OPTIONS_FLAGS = [
    # Expose the v8 garbage collection API to JS.
    "--node_options=--expose-gc",
    # Show ~full stack traces, instead of cutting off after 10 items.
    "--node_options=--stack-trace-limit=100",
    # Give 4 GB RAM to node to allow bigger google3 modules to compile.
    "--node_options=--max-old-space-size=4096",
]

def ngc_compile_action(
        ctx,
        label,
        inputs,
        outputs,
        messages_out,
        tsconfig_file,
        node_opts,
        locale = None,
        i18n_args = [],
        dts_bundles_out = None,
        compile_mode = "prodmode"):
    """Helper function to create the ngc action.

    This is exposed for google3 to wire up i18n replay rules, and is not intended
    as part of the public API.

    Args:
      ctx: skylark context
      label: the label of the ng_module being compiled
      inputs: passed to the ngc action's inputs
      outputs: passed to the ngc action's outputs
      messages_out: produced xmb files
      tsconfig_file: tsconfig file with settings used for the compilation
      node_opts: list of strings, extra nodejs options.
      locale: i18n locale, or None
      i18n_args: additional command-line arguments to ngc
      dts_bundles_out: produced flattened dts file

    Returns:
      the parameters of the compilation which will be used to replay the ngc action for i18N.
    """

    is_legacy_ngc = _is_legacy_ngc(ctx)

    mnemonic = "AngularTemplateCompile"
    progress_message = "Compiling Angular templates (%s - %s) %s" % (_compiler_name(ctx), compile_mode, label)

    if locale:
        mnemonic = "AngularI18NMerging"
        supports_workers = "0"
        progress_message = ("Recompiling Angular templates (ngc - %s) %s for locale %s" %
                            (compile_mode, label, locale))
    else:
        supports_workers = str(int(ctx.attr._supports_workers))

    arguments = (list(_EXTRA_NODE_OPTIONS_FLAGS) +
                 ["--node_options=%s" % opt for opt in node_opts])

    # One at-sign makes this a params-file, enabling the worker strategy.
    # Two at-signs escapes the argument so it's passed through to ngc
    # rather than the contents getting expanded.
    if supports_workers == "1":
        arguments += ["@@" + tsconfig_file.path]
    else:
        arguments += ["-p", tsconfig_file.path]

    arguments += i18n_args

    ctx.actions.run(
        progress_message = progress_message,
        mnemonic = mnemonic,
        inputs = inputs,
        outputs = outputs,
        arguments = arguments,
        executable = ctx.executable.compiler,
        execution_requirements = {
            "supports-workers": supports_workers,
        },
    )

    if is_legacy_ngc and messages_out != None:
        # The base path is bin_dir because of the way the ngc
        # compiler host is configured. Under Blaze, we need to explicitly
        # point to genfiles/ to redirect the output.
        # See _expected_outs above, where the output path for the message file
        # is conditional on whether we are in Bazel.
        message_file_path = messages_out[0].short_path if _is_bazel() else "../genfiles/" + messages_out[0].short_path
        ctx.actions.run(
            inputs = inputs,
            outputs = messages_out,
            executable = ctx.executable.ng_xi18n,
            arguments = (_EXTRA_NODE_OPTIONS_FLAGS +
                         [tsconfig_file.path] +
                         [message_file_path]),
            progress_message = "Extracting Angular 2 messages (ng_xi18n)",
            mnemonic = "Angular2MessageExtractor",
        )

    if dts_bundles_out != None:
        # combine the inputs and outputs and filter .d.ts and json files
        filter_inputs = [f for f in inputs.to_list() + outputs if f.path.endswith(".d.ts") or f.path.endswith(".json")]

        if _should_produce_flat_module_outs(ctx):
            dts_entry_points = ["%s.d.ts" % _flat_module_out_file(ctx)]
        else:
            dts_entry_points = [ctx.attr.entry_point.label.name.replace(".ts", ".d.ts")]

        if _should_produce_r3_symbols_bundle(ctx):
            dts_entry_points.append(_R3_SYMBOLS_DTS_FILE)

        ctx.actions.run(
            progress_message = "Bundling DTS (%s) %s" % (compile_mode, str(ctx.label)),
            mnemonic = "APIExtractor",
            executable = ctx.executable.api_extractor,
            inputs = filter_inputs,
            outputs = dts_bundles_out,
            arguments = [
                tsconfig_file.path,
                ",".join(["/".join([ctx.bin_dir.path, ctx.label.package, f]) for f in dts_entry_points]),
                ",".join([f.path for f in dts_bundles_out]),
            ],
        )

    if not locale and not ctx.attr.no_i18n:
        return struct(
            label = label,
            tsconfig = tsconfig_file,
            inputs = inputs,
            outputs = outputs,
            compiler = ctx.executable.compiler,
        )

    return None

def _filter_ts_inputs(all_inputs):
    # The compiler only needs to see TypeScript sources from the npm dependencies,
    # but may need to look at package.json and ngsummary.json files as well.
    return [
        f
        for f in all_inputs
        if f.path.endswith(".js") or f.path.endswith(".ts") or f.path.endswith(".json")
    ]

def _compile_action(
        ctx,
        inputs,
        outputs,
        dts_bundles_out,
        messages_out,
        tsconfig_file,
        node_opts,
        compile_mode):
    # Give the Angular compiler all the user-listed assets
    file_inputs = list(ctx.files.assets)

    if (type(inputs) == type([])):
        file_inputs.extend(inputs)
    else:
        # inputs ought to be a list, but allow depset as well
        # so that this can change independently of rules_typescript
        # TODO(alexeagle): remove this case after update (July 2019)
        file_inputs.extend(inputs.to_list())

    if hasattr(ctx.attr, "node_modules"):
        file_inputs.extend(_filter_ts_inputs(ctx.files.node_modules))

    # If the user supplies a tsconfig.json file, the Angular compiler needs to read it
    if hasattr(ctx.attr, "tsconfig") and ctx.file.tsconfig:
        file_inputs.append(ctx.file.tsconfig)
        if TsConfigInfo in ctx.attr.tsconfig:
            file_inputs += ctx.attr.tsconfig[TsConfigInfo].deps

    # Also include files from npm fine grained deps as action_inputs.
    # These deps are identified by the NodeModuleSources provider.
    for d in ctx.attr.deps:
        if NodeModuleSources in d:
            # Note: we can't avoid calling .to_list() on sources
            file_inputs.extend(_filter_ts_inputs(d[NodeModuleSources].sources.to_list()))

    # Collect the inputs and summary files from our deps
    action_inputs = depset(
        file_inputs,
        transitive = [
            dep.collect_summaries_aspect_result
            for dep in ctx.attr.deps
            if hasattr(dep, "collect_summaries_aspect_result")
        ],
    )

    return ngc_compile_action(ctx, ctx.label, action_inputs, outputs, messages_out, tsconfig_file, node_opts, None, [], dts_bundles_out, compile_mode)

def _prodmode_compile_action(ctx, inputs, outputs, tsconfig_file, node_opts):
    outs = _expected_outs(ctx)
    return _compile_action(ctx, inputs, outputs + outs.closure_js, None, outs.i18n_messages, tsconfig_file, node_opts, "prodmode")

def _devmode_compile_action(ctx, inputs, outputs, tsconfig_file, node_opts):
    outs = _expected_outs(ctx)
    compile_action_outputs = outputs + outs.devmode_js + outs.declarations + outs.summaries + outs.metadata
    _compile_action(ctx, inputs, compile_action_outputs, outs.dts_bundles, None, tsconfig_file, node_opts, "devmode")

def _ts_expected_outs(ctx, label, srcs_files = []):
    # rules_typescript expects a function with two or more arguments, but our
    # implementation doesn't use the label(and **kwargs).
    _ignored = [label, srcs_files]
    return _expected_outs(ctx)

def ng_module_impl(ctx, ts_compile_actions):
    """Implementation function for the ng_module rule.

    This is exposed so that google3 can have its own entry point that re-uses this
    and is not meant as a public API.

    Args:
      ctx: the skylark rule context
      ts_compile_actions: generates all the actions to run an ngc compilation

    Returns:
      the result of the ng_module rule as a dict, suitable for
      conversion by ts_providers_dict_to_struct
    """

    is_legacy_ngc = _is_legacy_ngc(ctx)

    providers = ts_compile_actions(
        ctx,
        is_library = True,
        deps = ctx.attr.deps,
        compile_action = _prodmode_compile_action,
        devmode_compile_action = _devmode_compile_action,
        tsc_wrapped_tsconfig = _ngc_tsconfig,
        outputs = _ts_expected_outs,
    )

    outs = _expected_outs(ctx)

    if is_legacy_ngc:
        providers["angular"] = {
            "summaries": outs.summaries,
            "metadata": outs.metadata,
        }
        providers["ngc_messages"] = outs.i18n_messages

    if is_legacy_ngc and _should_produce_flat_module_outs(ctx):
        if len(outs.metadata) > 1:
            fail("expecting exactly one metadata output for " + str(ctx.label))

        providers["angular"]["flat_module_metadata"] = struct(
            module_name = ctx.attr.module_name,
            metadata_file = outs.metadata[0],
            typings_file = outs.bundle_index_typings,
            flat_module_out_file = _flat_module_out_file(ctx),
        )

    if outs.dts_bundles != None:
        providers["dts_bundles"] = outs.dts_bundles

    return providers

def _ng_module_impl(ctx):
    ts_providers = ng_module_impl(ctx, compile_ts)

    # Add in new JS providers
    ts_providers["providers"].extend([
        transitive_js_ecma_script_module_info(
            sources = ts_providers["typescript"]["es6_sources"],
            deps = ctx.attr.deps,
        ),
    ])

    return ts_providers_dict_to_struct(ts_providers)
{'fraction_non_alphanumeric': 0.08802357398442433, 'fraction_numerical': 0.0060197853083561355, 'mean_word_length': 2.771911718005716, 'pattern_counts': {'":': 40, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 8, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5878282', 'n_tokens_mistral': 14674, 'n_tokens_neox': 14217, 'n_words': 4034}