mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-10-06 06:21:16 +07:00
bazel jps compiler (part 2)
GitOrigin-RevId: 8e61f22cab4bbac11555cb4f2234735cb469b3c0
This commit is contained in:
committed by
intellij-monorepo-bot
parent
aa9b7f09fa
commit
7e970c6cb3
@@ -1,4 +1,4 @@
|
||||
.idea
|
||||
bazel-*
|
||||
.bazelbsp/
|
||||
src/jps-builder/jps-build-standalone*
|
||||
src/jps-builder/jps-build-standalone.jar
|
||||
@@ -2,7 +2,7 @@ load("@rules_java//java:defs.bzl", "java_import")
|
||||
load("@rules_kotlin//kotlin:core.bzl", _kt_kotlinc_options = "kt_kotlinc_options")
|
||||
load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_import", "kt_jvm_library")
|
||||
load("//:jvm.bzl", "jvm_import", "kt_kotlinc_options")
|
||||
load(":rules/trace.bzl", "trace")
|
||||
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "int_flag")
|
||||
|
||||
label_flag(
|
||||
name = "default-kotlinc-opts",
|
||||
@@ -23,15 +23,20 @@ kt_kotlinc_options(
|
||||
)
|
||||
|
||||
# --@rules_jvm//:kt_trace
|
||||
trace(
|
||||
bool_flag(
|
||||
name = "kt_trace",
|
||||
build_setting_default = False,
|
||||
)
|
||||
# --@rules_jvm//:jps_threshold=500
|
||||
int_flag(
|
||||
name = "jps_threshold",
|
||||
build_setting_default = -1,
|
||||
)
|
||||
|
||||
_kt_kotlinc_options(
|
||||
name = "rules_jvm_bootstrap_kotlinc_options",
|
||||
include_stdlibs = "none",
|
||||
visibility = ["//src:__subpackages__"],
|
||||
visibility = ["//src:__subpackages__", "//zip:__pkg__"],
|
||||
)
|
||||
|
||||
### auto-generated section `libraries` start
|
||||
@@ -77,7 +82,7 @@ jvm_import(
|
||||
visibility = ["//visibility:public"],
|
||||
runtime_deps = [
|
||||
"@lib//:jetbrains-annotations",
|
||||
":kotlinx-coroutines-core",
|
||||
":kotlinx-coroutines-core"
|
||||
],
|
||||
)
|
||||
|
||||
@@ -123,4 +128,4 @@ jvm_import(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
### auto-generated section `libraries` end
|
||||
### auto-generated section `libraries` end
|
||||
@@ -6,6 +6,7 @@ bazel_dep(name = "rules_kotlin", version = "2.0.0-jb.5")
|
||||
bazel_dep(name = "rules_graalvm", version = "0.11.3")
|
||||
bazel_dep(name = "lib")
|
||||
bazel_dep(name = "protobuf", version = "29.2")
|
||||
bazel_dep(name = "bazel_skylib", version = "1.7.1")
|
||||
|
||||
# https://github.com/bazelbuild/bazel/issues/6681#issuecomment-2188972754
|
||||
bazel_dep(name = "contrib_rules_jvm", version = "0.27.0")
|
||||
@@ -191,28 +192,28 @@ http_file(
|
||||
|
||||
http_file(
|
||||
name = "poko-compiler-plugin",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/dev/drewhamilton/poko/poko-compiler-plugin/0.18.1/poko-compiler-plugin-0.18.1.jar",
|
||||
sha256 = "4def45d16f7b077c8f4fa31f46f940bff14ecb985408fd74ac26c7910576cf00",
|
||||
downloaded_file_path = "poko-compiler-plugin-0.18.1.jar",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/dev/drewhamilton/poko/poko-compiler-plugin/0.18.2/poko-compiler-plugin-0.18.2.jar",
|
||||
sha256 = "08edf341dfa4dd0a9e15b83a2a74850baad3a4a3ca2f93aed05ca6a481b1f394",
|
||||
downloaded_file_path = "poko-compiler-plugin-0.18.2.jar",
|
||||
)
|
||||
http_file(
|
||||
name = "poko-compiler-plugin-sources",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/dev/drewhamilton/poko/poko-compiler-plugin/0.18.1/poko-compiler-plugin-0.18.1-sources.jar",
|
||||
sha256 = "e33785f02bafc11751914ff1892c0a302a6408edb0ca6076436a71150b201e55",
|
||||
downloaded_file_path = "poko-compiler-plugin-0.18.1-sources.jar",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/dev/drewhamilton/poko/poko-compiler-plugin/0.18.2/poko-compiler-plugin-0.18.2-sources.jar",
|
||||
sha256 = "02f7ac954f3a9574b37a54d7bdc79190325b9ee38a3244256a84d6d760eb6f1f",
|
||||
downloaded_file_path = "poko-compiler-plugin-0.18.2-sources.jar",
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "kotlinx-coroutines-core-jvm",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.9.0/kotlinx-coroutines-core-jvm-1.9.0.jar",
|
||||
sha256 = "ad89c2892235e670f222d819cb3d81188143cb19a05b59df9889ae4269f5c70a",
|
||||
downloaded_file_path = "kotlinx-coroutines-core-jvm-1.9.0.jar",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.10.1/kotlinx-coroutines-core-jvm-1.10.1.jar",
|
||||
sha256 = "069c5988633230e074ec0d39321ec3cdaa4547c49e90ba936c63d8fc91c8c00d",
|
||||
downloaded_file_path = "kotlinx-coroutines-core-jvm-1.10.1.jar",
|
||||
)
|
||||
http_file(
|
||||
name = "kotlinx-coroutines-core-jvm-sources",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.9.0/kotlinx-coroutines-core-jvm-1.9.0-sources.jar",
|
||||
sha256 = "01e0f26a274d8f2f2ae702b161bc0980cea817bfcaf66ca4944434ea7b601771",
|
||||
downloaded_file_path = "kotlinx-coroutines-core-jvm-1.9.0-sources.jar",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.10.1/kotlinx-coroutines-core-jvm-1.10.1-sources.jar",
|
||||
sha256 = "3faa298de8000d68744a179f7d413ab1672acf1d77cb8d6be57a78544657edd2",
|
||||
downloaded_file_path = "kotlinx-coroutines-core-jvm-1.10.1-sources.jar",
|
||||
)
|
||||
|
||||
### auto-generated section `libraries` end
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
"7441a6394dca477b3f8b7c86f17bef035f705a8aaa11f53065d380300158177c",
|
||||
"68f0f47297335e2c3fb5a904ade91aadef884b3aa8f558a4efac16ba803b8b27"
|
||||
],
|
||||
"dev.drewhamilton.poko:poko-compiler-plugin:0.18.1": [
|
||||
"4def45d16f7b077c8f4fa31f46f940bff14ecb985408fd74ac26c7910576cf00",
|
||||
"e33785f02bafc11751914ff1892c0a302a6408edb0ca6076436a71150b201e55"
|
||||
"dev.drewhamilton.poko:poko-compiler-plugin:0.18.2": [
|
||||
"08edf341dfa4dd0a9e15b83a2a74850baad3a4a3ca2f93aed05ca6a481b1f394",
|
||||
"02f7ac954f3a9574b37a54d7bdc79190325b9ee38a3244256a84d6d760eb6f1f"
|
||||
],
|
||||
"org.jetbrains.kotlin:kotlin-build-tools-api:2.1.20-Beta1": [
|
||||
"09ddebb845f30310395d91c084f65d2133484080dfac1977502776b77345f3d7",
|
||||
@@ -43,8 +43,8 @@
|
||||
"f48b6323092cb30f860cb5c880d6107982d82941166073871a36349dfd3f7909",
|
||||
"4aa4da6df01507c0fe9b48ff4f3d2a5eb5edf5f894850169c90efc89330a9d8b"
|
||||
],
|
||||
"org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.9.0": [
|
||||
"ad89c2892235e670f222d819cb3d81188143cb19a05b59df9889ae4269f5c70a",
|
||||
"01e0f26a274d8f2f2ae702b161bc0980cea817bfcaf66ca4944434ea7b601771"
|
||||
"org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.1": [
|
||||
"069c5988633230e074ec0d39321ec3cdaa4547c49e90ba936c63d8fc91c8c00d",
|
||||
"3faa298de8000d68744a179f7d413ab1672acf1d77cb8d6be57a78544657edd2"
|
||||
]
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
- id: org.jetbrains.kotlin:kotlin-compose-compiler-plugin
|
||||
version: 2.1.20-Beta1
|
||||
- id: dev.drewhamilton.poko:poko-compiler-plugin
|
||||
version: 0.18.1
|
||||
version: 0.18.2
|
||||
# other libs
|
||||
- id: org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm
|
||||
version: 1.9.0
|
||||
version: 1.10.1
|
||||
|
||||
@@ -133,6 +133,7 @@ common_attr = add_dicts(
|
||||
),
|
||||
"_reduced_classpath": attr.bool(default = False),
|
||||
"_trace": attr.label(default = "//:kt_trace"),
|
||||
"_jps_threshold": attr.label(default = "//:jps_threshold"),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -45,20 +45,6 @@ def get_associates(ctx):
|
||||
module_name = module_names.keys()[0],
|
||||
)
|
||||
|
||||
def flatten_jars(nested_jars_depset):
|
||||
"""Returns a list of strings containing the compile_jars for depset of targets.
|
||||
|
||||
This ends up unwinding the nesting of depsets, since compile_jars contains depsets inside
|
||||
the nested_jars targets, which themselves are depsets. This function is intended to be called
|
||||
lazily form within Args.add_all(map_each) as it collapses depsets.
|
||||
"""
|
||||
compile_jars_depsets = [
|
||||
target[JavaInfo].compile_jars
|
||||
for target in nested_jars_depset.to_list()
|
||||
if target[JavaInfo].compile_jars
|
||||
]
|
||||
return [file.path for file in depset(transitive = compile_jars_depsets).to_list()]
|
||||
|
||||
def _derive_module_name(ctx):
|
||||
"""Gets the `module_name` attribute if it's set in the ctx, otherwise derive a unique module name using the elements
|
||||
found in the label."""
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
load("//:rules/trace.bzl", "TraceInfo")
|
||||
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
|
||||
load("@rules_java//java:defs.bzl", "JavaInfo")
|
||||
load("//:rules/impl/associates.bzl", "get_associates")
|
||||
load("//:rules/impl/kotlinc-options.bzl", "KotlincOptions", "kotlinc_options_to_flags")
|
||||
|
||||
visibility("private")
|
||||
|
||||
def init_builder_args(ctx, rule_kind, module_name):
|
||||
def init_builder_args(ctx, rule_kind, associates, transitiveInputs, plugins, compile_jars):
|
||||
"""Initialize an arg object for a task that will be executed by the Kotlin Builder."""
|
||||
args = ctx.actions.args()
|
||||
args.set_param_file_format("multiline")
|
||||
@@ -10,11 +13,49 @@ def init_builder_args(ctx, rule_kind, module_name):
|
||||
|
||||
args.add("--target_label", ctx.label)
|
||||
args.add("--rule_kind", rule_kind)
|
||||
args.add("--kotlin_module_name", module_name)
|
||||
args.add("--kotlin_module_name", associates.module_name)
|
||||
|
||||
trace = ctx.attr._trace[TraceInfo].trace
|
||||
kotlinc_options = ctx.attr.kotlinc_opts[KotlincOptions]
|
||||
|
||||
if ctx.attr._trace[TraceInfo].trace:
|
||||
if kotlinc_options.report_unused_deps:
|
||||
deps_artifacts = _collect_jdeps_from_artifacts(ctx.attr.deps + associates.targets)
|
||||
transitiveInputs.append(deps_artifacts)
|
||||
args.add_all("--deps_artifacts", deps_artifacts)
|
||||
|
||||
if associates:
|
||||
args.add_all("--friends", associates.jars, map_each = _flatten_jars)
|
||||
|
||||
if ctx.attr._trace[BuildSettingInfo].value:
|
||||
args.add("--trace")
|
||||
|
||||
kotlinc_options_to_flags(kotlinc_options, args)
|
||||
|
||||
args.add_all("--opt-in", kotlinc_options.opt_in)
|
||||
args.add_all("--classpath", compile_jars)
|
||||
|
||||
for id, classpath in plugins.compile_phase.classpath.items():
|
||||
args.add("--plugin-id", id)
|
||||
args.add_joined("--plugin-classpath", classpath, omit_if_empty = False, join_with = ":")
|
||||
transitiveInputs.append(classpath)
|
||||
|
||||
return args
|
||||
|
||||
def _collect_jdeps_from_artifacts(all_targets):
|
||||
"""Collect Jdeps artifacts if required."""
|
||||
|
||||
# collect `.jdeps` outputs from targets that contain JavaInfo and have a `jdeps` output
|
||||
return depset([t[JavaInfo].outputs.jdeps for t in all_targets if JavaInfo in t and t[JavaInfo].outputs.jdeps])
|
||||
|
||||
def _flatten_jars(nested_jars_depset):
|
||||
"""Returns a list of strings containing the compile_jars for depset of targets.
|
||||
|
||||
This ends up unwinding the nesting of depsets, since compile_jars contains depsets inside
|
||||
the nested_jars targets, which themselves are depsets. This function is intended to be called
|
||||
lazily form within Args.add_all(map_each) as it collapses depsets.
|
||||
"""
|
||||
compile_jars_depsets = [
|
||||
target[JavaInfo].compile_jars
|
||||
for target in nested_jars_depset.to_list()
|
||||
if target[JavaInfo].compile_jars
|
||||
]
|
||||
return [file.path for file in depset(transitive = compile_jars_depsets).to_list()]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
|
||||
|
||||
# Copyright 2018 The Bazel Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -11,11 +13,7 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
load(
|
||||
"@rules_java//java:defs.bzl",
|
||||
"JavaInfo",
|
||||
"java_common",
|
||||
)
|
||||
load("@rules_java//java:defs.bzl", "JavaInfo", "java_common")
|
||||
load(
|
||||
"@rules_kotlin//kotlin/internal:defs.bzl",
|
||||
_JAVA_RUNTIME_TOOLCHAIN_TYPE = "JAVA_RUNTIME_TOOLCHAIN_TYPE",
|
||||
@@ -27,11 +25,10 @@ load(
|
||||
)
|
||||
load("@rules_kotlin//kotlin/internal:opts.bzl", "JavacOptions")
|
||||
load("//:rules/common-attrs.bzl", "add_dicts")
|
||||
load("//:rules/impl/associates.bzl", "flatten_jars", "get_associates")
|
||||
load("//:rules/impl/associates.bzl", "get_associates")
|
||||
load("//:rules/impl/builder-args.bzl", "init_builder_args")
|
||||
load("//:rules/impl/kotlinc-options.bzl", "KotlincOptions", "kotlinc_options_to_flags")
|
||||
load("//:rules/impl/kotlinc-options.bzl", "KotlincOptions")
|
||||
|
||||
# UTILITY ##############################################################################################################
|
||||
def find_java_toolchain(ctx, target):
|
||||
if _JAVA_TOOLCHAIN_TYPE in ctx.toolchains:
|
||||
return ctx.toolchains[_JAVA_TOOLCHAIN_TYPE].java
|
||||
@@ -45,12 +42,6 @@ def find_java_runtime_toolchain(ctx, target):
|
||||
def _java_info(target):
|
||||
return target[JavaInfo] if JavaInfo in target else None
|
||||
|
||||
def _deps_artifacts(all_targets):
|
||||
"""Collect Jdeps artifacts if required."""
|
||||
|
||||
# collect `.jdeps` outputs from targets that contain JavaInfo and have a `jdeps` output
|
||||
return depset([t[JavaInfo].outputs.jdeps for t in all_targets if JavaInfo in t and t[JavaInfo].outputs.jdeps])
|
||||
|
||||
def _partitioned_srcs(srcs):
|
||||
"""Creates a struct of srcs sorted by extension. Fails if there are no sources."""
|
||||
kt_srcs = []
|
||||
@@ -134,12 +125,6 @@ def _collect_plugins_for_export(local, exports):
|
||||
],
|
||||
)
|
||||
|
||||
def _format_compile_plugin_options(o):
|
||||
"""Format compiler option into id:value for cmd line."""
|
||||
return [
|
||||
"%s:%s" % (o.id, o.value),
|
||||
]
|
||||
|
||||
def _new_plugins_from(targets):
|
||||
"""Returns a struct containing the plugin metadata for the given targets.
|
||||
|
||||
@@ -214,7 +199,6 @@ def _new_plugin_from(plugin_id_to_configuration, plugins_for_phase):
|
||||
options = options,
|
||||
)
|
||||
|
||||
# INTERNAL ACTIONS #####################################################################################################
|
||||
def _fold_jars_action(ctx, rule_kind, toolchains, output_jar, input_jars, action_type = ""):
|
||||
"""Set up an action to Fold the input jars into a normalized output jar."""
|
||||
args = ctx.actions.args()
|
||||
@@ -271,8 +255,6 @@ def _run_merge_jdeps_action(ctx, report_unused_deps, jdeps, output, deps):
|
||||
progress_message = progress_message,
|
||||
)
|
||||
|
||||
empty_depset = depset()
|
||||
|
||||
def _run_jvm_builder_action(
|
||||
mnemonic,
|
||||
executable,
|
||||
@@ -280,62 +262,22 @@ def _run_jvm_builder_action(
|
||||
rule_kind,
|
||||
toolchains,
|
||||
srcs,
|
||||
generated_src_jars,
|
||||
associates,
|
||||
transitiveInputs,
|
||||
compile_deps,
|
||||
annotation_processors,
|
||||
transitive_runtime_jars,
|
||||
plugins,
|
||||
args,
|
||||
outputs):
|
||||
"""Creates a KotlinBuilder action invocation."""
|
||||
kotlinc_options = ctx.attr.kotlinc_opts[KotlincOptions]
|
||||
|
||||
for f, path in outputs.items():
|
||||
args.add("--" + f, path)
|
||||
|
||||
kotlinc_options_to_flags(kotlinc_options, args)
|
||||
|
||||
args.add_all("--opt_in", kotlinc_options.opt_in)
|
||||
|
||||
args.add_all("--classpath", compile_deps.compile_jars)
|
||||
|
||||
if ctx.attr._reduced_classpath:
|
||||
args.add("--reduced-classpath-mode", "true")
|
||||
args.add_all("--direct-dependencies", _java_infos_to_compile_jars(compile_deps.deps))
|
||||
|
||||
deps_artifacts = _deps_artifacts(ctx.attr.deps + associates.targets) if kotlinc_options.report_unused_deps else empty_depset
|
||||
if kotlinc_options.report_unused_deps:
|
||||
args.add_all("--deps_artifacts", deps_artifacts)
|
||||
|
||||
if associates:
|
||||
args.add_all("--friend_paths", associates.jars, map_each = flatten_jars)
|
||||
if ctx.coverage_instrumented():
|
||||
args.add("--instrument_coverage")
|
||||
|
||||
all_classpath = depset()
|
||||
for id, classpath in plugins.compile_phase.classpath.items():
|
||||
args.add("--plugin-id", id)
|
||||
args.add_joined("--plugin-classpath", classpath, omit_if_empty = False, join_with = ":")
|
||||
all_classpath = depset(transitive = [classpath, all_classpath])
|
||||
|
||||
# args.add_all("--compiler_plugin_options", plugins.compile_phase.options, map_each = _format_compile_plugin_options)
|
||||
|
||||
progress_message = "Compile %%{label} { kt: %d, java: %d }" % (len(srcs.kt), len(srcs.java))
|
||||
|
||||
ctx.actions.run(
|
||||
mnemonic = mnemonic,
|
||||
inputs = depset(
|
||||
srcs.all_srcs + generated_src_jars,
|
||||
transitive = [
|
||||
compile_deps.compile_jars,
|
||||
transitive_runtime_jars,
|
||||
deps_artifacts,
|
||||
all_classpath,
|
||||
],
|
||||
),
|
||||
inputs = depset(srcs.all_srcs, transitive = transitiveInputs),
|
||||
use_default_shell_env = True,
|
||||
outputs = [f for f in outputs.values()],
|
||||
outputs = outputs,
|
||||
executable = ctx.attr._kotlin_builder.files_to_run.executable,
|
||||
execution_requirements = {
|
||||
"supports-workers": "1",
|
||||
@@ -349,8 +291,6 @@ def _run_jvm_builder_action(
|
||||
},
|
||||
)
|
||||
|
||||
# MAIN ACTIONS #########################################################################################################
|
||||
|
||||
def kt_jvm_produce_jar_actions(ctx, rule_kind):
|
||||
"""This macro sets up a compile action for a Kotlin jar.
|
||||
|
||||
@@ -372,15 +312,22 @@ def kt_jvm_produce_jar_actions(ctx, rule_kind):
|
||||
)
|
||||
|
||||
perTargetPlugins = ctx.attr.plugins if hasattr(ctx.attr, "plugins") else []
|
||||
transitive_runtime_jars = _targets_to_transitive_runtime_jars(perTargetPlugins + ctx.attr.deps)
|
||||
plugins = _new_plugins_from(perTargetPlugins + _exported_plugins(deps = ctx.attr.deps))
|
||||
|
||||
# merge outputs into final runtime jar
|
||||
output_jar = ctx.actions.declare_file(ctx.label.name + ".jar")
|
||||
|
||||
outputs_struct = None
|
||||
kotlinc_options = ctx.attr.kotlinc_opts[KotlincOptions]
|
||||
if kotlinc_options.jps_threshold != -1 and len(srcs.kt) >= kotlinc_options.jps_threshold:
|
||||
jps_threshold = ctx.attr._jps_threshold[BuildSettingInfo].value
|
||||
if jps_threshold == -1:
|
||||
kotlinc_options = ctx.attr.kotlinc_opts[KotlincOptions]
|
||||
jps_threshold = kotlinc_options.jps_threshold
|
||||
|
||||
transitiveInputs = [compile_deps.compile_jars]
|
||||
_collect_runtime_jars(perTargetPlugins, transitiveInputs)
|
||||
_collect_runtime_jars(ctx.attr.deps, transitiveInputs)
|
||||
|
||||
if jps_threshold != -1 and len(srcs.kt) >= jps_threshold:
|
||||
outputs_struct = _run_jps_builder(
|
||||
ctx = ctx,
|
||||
output_jar = output_jar,
|
||||
@@ -390,7 +337,7 @@ def kt_jvm_produce_jar_actions(ctx, rule_kind):
|
||||
generated_ksp_src_jars = [],
|
||||
associates = associates,
|
||||
compile_deps = compile_deps,
|
||||
transitive_runtime_jars = transitive_runtime_jars,
|
||||
transitiveInputs = transitiveInputs,
|
||||
plugins = plugins,
|
||||
)
|
||||
else:
|
||||
@@ -404,7 +351,7 @@ def kt_jvm_produce_jar_actions(ctx, rule_kind):
|
||||
associates = associates,
|
||||
compile_deps = compile_deps,
|
||||
annotation_processors = [],
|
||||
transitive_runtime_jars = transitive_runtime_jars,
|
||||
transitiveInputs = transitiveInputs,
|
||||
plugins = plugins,
|
||||
)
|
||||
|
||||
@@ -420,13 +367,6 @@ def kt_jvm_produce_jar_actions(ctx, rule_kind):
|
||||
java_toolchain = toolchains.java,
|
||||
)
|
||||
|
||||
generated_source_jar = java_common.pack_sources(
|
||||
ctx.actions,
|
||||
output_source_jar = ctx.actions.declare_file(ctx.label.name + "-gensrc.jar"),
|
||||
source_jars = generated_src_jars,
|
||||
java_toolchain = toolchains.java,
|
||||
) if generated_src_jars else None
|
||||
|
||||
java_info = JavaInfo(
|
||||
output_jar = output_jar,
|
||||
compile_jar = compile_jar,
|
||||
@@ -436,19 +376,10 @@ def kt_jvm_produce_jar_actions(ctx, rule_kind):
|
||||
runtime_deps = [_java_info(d) for d in ctx.attr.runtime_deps],
|
||||
exports = [_java_info(d) for d in getattr(ctx.attr, "exports", [])],
|
||||
neverlink = getattr(ctx.attr, "neverlink", False),
|
||||
generated_source_jar = generated_source_jar,
|
||||
)
|
||||
|
||||
instrumented_files = coverage_common.instrumented_files_info(
|
||||
ctx,
|
||||
source_attributes = ["srcs"],
|
||||
dependency_attributes = ["deps", "exports", "associates"],
|
||||
extensions = ["kt", "java"],
|
||||
)
|
||||
|
||||
return struct(
|
||||
java = java_info,
|
||||
instrumented_files = instrumented_files,
|
||||
kt = _KtJvmInfo(
|
||||
srcs = ctx.files.srcs,
|
||||
module_name = associates.module_name,
|
||||
@@ -536,7 +467,7 @@ def _run_kt_java_builder_actions(
|
||||
associates,
|
||||
compile_deps,
|
||||
annotation_processors,
|
||||
transitive_runtime_jars,
|
||||
transitiveInputs,
|
||||
plugins):
|
||||
"""Runs the necessary KotlinBuilder and JavaBuilder actions to compile a jar
|
||||
|
||||
@@ -546,7 +477,7 @@ def _run_kt_java_builder_actions(
|
||||
has_kt_sources = srcs.kt or srcs.src_jars
|
||||
|
||||
java_infos = []
|
||||
outputs = None
|
||||
outputs = []
|
||||
kt_compile_jar = None
|
||||
|
||||
kotlinc_options = ctx.attr.kotlinc_opts[KotlincOptions]
|
||||
@@ -559,38 +490,36 @@ def _run_kt_java_builder_actions(
|
||||
|
||||
# build Kotlin
|
||||
if has_kt_sources:
|
||||
args = init_builder_args(ctx, rule_kind, associates, transitiveInputs, plugins = plugins, compile_jars = compile_deps.compile_jars)
|
||||
|
||||
kt_output_jar = ctx.actions.declare_file(ctx.label.name + "-kt.jar") if has_java_sources else output_jar
|
||||
outputs.append(kt_output_jar)
|
||||
|
||||
if not "kt_abi_plugin_incompatible" in ctx.attr.tags:
|
||||
kt_compile_jar = ctx.actions.declare_file(ctx.label.name + ("-kt.abi.jar" if has_java_sources else ".abi.jar"))
|
||||
outputs = {
|
||||
"output": kt_output_jar,
|
||||
"abi_jar": kt_compile_jar,
|
||||
}
|
||||
outputs.append(kt_compile_jar)
|
||||
args.add("--abi-out", kt_compile_jar)
|
||||
else:
|
||||
kt_compile_jar = kt_output_jar
|
||||
outputs = {
|
||||
"output": kt_output_jar,
|
||||
}
|
||||
|
||||
args.add("--out", kt_output_jar)
|
||||
|
||||
kt_jdeps = None
|
||||
if emit_jdeps:
|
||||
kt_jdeps = ctx.actions.declare_file(ctx.label.name + "-kt.jdeps")
|
||||
outputs["kotlin_output_jdeps"] = kt_jdeps
|
||||
args.add("--jdeps-out", kt_jdeps)
|
||||
outputs.append(kt_jdeps)
|
||||
|
||||
_run_jvm_builder_action(
|
||||
mnemonic = "KotlinCompile",
|
||||
executable = ctx.attr._kotlin_builder.files_to_run.executable,
|
||||
args = init_builder_args(ctx, rule_kind, associates.module_name),
|
||||
args = args,
|
||||
ctx = ctx,
|
||||
rule_kind = rule_kind,
|
||||
toolchains = toolchains,
|
||||
srcs = srcs,
|
||||
generated_src_jars = generated_ksp_src_jars,
|
||||
associates = associates,
|
||||
transitiveInputs = transitiveInputs,
|
||||
compile_deps = compile_deps,
|
||||
annotation_processors = [],
|
||||
transitive_runtime_jars = transitive_runtime_jars,
|
||||
plugins = plugins,
|
||||
outputs = outputs,
|
||||
)
|
||||
|
||||
@@ -662,13 +591,13 @@ def _run_kt_java_builder_actions(
|
||||
ctx.actions.symlink(output = output_jar, target_file = toolchains.kt.empty_jar)
|
||||
|
||||
annotation_processing = None
|
||||
if annotation_processors:
|
||||
outputs_list = [java_info.outputs for java_info in java_infos]
|
||||
annotation_processing = _create_annotation_processing(
|
||||
annotation_processors = annotation_processors,
|
||||
ap_class_jar = [jars.class_jar for outputs in outputs_list for jars in outputs.jars][0],
|
||||
ap_source_jar = ap_generated_src_jar,
|
||||
)
|
||||
# if annotation_processors:
|
||||
# outputs_list = [java_info.outputs for java_info in java_infos]
|
||||
# annotation_processing = _create_annotation_processing(
|
||||
# annotation_processors = annotation_processors,
|
||||
# ap_class_jar = [jars.class_jar for outputs in outputs_list for jars in outputs.jars][0],
|
||||
# ap_source_jar = ap_generated_src_jar,
|
||||
# )
|
||||
|
||||
return struct(
|
||||
compile_jar = compile_jar,
|
||||
@@ -686,43 +615,36 @@ def _run_jps_builder(
|
||||
generated_ksp_src_jars,
|
||||
associates,
|
||||
compile_deps,
|
||||
transitive_runtime_jars,
|
||||
transitiveInputs,
|
||||
plugins):
|
||||
"""Runs the necessary JpsBuilder actions to compile a jar
|
||||
|
||||
Returns:
|
||||
A struct containing the a list of output_jars and a struct annotation_processing jars
|
||||
"""
|
||||
outputs = None
|
||||
kt_compile_jar = None
|
||||
|
||||
kotlinc_options = ctx.attr.kotlinc_opts[KotlincOptions]
|
||||
args = init_builder_args(ctx, rule_kind, associates, transitiveInputs, plugins = plugins, compile_jars = compile_deps.compile_jars)
|
||||
args.add("--out", output_jar)
|
||||
|
||||
outputs = []
|
||||
abi_jar = output_jar
|
||||
if not "kt_abi_plugin_incompatible" in ctx.attr.tags:
|
||||
abi_jar = ctx.actions.declare_file(ctx.label.name + ".abi.jar")
|
||||
outputs = [output_jar, abi_jar]
|
||||
args.add("--abi-out", abi_jar)
|
||||
else:
|
||||
outputs = [output_jar]
|
||||
|
||||
# todo JDEPS for JVM
|
||||
emit_jdeps = False
|
||||
# emit_jdeps = kotlinc_options.emit_jdeps
|
||||
|
||||
kt_output_jar = output_jar
|
||||
|
||||
# todo ABI for JPS
|
||||
if False:
|
||||
kt_compile_jar = ctx.actions.declare_file(ctx.label.name + ".abi.jar")
|
||||
outputs = {
|
||||
"output": kt_output_jar,
|
||||
"abi_jar": kt_compile_jar,
|
||||
}
|
||||
else:
|
||||
kt_compile_jar = kt_output_jar
|
||||
outputs = {
|
||||
"output": kt_output_jar,
|
||||
}
|
||||
|
||||
kt_jdeps = None
|
||||
#kotlinc_options = ctx.attr.kotlinc_opts[KotlincOptions]
|
||||
jdeps = None
|
||||
emit_jdeps = False
|
||||
if emit_jdeps:
|
||||
kt_jdeps = ctx.actions.declare_file(ctx.label.name + "-kt.jdeps")
|
||||
outputs["kotlin_output_jdeps"] = kt_jdeps
|
||||
jdeps = ctx.actions.declare_file(ctx.label.name + ".jdeps")
|
||||
outputs.append(jdeps)
|
||||
args.add("--jdeps-out", jdeps)
|
||||
|
||||
args = init_builder_args(ctx, rule_kind, associates.module_name)
|
||||
javac_opts = ctx.attr.javac_opts[JavacOptions] if ctx.attr.javac_opts else None
|
||||
if javac_opts and javac_opts.add_exports:
|
||||
args.add_all("--add-export", javac_opts.add_exports)
|
||||
@@ -735,53 +657,19 @@ def _run_jps_builder(
|
||||
rule_kind = rule_kind,
|
||||
toolchains = toolchains,
|
||||
srcs = srcs,
|
||||
generated_src_jars = generated_ksp_src_jars,
|
||||
associates = associates,
|
||||
transitiveInputs = transitiveInputs,
|
||||
compile_deps = compile_deps,
|
||||
annotation_processors = [],
|
||||
transitive_runtime_jars = transitive_runtime_jars,
|
||||
plugins = plugins,
|
||||
outputs = outputs,
|
||||
)
|
||||
|
||||
java_infos = [
|
||||
JavaInfo(
|
||||
output_jar = kt_output_jar,
|
||||
compile_jar = kt_compile_jar,
|
||||
jdeps = kt_jdeps,
|
||||
deps = compile_deps.deps,
|
||||
runtime_deps = [d[JavaInfo] for d in ctx.attr.runtime_deps],
|
||||
exports = [d[JavaInfo] for d in getattr(ctx.attr, "exports", [])],
|
||||
neverlink = getattr(ctx.attr, "neverlink", False),
|
||||
),
|
||||
]
|
||||
|
||||
return struct(
|
||||
compile_jar = kt_compile_jar,
|
||||
compile_jar = abi_jar,
|
||||
generated_src_jars = generated_ksp_src_jars,
|
||||
annotation_processing = None,
|
||||
output_jdeps = _get_or_create_single_jdeps_output(kotlinc_options.report_unused_deps, java_infos, ctx, compile_deps) if emit_jdeps else None,
|
||||
output_jdeps = jdeps,
|
||||
)
|
||||
|
||||
def _create_annotation_processing(annotation_processors, ap_class_jar, ap_source_jar):
|
||||
"""Creates the annotation_processing field for Kt to match what JavaInfo
|
||||
|
||||
The Bazel Plugin IDE logic is based on this assumption in order to locate the Annotation
|
||||
Processor generated source code.
|
||||
|
||||
See https://docs.bazel.build/versions/master/skylark/lib/JavaInfo.html#annotation_processing
|
||||
"""
|
||||
if annotation_processors:
|
||||
return struct(
|
||||
enabled = True,
|
||||
class_jar = ap_class_jar,
|
||||
source_jar = ap_source_jar,
|
||||
)
|
||||
return None
|
||||
|
||||
def _targets_to_transitive_runtime_jars(targets):
|
||||
transitive = []
|
||||
def _collect_runtime_jars(targets, transitive):
|
||||
for t in targets:
|
||||
if JavaInfo in t:
|
||||
transitive.append(t[JavaInfo].plugins.processor_jars)
|
||||
return depset(transitive = transitive)
|
||||
transitive.append(t[JavaInfo].plugins.processor_jars)
|
||||
@@ -13,7 +13,6 @@ def _make_providers(ctx, providers):
|
||||
return [
|
||||
providers.java,
|
||||
providers.kt,
|
||||
providers.instrumented_files,
|
||||
DefaultInfo(
|
||||
files = depset(files),
|
||||
runfiles = ctx.runfiles(
|
||||
|
||||
@@ -119,7 +119,6 @@ def _jvm_test(ctx):
|
||||
return [
|
||||
providers.java,
|
||||
providers.kt,
|
||||
providers.instrumented_files,
|
||||
DefaultInfo(
|
||||
files = depset(files),
|
||||
runfiles = ctx.runfiles(
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
TraceInfo = provider(doc = "", fields = ["trace"])
|
||||
|
||||
def _impl(ctx):
|
||||
trace = ctx.build_setting_value
|
||||
return TraceInfo(trace = trace)
|
||||
|
||||
trace = rule(
|
||||
implementation = _impl,
|
||||
build_setting = config.bool(flag = True),
|
||||
)
|
||||
@@ -7,7 +7,7 @@ import java.util.regex.Pattern
|
||||
|
||||
private val FLAG_FILE_RE: Regex = Pattern.compile("""^--flagfile=((.*)-(\d+).params)$""").toRegex()
|
||||
|
||||
fun parseArgs(args: List<String>): ArgMap<JvmBuilderFlags> {
|
||||
fun parseArgs(args: Array<String>): ArgMap<JvmBuilderFlags> {
|
||||
check(args.isNotEmpty()) {
|
||||
"expected at least a single arg got: ${args.joinToString(" ")}"
|
||||
}
|
||||
@@ -15,7 +15,7 @@ fun parseArgs(args: List<String>): ArgMap<JvmBuilderFlags> {
|
||||
return createArgMap(
|
||||
args = FLAG_FILE_RE.matchEntire(args[0])?.groups?.get(1)?.let {
|
||||
Files.readAllLines(Path.of(it.value))
|
||||
} ?: args,
|
||||
} ?: args.asList(),
|
||||
enumClass = JvmBuilderFlags::class.java,
|
||||
)
|
||||
}
|
||||
@@ -29,7 +29,9 @@ enum class JvmBuilderFlags {
|
||||
PLUGIN_ID,
|
||||
PLUGIN_CLASSPATH,
|
||||
|
||||
OUTPUT,
|
||||
OUT,
|
||||
ABI_OUT,
|
||||
|
||||
RULE_KIND,
|
||||
KOTLIN_MODULE_NAME,
|
||||
|
||||
@@ -46,10 +48,9 @@ enum class JvmBuilderFlags {
|
||||
|
||||
WARN,
|
||||
|
||||
FRIEND_PATHS,
|
||||
KOTLIN_OUTPUT_JDEPS,
|
||||
FRIENDS,
|
||||
JDEPS_OUT,
|
||||
TRACE,
|
||||
ABI_JAR,
|
||||
STRICT_KOTLIN_DEPS,
|
||||
REDUCED_CLASSPATH_MODE,
|
||||
|
||||
@@ -68,8 +69,8 @@ fun configureCommonCompilerArgs(kotlinArgs: K2JVMCompilerArguments, args: ArgMap
|
||||
|
||||
// kotlin bug - not compatible with a new X compiler-plugin syntax
|
||||
//compilationArgs.disableDefaultScriptingPlugin = true
|
||||
args.optional(JvmBuilderFlags.FRIEND_PATHS)?.let { value ->
|
||||
kotlinArgs.friendPaths = value.map { workingDir.resolve(it).toString() }.toTypedArray()
|
||||
args.optional(JvmBuilderFlags.FRIENDS)?.let { value ->
|
||||
kotlinArgs.friendPaths = value.map { workingDir.resolve(it).normalize().toString() }.toTypedArray()
|
||||
}
|
||||
|
||||
args.optional(JvmBuilderFlags.OPT_IN)?.let {
|
||||
|
||||
@@ -4,24 +4,24 @@ load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
|
||||
|
||||
kt_jvm_library(
|
||||
name = "worker-lib",
|
||||
srcs = glob(["**/*.kt"]),
|
||||
srcs = glob(["*.kt", "impl/*.kt", "state/*.kt"]),
|
||||
kotlinc_opts = "//:rules_jvm_bootstrap_kotlinc_options",
|
||||
deps = [
|
||||
"@lib//:kotlin-stdlib",
|
||||
":jps-standalone",
|
||||
"@lib//:fastutil-min",
|
||||
"//src/compiler-util",
|
||||
"//src/worker-framework",
|
||||
"//zip:build-zip",
|
||||
"@rules_java//java/runfiles",
|
||||
":jps-standalone",
|
||||
"//:kotlin-jps-plugin",
|
||||
"//src/jps-builder/packager",
|
||||
"@lib//:hash4j",
|
||||
"@lib//:mvstore",
|
||||
],
|
||||
runtime_deps = [
|
||||
# jps needs these libs
|
||||
"@lib//:asm",
|
||||
"@lib//:aalto-xml",
|
||||
"@lib//:mvstore",
|
||||
"@lib//:hash4j",
|
||||
"@lib//:caffeine",
|
||||
"@lib//:platform-jps-build-qdox-java-parser",
|
||||
"@lib//:jps-javac-extension",
|
||||
@@ -37,23 +37,31 @@ kt_jvm_library(
|
||||
|
||||
java_import(
|
||||
name = "jps-standalone",
|
||||
jars = ["jps-build-standalone-2.jar"],
|
||||
jars = ["jps-build-standalone.jar"],
|
||||
visibility = ["//src/jps-builder:__subpackages__"],
|
||||
)
|
||||
|
||||
java_binary(
|
||||
name = "worker-jvm",
|
||||
runtime_deps = [":worker-lib"],
|
||||
data = [
|
||||
"@kotlinc//:kotlinc_dist",
|
||||
"@kotlin-serialization-compiler-plugin//file",
|
||||
],
|
||||
main_class = "org.jetbrains.bazel.jvm.jps.JpsBuildWorker",
|
||||
jvm_flags = [
|
||||
"-Xms1000m",
|
||||
"-Xmx6000m",
|
||||
"--add-opens=java.base/java.util.concurrent=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util.concurrent.locks=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.nio=ALL-UNNAMED",
|
||||
"-Xms1024m",
|
||||
"-Xmx6144m",
|
||||
"-Djava.awt.headless=true",
|
||||
"-Dapple.awt.UIElement=true",
|
||||
"-Dkotlin.environment.keepalive=true",
|
||||
"-Djps.use.experimental.storage=true",
|
||||
# Kotlin Compiler sets it
|
||||
"-Didea.io.use.nio2=true",
|
||||
"-Dzip.handler.uses.crc.instead.of.timestamp=true",
|
||||
"-Djps.kotlin.home=$(rlocationpath @kotlinc//:kotlinc_dist)",
|
||||
"-Dorg.jetbrains.kotlin.kotlin-serialization-compiler-plugin.path=$(rlocationpath @kotlin-serialization-compiler-plugin//file)",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -67,9 +75,12 @@ java_binary(
|
||||
],
|
||||
main_class = "org.jetbrains.bazel.jvm.jps.TestJpsBuildWorker",
|
||||
jvm_flags = [
|
||||
"-Xms1000m",
|
||||
"-Xmx6000m",
|
||||
"-Djps.service.manager.impl=org.jetbrains.bazel.jvm.jps.BazelJpsServiceManager",
|
||||
"--add-opens=java.base/java.util.concurrent=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util.concurrent.locks=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.nio=ALL-UNNAMED",
|
||||
"-Xms1024m",
|
||||
"-Xmx6144m",
|
||||
"-Djava.awt.headless=true",
|
||||
"-Dapple.awt.UIElement=true",
|
||||
"-Dkotlin.environment.keepalive=true",
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("UnstableApiUsage", "ReplaceJavaStaticMethodWithKotlinAnalog", "ReplaceGetOrSet")
|
||||
|
||||
package org.jetbrains.bazel.jvm.jps
|
||||
|
||||
import com.dynatrace.hash4j.hashing.HashFunnel
|
||||
import com.dynatrace.hash4j.hashing.HashStream64
|
||||
import com.dynatrace.hash4j.hashing.Hashing
|
||||
import com.google.devtools.build.runfiles.Runfiles
|
||||
import org.jetbrains.annotations.Unmodifiable
|
||||
import org.jetbrains.bazel.jvm.jps.state.TargetConfigurationDigestContainer
|
||||
import org.jetbrains.bazel.jvm.jps.state.TargetConfigurationDigestProperty
|
||||
import org.jetbrains.bazel.jvm.kotlin.ArgMap
|
||||
import org.jetbrains.bazel.jvm.kotlin.JvmBuilderFlags
|
||||
import org.jetbrains.bazel.jvm.kotlin.configureCommonCompilerArgs
|
||||
import org.jetbrains.jps.model.JpsDummyElement
|
||||
import org.jetbrains.jps.model.JpsElementFactory
|
||||
import org.jetbrains.jps.model.JpsModel
|
||||
import org.jetbrains.jps.model.JpsProject
|
||||
import org.jetbrains.jps.model.*
|
||||
import org.jetbrains.jps.model.ex.JpsNamedCompositeElementBase
|
||||
import org.jetbrains.jps.model.impl.JpsElementCollectionImpl
|
||||
import org.jetbrains.jps.model.java.*
|
||||
import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerOptions
|
||||
import org.jetbrains.jps.model.library.JpsOrderRootType
|
||||
import org.jetbrains.jps.model.library.impl.JpsLibraryImpl
|
||||
import org.jetbrains.jps.model.library.*
|
||||
import org.jetbrains.jps.model.library.JpsLibraryRoot.InclusionOptions
|
||||
import org.jetbrains.jps.model.library.impl.JpsLibraryReferenceImpl
|
||||
import org.jetbrains.jps.model.module.JpsDependenciesList
|
||||
import org.jetbrains.jps.model.module.impl.JpsModuleImpl
|
||||
import org.jetbrains.jps.model.module.impl.JpsModuleSourceRootImpl
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
|
||||
import org.jetbrains.kotlin.config.KotlinFacetSettings
|
||||
import org.jetbrains.kotlin.jps.model.JpsKotlinFacetModuleExtension
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
@@ -31,21 +39,23 @@ internal val runFiles by lazy {
|
||||
Runfiles.preload().unmapped()
|
||||
}
|
||||
|
||||
private fun createClasspath(args: ArgMap<JvmBuilderFlags>, baseDir: Path): Sequence<Path> {
|
||||
// REDUCED_CLASSPATH_MODE is not supported for JPS
|
||||
return args.mandatory(JvmBuilderFlags.CLASSPATH).asSequence().map { baseDir.resolve(it).normalize() }
|
||||
}
|
||||
|
||||
private val javaHome = Path.of(System.getProperty("java.home")).normalize() ?: error("No java.home system property")
|
||||
|
||||
internal fun loadJpsModel(
|
||||
sources: List<Path>,
|
||||
args: ArgMap<JvmBuilderFlags>,
|
||||
classPathRootDir: Path,
|
||||
classOutDir: Path
|
||||
): JpsModel {
|
||||
classOutDir: Path,
|
||||
dependencyFileToDigest: Map<Path, ByteArray>,
|
||||
): Pair<JpsModel, TargetConfigurationDigestContainer> {
|
||||
val model = jpsElementFactory.createModel()
|
||||
|
||||
val digests = TargetConfigurationDigestContainer()
|
||||
|
||||
val configHash = Hashing.xxh3_64().hashStream()
|
||||
// version
|
||||
configHash.putInt(1)
|
||||
|
||||
// properties not needed for us (not implemented for java)
|
||||
// extension.loadModuleOptions not needed for us (not implemented for java)
|
||||
val module = JpsModuleImpl(
|
||||
@@ -57,7 +67,10 @@ internal fun loadJpsModel(
|
||||
jpsJavaModuleExtension.outputUrl = classOutDir.toUri().toString()
|
||||
|
||||
val languageLevelEnumName = "JDK_" + args.mandatorySingle(JvmBuilderFlags.JVM_TARGET).let { if (it == "8") "1_8" else it }
|
||||
jpsJavaModuleExtension.languageLevel = LanguageLevel.valueOf(languageLevelEnumName)
|
||||
val langLevel = LanguageLevel.valueOf(languageLevelEnumName)
|
||||
jpsJavaModuleExtension.languageLevel = langLevel
|
||||
|
||||
configHash.putInt(langLevel.ordinal)
|
||||
|
||||
for (source in sources) {
|
||||
// used as a key - immutable instance cannot be used
|
||||
@@ -65,20 +78,30 @@ internal fun loadJpsModel(
|
||||
module.addSourceRoot(JpsModuleSourceRootImpl(source.toUri().toString(), JavaSourceRootType.SOURCE, properties))
|
||||
}
|
||||
|
||||
configureKotlinCompiler(module = module, args = args, classPathRootDir = classPathRootDir)
|
||||
configureKotlinCompiler(module = module, args = args, classPathRootDir = classPathRootDir, configHash = configHash)
|
||||
|
||||
val dependencyList = module.dependenciesList
|
||||
dependencyList.clear()
|
||||
configureJdk(model = model, module = module, dependencyList = dependencyList)
|
||||
dependencyList.addModuleSourceDependency()
|
||||
|
||||
configureClasspath(module = module, dependencyList = dependencyList, args = args, baseDir = classPathRootDir)
|
||||
configureClasspath(
|
||||
module = module,
|
||||
dependencyList = dependencyList,
|
||||
args = args,
|
||||
baseDir = classPathRootDir,
|
||||
dependencyFileToDigest = dependencyFileToDigest,
|
||||
digests = digests,
|
||||
)
|
||||
|
||||
val project = model.project
|
||||
project.addModule(module)
|
||||
|
||||
configHash.putUnorderedIterable(args.optionalList(JvmBuilderFlags.ADD_EXPORT), HashFunnel.forString(), Hashing.xxh3_64())
|
||||
configureJavac(project, args)
|
||||
return model
|
||||
digests.set(TargetConfigurationDigestProperty.COMPILER, configHash.asLong)
|
||||
|
||||
return model to digests
|
||||
}
|
||||
|
||||
private fun configureJavac(project: JpsProject, args: ArgMap<JvmBuilderFlags>) {
|
||||
@@ -94,14 +117,28 @@ private fun configureJavac(project: JpsProject, args: ArgMap<JvmBuilderFlags>) {
|
||||
configuration.setCompilerOptions("Javac", compilerOptions)
|
||||
}
|
||||
|
||||
private fun configureKotlinCompiler(module: JpsModuleImpl<JpsDummyElement>, args: ArgMap<JvmBuilderFlags>, classPathRootDir: Path) {
|
||||
private fun configureKotlinCompiler(
|
||||
module: JpsModuleImpl<JpsDummyElement>,
|
||||
args: ArgMap<JvmBuilderFlags>,
|
||||
classPathRootDir: Path,
|
||||
configHash: HashStream64,
|
||||
) {
|
||||
val kotlinFacetSettings = KotlinFacetSettings()
|
||||
kotlinFacetSettings.useProjectSettings = false
|
||||
val kotlinArgs = K2JVMCompilerArguments()
|
||||
kotlinFacetSettings.compilerArguments = kotlinArgs
|
||||
configureCommonCompilerArgs(kotlinArgs = kotlinArgs, args = args, workingDir = classPathRootDir)
|
||||
|
||||
configHash.putString(kotlinArgs.apiVersion ?: "")
|
||||
configHash.putString(kotlinArgs.languageVersion ?: "")
|
||||
configHash.putString(kotlinArgs.jvmTarget ?: "")
|
||||
configHash.putString(kotlinArgs.lambdas ?: "")
|
||||
configHash.putString(kotlinArgs.jvmDefault)
|
||||
configHash.putBoolean(kotlinArgs.inlineClasses)
|
||||
configHash.putBoolean(kotlinArgs.allowKotlinPackage)
|
||||
|
||||
val plugins = args.optionalList(JvmBuilderFlags.PLUGIN_ID).zip(args.optionalList(JvmBuilderFlags.PLUGIN_CLASSPATH))
|
||||
configHash.putInt(plugins.size)
|
||||
if (plugins.isNotEmpty()) {
|
||||
val pluginClassPaths = mutableListOf<String>()
|
||||
@Suppress("UnusedVariable")
|
||||
@@ -112,6 +149,7 @@ private fun configureKotlinCompiler(module: JpsModuleImpl<JpsDummyElement>, args
|
||||
throw IllegalArgumentException("Missing system property $propertyName")
|
||||
}
|
||||
|
||||
configHash.putString(id)
|
||||
pluginClassPaths.add(runFiles.rlocation(relativePath))
|
||||
}
|
||||
kotlinArgs.pluginClasspaths = pluginClassPaths.toTypedArray()
|
||||
@@ -143,17 +181,34 @@ private fun configureClasspath(
|
||||
dependencyList: JpsDependenciesList,
|
||||
args: ArgMap<JvmBuilderFlags>,
|
||||
baseDir: Path,
|
||||
dependencyFileToDigest: Map<Path, ByteArray>,
|
||||
digests: TargetConfigurationDigestContainer,
|
||||
) {
|
||||
val lib = JpsLibraryImpl(
|
||||
"class-path-lib",
|
||||
JpsJavaLibraryType.INSTANCE,
|
||||
jpsElementFactory.createDummyElement()
|
||||
)
|
||||
for (file in createClasspath(args, baseDir)) {
|
||||
lib.addRoot(file.toUri().toString(), JpsOrderRootType.COMPILED)
|
||||
// REDUCED_CLASSPATH_MODE is not supported for JPS
|
||||
val classPathRaw = args.mandatory(JvmBuilderFlags.CLASSPATH)
|
||||
val files = Array<Path>(classPathRaw.size) {
|
||||
baseDir.resolve(classPathRaw[it]).normalize()
|
||||
}
|
||||
val lib = BazelJpsLibrary("class-path-lib", files.asList())
|
||||
module.addModuleLibrary(lib)
|
||||
dependencyList.addLibraryDependency(lib)
|
||||
|
||||
val hash = Hashing.xxh3_64().hashStream()
|
||||
hash.putOrderedIterable(classPathRaw, HashFunnel.forString())
|
||||
digests.set(TargetConfigurationDigestProperty.DEPENDENCY_PATH_LIST, hash.asLong)
|
||||
hash.reset()
|
||||
|
||||
// todo JPS should support dependency as JARs, but for now we do include digest into hash
|
||||
for (file in files) {
|
||||
val digest = requireNotNull(dependencyFileToDigest.get(file)) {
|
||||
"Missing digest for $file.\nAvailable digests: ${dependencyFileToDigest.keys.joinToString(separator = ",\n") { it.invariantSeparatorsPathString }}"
|
||||
}
|
||||
hash.putBytes(digest)
|
||||
}
|
||||
hash.putInt(files.size)
|
||||
|
||||
digests.set(TargetConfigurationDigestProperty.DEPENDENCY_DIGEST_LIST, hash.asLong)
|
||||
hash.reset()
|
||||
}
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
@@ -165,3 +220,79 @@ private fun readModulesFromJdkReleaseFile(javaHome: Path): Sequence<String> {
|
||||
val modules = p.getProperty("MODULES") ?: return emptySequence()
|
||||
return modules.removeSurrounding("\"").removeSurrounding("'").splitToSequence(' ').map { jbrBaseUrl + it }
|
||||
}
|
||||
|
||||
private class BazelJpsLibrary(
|
||||
name: String,
|
||||
private val files: List<Path>,
|
||||
) : JpsNamedCompositeElementBase<BazelJpsLibrary>(name), JpsTypedLibrary<JpsDummyElement> {
|
||||
private val properties = JpsElementFactory.getInstance().createDummyElement()
|
||||
//private val roots = files.map {
|
||||
// JpsLibraryRootImpl("jar://" + it.invariantSeparatorsPathString + "!/", JpsOrderRootType.COMPILED, InclusionOptions.ROOT_ITSELF)
|
||||
//}
|
||||
|
||||
override fun getType(): JpsJavaLibraryType = JpsJavaLibraryType.INSTANCE
|
||||
|
||||
override fun <T : JpsElement> asTyped(type: JpsLibraryType<T>): JpsTypedLibrary<T>? {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return if (getType() == type) this as JpsTypedLibrary<T> else null
|
||||
}
|
||||
|
||||
override fun getProperties(): JpsDummyElement = properties
|
||||
|
||||
override fun getRoots(rootType: JpsOrderRootType): List<JpsLibraryRoot> {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun addRoot(url: String, rootType: JpsOrderRootType) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun addRoot(file: File, rootType: JpsOrderRootType) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun addRoot(url: String, rootType: JpsOrderRootType, options: InclusionOptions) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun removeUrl(url: String, rootType: JpsOrderRootType) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun delete() {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun getParent(): JpsElementCollectionImpl<JpsLibrary>? {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return myParent as JpsElementCollectionImpl<JpsLibrary>?
|
||||
}
|
||||
|
||||
@Suppress("OVERRIDE_DEPRECATION", "removal")
|
||||
override fun createCopy(): BazelJpsLibrary {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun createReference(): JpsLibraryReference {
|
||||
return JpsLibraryReferenceImpl(name, createParentReference())
|
||||
}
|
||||
|
||||
private fun createParentReference(): JpsElementReference<JpsCompositeElement> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return (parent!!.parent as JpsReferenceableElement<JpsCompositeElement>).createReference()
|
||||
}
|
||||
|
||||
override fun getFiles(rootType: JpsOrderRootType): @Unmodifiable List<File> {
|
||||
return files.map { it.toFile() }
|
||||
}
|
||||
|
||||
override fun getPaths(rootType: JpsOrderRootType): List<Path> = files
|
||||
|
||||
override fun getRootUrls(rootType: JpsOrderRootType): List<String> {
|
||||
// kotlin uses this API
|
||||
//return files.map { "jar://" + it.invariantSeparatorsPathString + "!/" }
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun toString(): String = "BazelJpsLibrary(files=$files)"
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("UnstableApiUsage", "ReplaceGetOrSet", "ReplaceJavaStaticMethodWithKotlinAnalog")
|
||||
|
||||
package org.jetbrains.bazel.jvm.jps
|
||||
@@ -15,7 +16,8 @@ import org.jetbrains.jps.incremental.java.JavaBuilder
|
||||
import org.jetbrains.jps.model.*
|
||||
import org.jetbrains.jps.service.JpsServiceManager
|
||||
import org.jetbrains.jps.service.SharedThreadPool
|
||||
import org.jetbrains.kotlin.jps.build.KotlinBuilderService
|
||||
import org.jetbrains.kotlin.jps.build.KotlinBuilder
|
||||
import org.jetbrains.kotlin.jps.incremental.KotlinCompilerReferenceIndexBuilder
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
@@ -31,7 +33,7 @@ internal class BazelJpsServiceManager : JpsServiceManager() {
|
||||
|
||||
init {
|
||||
extensions.put(JavaCompilingTool::class.java, listOf(JavacCompilerTool()))
|
||||
extensions.put(BuilderService::class.java, listOf(BazelJavaBuilderService, KotlinBuilderService()))
|
||||
extensions.put(BuilderService::class.java, listOf(BazelJavaBuilderService))
|
||||
// org.jetbrains.kotlin.jps.build.KotlinResourcesRootProvider and KotlinSourceRootProvider are not needed
|
||||
extensions.put(AdditionalRootsProviderService::class.java, listOf())
|
||||
extensions.put(ExcludedJavaSourceRootProvider::class.java, listOf())
|
||||
@@ -120,7 +122,9 @@ private object BazelJavaBuilderService : BuilderService() {
|
||||
return listOf(
|
||||
JavaBuilder(SharedThreadPool.getInstance()),
|
||||
//NotNullInstrumentingBuilder(),
|
||||
JavaBackwardReferenceIndexBuilder()
|
||||
JavaBackwardReferenceIndexBuilder(),
|
||||
KotlinBuilder(),
|
||||
KotlinCompilerReferenceIndexBuilder(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
package org.jetbrains.bazel.jvm.jps
|
||||
@@ -7,16 +8,15 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asExecutor
|
||||
import org.jetbrains.jps.service.SharedThreadPool
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
object BazelSharedThreadPool : SharedThreadPool() {
|
||||
internal object BazelSharedThreadPool : SharedThreadPool() {
|
||||
private val executor = Dispatchers.Default.asExecutor()
|
||||
|
||||
// JPS uses MAX_BUILDER_THREADS as maxThreads - doesn't make sense for standalone, use coroutine dispatcher pool
|
||||
override fun createBoundedExecutor(name: String, maxThreads: Int) = this
|
||||
|
||||
override fun createCustomPriorityQueueBoundedExecutor(name: String, maxThreads: Int, comparator: Comparator<in Runnable>): Executor {
|
||||
return PriorityQueueExecutor(this, comparator)
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun execute(command: Runnable) {
|
||||
@@ -68,40 +68,4 @@ object BazelSharedThreadPool : SharedThreadPool() {
|
||||
override fun <T> invokeAny(tasks: Collection<Callable<T>>, timeout: Long, unit: TimeUnit): T? {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
||||
private class PriorityQueueExecutor(
|
||||
private val delegateExecutor: Executor,
|
||||
comparator: Comparator<in Runnable>
|
||||
) : Executor {
|
||||
private val taskQueue = PriorityBlockingQueue<Runnable>(11, comparator)
|
||||
private val isProcessing = AtomicBoolean(false)
|
||||
|
||||
override fun execute(command: Runnable) {
|
||||
taskQueue.put(command)
|
||||
processQueue()
|
||||
}
|
||||
|
||||
private fun processQueue() {
|
||||
if (!isProcessing.compareAndSet(false, true)) {
|
||||
return
|
||||
}
|
||||
|
||||
delegateExecutor.execute {
|
||||
try {
|
||||
while (true) {
|
||||
val task = taskQueue.poll() ?: break
|
||||
task.run()
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (taskQueue.isEmpty()) {
|
||||
isProcessing.set(false)
|
||||
}
|
||||
else {
|
||||
processQueue()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,42 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("HardCodedStringLiteral", "UnstableApiUsage", "ReplaceJavaStaticMethodWithKotlinAnalog")
|
||||
|
||||
package org.jetbrains.bazel.jvm.jps
|
||||
|
||||
import com.google.devtools.build.lib.worker.WorkerProtocol
|
||||
import com.intellij.openapi.util.io.FileUtilRt
|
||||
import com.intellij.openapi.diagnostic.IdeaLogRecordFormatter
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.tracing.Tracer
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.bazel.jvm.WorkRequest
|
||||
import org.jetbrains.bazel.jvm.WorkRequestExecutor
|
||||
import org.jetbrains.bazel.jvm.jps.impl.BazelModuleExcludeIndex
|
||||
import org.jetbrains.bazel.jvm.jps.impl.*
|
||||
import org.jetbrains.bazel.jvm.jps.state.TargetConfigurationDigestContainer
|
||||
import org.jetbrains.bazel.jvm.jps.state.saveTargetState
|
||||
import org.jetbrains.bazel.jvm.kotlin.ArgMap
|
||||
import org.jetbrains.bazel.jvm.kotlin.JvmBuilderFlags
|
||||
import org.jetbrains.bazel.jvm.kotlin.parseArgs
|
||||
import org.jetbrains.bazel.jvm.processRequests
|
||||
import org.jetbrains.jps.api.CanceledStatus
|
||||
import org.jetbrains.jps.api.GlobalOptions
|
||||
import org.jetbrains.jps.backwardRefs.JavaBackwardReferenceIndexWriter
|
||||
import org.jetbrains.jps.builders.impl.BuildDataPathsImpl
|
||||
import org.jetbrains.jps.builders.impl.BuildRootIndexImpl
|
||||
import org.jetbrains.jps.builders.impl.BuildTargetIndexImpl
|
||||
import org.jetbrains.jps.builders.impl.BuildTargetRegistryImpl
|
||||
import org.jetbrains.jps.builders.java.JavaModuleBuildTargetType
|
||||
import org.jetbrains.jps.builders.logging.BuildLoggingManager
|
||||
import org.jetbrains.jps.cmdline.ProjectDescriptor
|
||||
import org.jetbrains.jps.incremental.BuilderRegistry
|
||||
import org.jetbrains.jps.incremental.CompileScopeImpl
|
||||
import org.jetbrains.jps.incremental.MessageHandler
|
||||
import org.jetbrains.jps.incremental.RebuildRequestedException
|
||||
import org.jetbrains.jps.incremental.fs.BuildFSState
|
||||
import org.jetbrains.jps.incremental.messages.BuildMessage
|
||||
import org.jetbrains.jps.incremental.messages.CompilerMessage
|
||||
import org.jetbrains.jps.incremental.*
|
||||
import org.jetbrains.jps.incremental.relativizer.PathRelativizer
|
||||
import org.jetbrains.jps.incremental.relativizer.PathRelativizerService
|
||||
import org.jetbrains.jps.incremental.storage.BuildDataManager
|
||||
import org.jetbrains.jps.incremental.storage.BuildTargetsState
|
||||
import org.jetbrains.jps.incremental.storage.ExperimentalSourceToOutputMapping
|
||||
import org.jetbrains.jps.incremental.storage.PathTypeAwareRelativizer
|
||||
import org.jetbrains.jps.incremental.storage.RelativePathType
|
||||
import org.jetbrains.jps.incremental.storage.StorageManager
|
||||
import org.jetbrains.jps.indices.impl.IgnoredFileIndexImpl
|
||||
import org.jetbrains.jps.model.JpsModel
|
||||
import org.jetbrains.kotlin.config.IncrementalCompilation
|
||||
import java.io.File
|
||||
import java.io.Writer
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.Map
|
||||
import java.util.Set
|
||||
import kotlin.coroutines.coroutineContext
|
||||
import kotlin.io.path.invariantSeparatorsPathString
|
||||
|
||||
// Please note: for performance reasons, we do not set `jps.new.storage.compact.on.close` to true.
|
||||
// As a result, the database file on disk may grow to some extent.
|
||||
@@ -52,6 +51,9 @@ private fun configureKotlincHome() {
|
||||
}
|
||||
|
||||
internal fun configureGlobalJps() {
|
||||
Logger.setFactory { BazelLogger(IdeaLogRecordFormatter.smartAbbreviate(it)) }
|
||||
System.setProperty("jps.service.manager.impl", BazelJpsServiceManager::class.java.name)
|
||||
System.setProperty("jps.backward.ref.index.builder.fs.case.sensitive", "true")
|
||||
System.setProperty(GlobalOptions.COMPILE_PARALLEL_MAX_THREADS_OPTION, Runtime.getRuntime().availableProcessors().toString())
|
||||
System.setProperty(GlobalOptions.COMPILE_PARALLEL_OPTION, "true")
|
||||
System.setProperty(GlobalOptions.DEPENDENCY_GRAPH_ENABLED, "true")
|
||||
@@ -68,195 +70,186 @@ object JpsBuildWorker : WorkRequestExecutor {
|
||||
processRequests(startupArgs, this)
|
||||
}
|
||||
|
||||
override suspend fun execute(request: WorkerProtocol.WorkRequest, writer: Writer, baseDir: Path): Int {
|
||||
val sources = request.inputsList.asSequence()
|
||||
.filter { it.path.endsWith(".kt") || it.path.endsWith(".java") }
|
||||
.map { baseDir.resolve(it.path) }
|
||||
.toList()
|
||||
return buildUsingJps(workingDir = baseDir, args = parseArgs(request.argumentsList), out = writer, sources = sources)
|
||||
override suspend fun execute(request: WorkRequest, writer: Writer, baseDir: Path): Int {
|
||||
val dependencyFileToDigest = HashMap<Path, ByteArray>()
|
||||
val sources = ArrayList<Path>()
|
||||
for (input in request.inputs) {
|
||||
if (input.path.endsWith(".kt") || input.path.endsWith(".java")) {
|
||||
sources.add(baseDir.resolve(input.path).normalize())
|
||||
}
|
||||
else if (input.path.endsWith(".jar")) {
|
||||
dependencyFileToDigest.put(baseDir.resolve(input.path).normalize(), input.digest)
|
||||
}
|
||||
}
|
||||
return buildUsingJps(
|
||||
baseDir = baseDir,
|
||||
args = parseArgs(request.arguments),
|
||||
out = writer,
|
||||
sources = sources,
|
||||
dependencyFileToDigest = dependencyFileToDigest,
|
||||
isDebugEnabled = request.verbosity > 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal suspend fun buildUsingJps(
|
||||
workingDir: Path,
|
||||
baseDir: Path,
|
||||
args: ArgMap<JvmBuilderFlags>,
|
||||
out: Writer,
|
||||
sources: List<Path>,
|
||||
classPathRootDir: Path = workingDir,
|
||||
dependencyFileToDigest: Map<Path, ByteArray>,
|
||||
isDebugEnabled: Boolean,
|
||||
): Int {
|
||||
|
||||
val messageHandler = ConsoleMessageHandler(out)
|
||||
|
||||
val outJar = workingDir.resolve(args.mandatorySingle(JvmBuilderFlags.OUTPUT)).normalize()
|
||||
val abiJar = args.optionalSingle(JvmBuilderFlags.ABI_OUT)?.let { baseDir.resolve(it).normalize() }
|
||||
val outJar = baseDir.resolve(args.mandatorySingle(JvmBuilderFlags.OUT)).normalize()
|
||||
val prefix = outJar.fileName.toString().removeSuffix(".jar")
|
||||
val bazelOutDir = outJar.parent
|
||||
val dataStorageRoot = bazelOutDir.resolve("$prefix-jps-data")
|
||||
val dataDir = bazelOutDir.resolve("$prefix-jps-data")
|
||||
|
||||
// incremental compilation - we do not clear dir
|
||||
val classOutDir = bazelOutDir.resolve("$prefix-classes")
|
||||
|
||||
val buildRunner = BuildRunner()
|
||||
val jpsModel = loadJpsModel(sources = sources, args = args, classPathRootDir = classPathRootDir, classOutDir = classOutDir)
|
||||
val (jpsModel, targetDigests) = loadJpsModel(
|
||||
sources = sources,
|
||||
args = args,
|
||||
classPathRootDir = baseDir,
|
||||
classOutDir = classOutDir,
|
||||
dependencyFileToDigest = dependencyFileToDigest,
|
||||
)
|
||||
val moduleTarget = BazelModuleBuildTarget(
|
||||
outDir = classOutDir,
|
||||
module = jpsModel.project.modules.single(),
|
||||
sources = sources,
|
||||
)
|
||||
|
||||
fun createProjectDescriptor(): ProjectDescriptor? {
|
||||
return buildRunner.load(
|
||||
messageHandler = messageHandler,
|
||||
dataStorageRoot = dataStorageRoot,
|
||||
fsState = BuildFSState(/* alwaysScanFS = */ true),
|
||||
jpsModel = jpsModel,
|
||||
)
|
||||
}
|
||||
val relativizer = createPathRelativizer(baseDir = baseDir, classOutDir = classOutDir)
|
||||
|
||||
fun clearStorage() {
|
||||
// todo rename and store
|
||||
FileUtilRt.deleteRecursively(dataStorageRoot)
|
||||
FileUtilRt.deleteRecursively(classOutDir)
|
||||
}
|
||||
val compileScope = CompileScopeImpl(
|
||||
/* types = */ Set.of(JavaModuleBuildTargetType.PRODUCTION),
|
||||
/* typesToForceBuild = */ Set.of(),
|
||||
/* targets = */ Set.of(),
|
||||
/* files = */ java.util.Map.of()
|
||||
)
|
||||
|
||||
suspend fun initAndBuild(isRebuild: Boolean): Boolean {
|
||||
val storageInitializer = StorageInitializer(dataDir = dataDir, classOutDir = classOutDir)
|
||||
val storageManager = if (isRebuild) {
|
||||
storageInitializer.clearAndInit(messageHandler)
|
||||
}
|
||||
else {
|
||||
storageInitializer.init(messageHandler, targetDigests)
|
||||
}
|
||||
|
||||
try {
|
||||
var projectDescriptor: ProjectDescriptor? = null
|
||||
try {
|
||||
projectDescriptor = createProjectDescriptor()
|
||||
var checkRebuildRequired = true
|
||||
if (projectDescriptor == null) {
|
||||
clearStorage()
|
||||
checkRebuildRequired = false
|
||||
val projectDescriptor = storageInitializer.createProjectDescriptor(messageHandler, jpsModel, moduleTarget, relativizer)
|
||||
try {
|
||||
if (storageInitializer.isCheckRebuildRequired) {
|
||||
val rebuildRequiredSpan = Tracer.start("JpsProjectBuilder.checkRebuildRequired")
|
||||
val isRebuildRequired = checkRebuildRequired(
|
||||
scope = compileScope,
|
||||
projectDescriptor = projectDescriptor,
|
||||
moduleTarget = moduleTarget,
|
||||
isDebugEnabled = isDebugEnabled,
|
||||
messageHandler = messageHandler,
|
||||
)
|
||||
rebuildRequiredSpan.complete()
|
||||
|
||||
projectDescriptor = requireNotNull(createProjectDescriptor()) {
|
||||
"The storage has been corrupted twice in a row, resulting in an unrecoverable error"
|
||||
if (isRebuildRequired) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doBuild(checkRebuildRequired = checkRebuildRequired, projectDescriptor = projectDescriptor, messageHandler = messageHandler)
|
||||
val coroutineContext = coroutineContext
|
||||
val context = CompileContextImpl(
|
||||
compileScope,
|
||||
projectDescriptor,
|
||||
messageHandler,
|
||||
emptyMap(),
|
||||
object : CanceledStatus {
|
||||
override fun isCanceled(): Boolean = !coroutineContext.isActive
|
||||
},
|
||||
)
|
||||
JpsProjectBuilder(
|
||||
builderRegistry = BuilderRegistry.getInstance(),
|
||||
messageHandler = messageHandler,
|
||||
isCleanBuild = storageInitializer.isCleanBuild,
|
||||
).build(context)
|
||||
postBuild(
|
||||
messageHandler = messageHandler,
|
||||
moduleTarget = moduleTarget,
|
||||
outJar = outJar,
|
||||
abiJar = abiJar,
|
||||
classOutDir = classOutDir,
|
||||
context = context,
|
||||
targetDigests = targetDigests,
|
||||
storageManager = storageManager,
|
||||
)
|
||||
return true
|
||||
}
|
||||
finally {
|
||||
projectDescriptor.release()
|
||||
}
|
||||
}
|
||||
catch (_: RebuildRequestedException) {
|
||||
return false
|
||||
}
|
||||
finally {
|
||||
projectDescriptor?.release()
|
||||
storageManager.forceClose()
|
||||
}
|
||||
}
|
||||
catch (_: RebuildRequestedException) {
|
||||
clearStorage()
|
||||
|
||||
val projectDescriptor = requireNotNull(createProjectDescriptor()) {
|
||||
"Unrecoverable error"
|
||||
}
|
||||
|
||||
try {
|
||||
doBuild(checkRebuildRequired = false, projectDescriptor = projectDescriptor, messageHandler = messageHandler)
|
||||
}
|
||||
finally {
|
||||
projectDescriptor.release()
|
||||
}
|
||||
// if class output dir doesn't exist, make sure that we do not to use existing cache - pass `isRebuild` as true in this case
|
||||
if (!initAndBuild(isRebuild = Files.notExists(classOutDir))) {
|
||||
initAndBuild(isRebuild = true)
|
||||
}
|
||||
|
||||
return if (messageHandler.hasErrors()) 1 else 0
|
||||
}
|
||||
|
||||
private suspend fun doBuild(
|
||||
checkRebuildRequired: Boolean,
|
||||
projectDescriptor: ProjectDescriptor,
|
||||
messageHandler: MessageHandler,
|
||||
|
||||
private suspend fun postBuild(
|
||||
messageHandler: ConsoleMessageHandler,
|
||||
moduleTarget: ModuleBuildTarget,
|
||||
outJar: Path,
|
||||
abiJar: Path?,
|
||||
classOutDir: Path,
|
||||
context: CompileContextImpl,
|
||||
targetDigests: TargetConfigurationDigestContainer,
|
||||
storageManager: StorageManager,
|
||||
) {
|
||||
val compileScope = CompileScopeImpl(
|
||||
/* types = */ Set.of(JavaModuleBuildTargetType.PRODUCTION),
|
||||
/* typesToForceBuild = */ Set.of(),
|
||||
/* targets = */ Set.of(),
|
||||
/* files = */ Map.of()
|
||||
)
|
||||
|
||||
val builder = JpsProjectBuilder(
|
||||
projectDescriptor = projectDescriptor,
|
||||
builderRegistry = BuilderRegistry.getInstance(),
|
||||
builderParams = emptyMap(),
|
||||
isTestMode = false,
|
||||
messageHandler = messageHandler,
|
||||
)
|
||||
if (checkRebuildRequired) {
|
||||
builder.checkRebuildRequired(compileScope)
|
||||
}
|
||||
builder.build(compileScope)
|
||||
}
|
||||
|
||||
private class ConsoleMessageHandler(private val out: Writer) : MessageHandler {
|
||||
private var hasErrors = false
|
||||
|
||||
override fun processMessage(message: BuildMessage) {
|
||||
val messageText = when (message) {
|
||||
is CompilerMessage -> {
|
||||
when {
|
||||
message.sourcePath == null -> message.messageText
|
||||
message.line < 0 -> message.sourcePath + ": " + message.messageText
|
||||
else -> message.sourcePath + "(" + message.line + ":" + message.column + "): " + message.messageText
|
||||
}
|
||||
}
|
||||
|
||||
else -> message.messageText
|
||||
}
|
||||
|
||||
if (messageText.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (message.kind == BuildMessage.Kind.ERROR) {
|
||||
out.appendLine("Error: $messageText")
|
||||
hasErrors = true
|
||||
}
|
||||
else if (message.kind !== BuildMessage.Kind.PROGRESS || !messageText.startsWith("Compiled") && !messageText.startsWith("Copying")) {
|
||||
out.appendLine(messageText)
|
||||
}
|
||||
}
|
||||
|
||||
fun hasErrors(): Boolean = hasErrors
|
||||
}
|
||||
|
||||
private fun createStorageManager(dataStorageRoot: Path): StorageManager {
|
||||
val manager = StorageManager(dataStorageRoot.resolve("jps-portable-cache.db"))
|
||||
manager.open()
|
||||
return manager
|
||||
}
|
||||
|
||||
private class BuildRunner() {
|
||||
fun load(messageHandler: MessageHandler, dataStorageRoot: Path, fsState: BuildFSState, jpsModel: JpsModel): ProjectDescriptor? {
|
||||
val dataPaths = BuildDataPathsImpl(dataStorageRoot.toFile())
|
||||
val targetRegistry = BuildTargetRegistryImpl(jpsModel)
|
||||
val moduleExcludeIndex = BazelModuleExcludeIndex
|
||||
val ignoredFileIndex = IgnoredFileIndexImpl(jpsModel)
|
||||
val buildRootIndex = BuildRootIndexImpl(targetRegistry, jpsModel, moduleExcludeIndex, dataPaths, ignoredFileIndex)
|
||||
val targetIndex = BuildTargetIndexImpl(targetRegistry, buildRootIndex)
|
||||
|
||||
val relativizer = PathRelativizerService(jpsModel.project, JavaBackwardReferenceIndexWriter.isCompilerReferenceFSCaseSensitive())
|
||||
|
||||
val storageManager = createStorageManager(dataStorageRoot)
|
||||
var dataManager: BuildDataManager? = null
|
||||
try {
|
||||
@Suppress("DEPRECATION")
|
||||
dataManager = BuildDataManager(
|
||||
dataPaths,
|
||||
BuildTargetsState(dataPaths, jpsModel, buildRootIndex),
|
||||
relativizer,
|
||||
storageManager,
|
||||
coroutineScope {
|
||||
val dataManager = context.projectDescriptor.dataManager
|
||||
launch(CoroutineName("save caches")) {
|
||||
// save config state only in the end
|
||||
saveTargetState(
|
||||
targetDigests = targetDigests,
|
||||
manager = context.projectDescriptor.dataManager.targetStateManager as BazelBuildTargetStateManager,
|
||||
storageManager = storageManager,
|
||||
)
|
||||
if (dataManager.versionDiffers()) {
|
||||
storageManager.forceClose()
|
||||
|
||||
messageHandler.processMessage(
|
||||
CompilerMessage("", BuildMessage.Kind.INFO, "Dependency data format has changed, project rebuild required"),
|
||||
dataManager.flush(/* memoryCachesOnly = */ false)
|
||||
}
|
||||
|
||||
launch(CoroutineName("create output JAR and ABI JAR")) {
|
||||
// pack to jar
|
||||
messageHandler.measureTime("pack and abi") {
|
||||
val sourceToOutputMap = dataManager.getSourceToOutputMap(moduleTarget) as ExperimentalSourceToOutputMapping
|
||||
packageToJar(
|
||||
outJar = outJar,
|
||||
abiJar = abiJar,
|
||||
sourceToOutputMap = sourceToOutputMap,
|
||||
classOutDir = classOutDir,
|
||||
messageHandler = messageHandler,
|
||||
)
|
||||
return null
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
messageHandler.processMessage(
|
||||
CompilerMessage("", BuildMessage.Kind.INTERNAL_BUILDER_ERROR, "Cannot open cache storage: ${e.stackTraceToString()}"),
|
||||
)
|
||||
|
||||
storageManager.forceClose()
|
||||
dataManager?.close()
|
||||
|
||||
return null
|
||||
launch(CoroutineName("report build state")) {
|
||||
dataManager.reportUnhandledRelativizerPaths()
|
||||
reportRebuiltModules(context)
|
||||
reportUnprocessedChanges(context, moduleTarget)
|
||||
}
|
||||
|
||||
return ProjectDescriptor(
|
||||
jpsModel, fsState, dataManager, BuildLoggingManager.DEFAULT, moduleExcludeIndex, targetIndex, buildRootIndex, ignoredFileIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,162 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("UnstableApiUsage", "ReplaceGetOrSet")
|
||||
|
||||
package org.jetbrains.bazel.jvm.jps
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtilRt
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import org.h2.mvstore.MVStore
|
||||
import org.jetbrains.bazel.jvm.jps.impl.BazelModuleBuildTarget
|
||||
import org.jetbrains.bazel.jvm.jps.impl.loadJpsProject
|
||||
import org.jetbrains.bazel.jvm.jps.state.TargetConfigurationDigestContainer
|
||||
import org.jetbrains.bazel.jvm.jps.state.checkConfiguration
|
||||
import org.jetbrains.jps.cmdline.ProjectDescriptor
|
||||
import org.jetbrains.jps.incremental.fs.BuildFSState
|
||||
import org.jetbrains.jps.incremental.relativizer.PathRelativizerService
|
||||
import org.jetbrains.jps.incremental.storage.StorageManager
|
||||
import org.jetbrains.jps.incremental.storage.StoreLogger
|
||||
import org.jetbrains.jps.incremental.storage.tryOpenMvStore
|
||||
import org.jetbrains.jps.model.JpsModel
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
internal class StorageInitializer(private val dataDir: Path, private val classOutDir: Path) {
|
||||
private var storageManager: StorageManager? = null
|
||||
private val cacheDbFile = dataDir.resolve("jps-portable-cache.db")
|
||||
|
||||
var isCheckRebuildRequired: Boolean = true
|
||||
private set
|
||||
|
||||
private var wasCleared = false
|
||||
|
||||
var isCleanBuild: Boolean = false
|
||||
private set
|
||||
|
||||
fun clearAndInit(messageHandler: ConsoleMessageHandler): StorageManager {
|
||||
isCheckRebuildRequired = false
|
||||
wasCleared = true
|
||||
isCleanBuild = true
|
||||
|
||||
clearStorage()
|
||||
|
||||
Files.createDirectories(dataDir)
|
||||
val logger = createLogger(messageHandler)
|
||||
val store = tryOpenMvStore(file = cacheDbFile, readOnly = false, autoCommitDelay = 0, logger = logger)
|
||||
return StorageManager(cacheDbFile, store)
|
||||
.also { storageManager = it }
|
||||
}
|
||||
|
||||
suspend fun init(messageHandler: ConsoleMessageHandler, targetDigests: TargetConfigurationDigestContainer): StorageManager {
|
||||
val logger = createLogger(messageHandler)
|
||||
coroutineContext.ensureActive()
|
||||
|
||||
isCleanBuild = Files.notExists(cacheDbFile)
|
||||
|
||||
if (isCleanBuild && Files.isDirectory(dataDir)) {
|
||||
messageHandler.info("remove $dataDir and $classOutDir because no cache db file found: $cacheDbFile")
|
||||
// if no db file, make sure that data dir is also not reused
|
||||
deleteDirs()
|
||||
}
|
||||
Files.createDirectories(dataDir)
|
||||
|
||||
var store = try {
|
||||
tryOpenMvStore(file = cacheDbFile, readOnly = false, autoCommitDelay = 0, logger = logger)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
messageHandler.info("rebuild due to internal error: ${e.stackTraceToString()}")
|
||||
clearStorage()
|
||||
|
||||
return StorageManager(cacheDbFile, createStoreAfterClear(logger))
|
||||
.also { storageManager = it }
|
||||
}
|
||||
|
||||
val reasonToRebuild = try {
|
||||
checkConfiguration(store, targetDigests)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
store.closeImmediately()
|
||||
"Cannot open cache storage: ${e.stackTraceToString()}"
|
||||
}
|
||||
|
||||
if (reasonToRebuild != null) {
|
||||
store.closeImmediately()
|
||||
|
||||
messageHandler.info("rebuild: $reasonToRebuild")
|
||||
clearStorage()
|
||||
store = createStoreAfterClear(logger)
|
||||
}
|
||||
|
||||
return StorageManager(cacheDbFile, store)
|
||||
.also { storageManager = it }
|
||||
}
|
||||
|
||||
private suspend fun createStoreAfterClear(logger: StoreLogger): MVStore {
|
||||
require(wasCleared)
|
||||
|
||||
coroutineContext.ensureActive()
|
||||
|
||||
Files.createDirectories(dataDir)
|
||||
return tryOpenMvStore(file = cacheDbFile, readOnly = false, autoCommitDelay = 0, logger = logger)
|
||||
}
|
||||
|
||||
private fun createLogger(messageHandler: ConsoleMessageHandler): StoreLogger {
|
||||
return { m: String, e: Throwable, isWarn: Boolean ->
|
||||
val message = "$m: ${e.stackTraceToString()}"
|
||||
if (isWarn) {
|
||||
messageHandler.warn(message)
|
||||
}
|
||||
else {
|
||||
messageHandler.error(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createProjectDescriptor(
|
||||
messageHandler: ConsoleMessageHandler,
|
||||
jpsModel: JpsModel,
|
||||
moduleTarget: BazelModuleBuildTarget,
|
||||
relativizer: PathRelativizerService,
|
||||
): ProjectDescriptor {
|
||||
try {
|
||||
return loadJpsProject(
|
||||
dataStorageRoot = dataDir,
|
||||
// alwaysScanFS doesn't matter, we use our own version of `BuildOperations.ensureFSStateInitialized`,
|
||||
// see `JpsProjectBuilder.ensureFsStateInitialized`
|
||||
fsState = BuildFSState(/* alwaysScanFS = */ true),
|
||||
jpsModel = jpsModel,
|
||||
moduleTarget = moduleTarget,
|
||||
relativizer = relativizer,
|
||||
storageManager = storageManager!!,
|
||||
)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
storageManager!!.forceClose()
|
||||
if (wasCleared) {
|
||||
throw e
|
||||
}
|
||||
|
||||
messageHandler.warn("Cannot open cache storage: ${e.stackTraceToString()}")
|
||||
}
|
||||
|
||||
return createProjectDescriptor(
|
||||
messageHandler = messageHandler,
|
||||
jpsModel = jpsModel,
|
||||
moduleTarget = moduleTarget,
|
||||
relativizer = relativizer,
|
||||
)
|
||||
}
|
||||
|
||||
private fun clearStorage() {
|
||||
isCleanBuild = true
|
||||
wasCleared = true
|
||||
isCheckRebuildRequired = false
|
||||
// todo rename and store
|
||||
deleteDirs()
|
||||
}
|
||||
|
||||
private fun deleteDirs() {
|
||||
FileUtilRt.deleteRecursively(dataDir)
|
||||
FileUtilRt.deleteRecursively(classOutDir)
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,12 @@ package org.jetbrains.bazel.jvm.jps
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.bazel.jvm.kotlin.JvmBuilderFlags
|
||||
import org.jetbrains.bazel.jvm.kotlin.parseArgs
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.security.MessageDigest
|
||||
import kotlin.io.path.ExperimentalPathApi
|
||||
import kotlin.io.path.invariantSeparatorsPathString
|
||||
import kotlin.io.path.walk
|
||||
@@ -18,41 +20,52 @@ object TestJpsBuildWorker {
|
||||
configureGlobalJps()
|
||||
|
||||
val userHomeDir = Path.of(System.getProperty("user.home"))
|
||||
val workingDir = userHomeDir.resolve("bazel-jps-test")
|
||||
Files.createDirectories(workingDir)
|
||||
val out = StringWriter()
|
||||
try {
|
||||
val out = PrintWriter(System.out)
|
||||
try {
|
||||
//val sources = Files.newDirectoryStream(userHomeDir.resolve("projects/idea/community/platform/util/xmlDom/src")).use { it.toList() }
|
||||
val sourceDir = userHomeDir.resolve("projects/idea/community/platform/platform-impl/src")
|
||||
val sources = sourceDir
|
||||
.walk()
|
||||
.filter {
|
||||
val p = it.toString()
|
||||
p.endsWith(".kt") || p.endsWith(".java")
|
||||
}
|
||||
.sortedBy { sourceDir.relativize(it).invariantSeparatorsPathString }
|
||||
.toList()
|
||||
runBlocking(Dispatchers.Default) {
|
||||
buildUsingJps(
|
||||
workingDir = workingDir,
|
||||
args = parseArgs(testParams.trimStart().lines()),
|
||||
out = out,
|
||||
sources = sources,
|
||||
classPathRootDir = Path.of("/private/var/tmp/_bazel_develar/1693e3b60d193556354eca5e9446027e/execroot/_main"),
|
||||
)
|
||||
@Suppress("SpellCheckingInspection")
|
||||
val baseDir = Path.of("/private/var/tmp/_bazel_develar/c002af20f6ada3e2667e9e2ceaf2ceca/execroot/_main")
|
||||
|
||||
//val sources = Files.newDirectoryStream(userHomeDir.resolve("projects/idea/community/platform/util/xmlDom/src")).use { it.toList() }
|
||||
val ideaProjectDirName = if (Runtime.getRuntime().availableProcessors() >= 20) "idea-push" else "idea-second"
|
||||
val communityDir = userHomeDir.resolve("projects/$ideaProjectDirName/community")
|
||||
val sources = communityDir.resolve("platform/platform-impl/src")
|
||||
.walk()
|
||||
.filter {
|
||||
val p = it.toString()
|
||||
p.endsWith(".kt") || p.endsWith(".java")
|
||||
}
|
||||
}
|
||||
finally {
|
||||
out.flush()
|
||||
.map { "../community+/" + communityDir.relativize(it).invariantSeparatorsPathString }
|
||||
.sorted()
|
||||
.map { baseDir.resolve(it).normalize() }
|
||||
.toList()
|
||||
|
||||
require(sources.isNotEmpty())
|
||||
|
||||
runBlocking(Dispatchers.Default) {
|
||||
val args = parseArgs(testParams.trimStart().lines().toTypedArray())
|
||||
val messageDigest = MessageDigest.getInstance("SHA-256")
|
||||
buildUsingJps(
|
||||
baseDir = baseDir,
|
||||
args = args,
|
||||
out = out,
|
||||
sources = sources,
|
||||
dependencyFileToDigest = args.optionalList(JvmBuilderFlags.CLASSPATH).associate {
|
||||
val file = baseDir.resolve(it).normalize()
|
||||
val digest = messageDigest.digest(Files.readAllBytes(file))
|
||||
messageDigest.reset()
|
||||
file to digest
|
||||
},
|
||||
isDebugEnabled = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
//workingDir.deleteRecursively()
|
||||
System.out.append(out.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private const val testParams = """
|
||||
--target_label
|
||||
@community//platform/platform-impl:ide-impl
|
||||
@@ -69,8 +82,10 @@ java.desktop/sun.awt.image=ALL-UNNAMED
|
||||
java.desktop/sun.awt.datatransfer=ALL-UNNAMED
|
||||
java.desktop/sun.swing=ALL-UNNAMED
|
||||
java.base/sun.nio.fs=ALL-UNNAMED
|
||||
--output
|
||||
--out
|
||||
bazel-out/community+/darwin_arm64-fastbuild/bin/platform/platform-impl/ide-impl.jar
|
||||
--abi-out
|
||||
bazel-out/community+/darwin_arm64-fastbuild/bin/platform/platform-impl/ide-impl.abi.jar
|
||||
--warn
|
||||
off
|
||||
--jvm-default
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
@file:Suppress("ReplaceJavaStaticMethodWithKotlinAnalog", "ReplaceGetOrSet")
|
||||
|
||||
package org.jetbrains.bazel.jvm.jps.impl
|
||||
|
||||
import com.intellij.openapi.util.io.FileFilters
|
||||
import org.jetbrains.jps.builders.BuildRootDescriptor
|
||||
import org.jetbrains.jps.builders.BuildRootIndex
|
||||
import org.jetbrains.jps.builders.BuildTarget
|
||||
import org.jetbrains.jps.builders.BuildTargetType
|
||||
import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor
|
||||
import org.jetbrains.jps.incremental.CompileContext
|
||||
import java.io.File
|
||||
import java.io.FileFilter
|
||||
import java.nio.file.Path
|
||||
|
||||
internal class BazelBuildRootIndex(target: BazelModuleBuildTarget) : BuildRootIndex {
|
||||
@JvmField val fileToDescriptors = HashMap<Path, JavaSourceRootDescriptor>()
|
||||
@JvmField val descriptors = target.computeRootDescriptors()
|
||||
|
||||
init {
|
||||
val descriptors = descriptors
|
||||
for (descriptor in descriptors) {
|
||||
val old = fileToDescriptors.put(descriptor.rootFile, descriptor)
|
||||
require(old == null) {
|
||||
"Duplicated root (old=$old, new=$descriptor)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun <R : BuildRootDescriptor> getRootDescriptors(
|
||||
root: File,
|
||||
types: Collection<BuildTargetType<out BuildTarget<R>>>?,
|
||||
context: CompileContext?,
|
||||
): List<R> {
|
||||
val descriptor = fileToDescriptors.get(root.toPath()) ?: return java.util.List.of()
|
||||
if (types == null || types.any { it == JavaSourceRootDescriptor::class.java }) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return java.util.List.of(descriptor as R)
|
||||
}
|
||||
return java.util.List.of()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <R : BuildRootDescriptor> getTargetRoots(target: BuildTarget<R>, context: CompileContext?): List<R> = descriptors as List<R>
|
||||
|
||||
override fun <R : BuildRootDescriptor?> getTempTargetRoots(target: BuildTarget<R?>, context: CompileContext): List<R> = java.util.List.of()
|
||||
|
||||
override fun <R : BuildRootDescriptor> associateTempRoot(context: CompileContext, target: BuildTarget<R>, root: R) {
|
||||
throw IllegalStateException("Should not be called")
|
||||
}
|
||||
|
||||
// in Bazel, each root it is a file, so, no parent
|
||||
override fun <R : BuildRootDescriptor?> findParentDescriptor(
|
||||
file: File,
|
||||
types: Collection<BuildTargetType<out BuildTarget<R>>>,
|
||||
context: CompileContext?,
|
||||
): R? {
|
||||
if (types.any { it == JavaSourceRootDescriptor::class.java }) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return fileToDescriptors.get(file.toPath()) as R
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun <R : BuildRootDescriptor> findAllParentDescriptors(
|
||||
file: File,
|
||||
types: Collection<BuildTargetType<out BuildTarget<R>>>?,
|
||||
context: CompileContext?,
|
||||
): Collection<R> {
|
||||
if (types == null || types.any { it == JavaSourceRootDescriptor::class.java }) {
|
||||
return findAllParentDescriptors<R>(file, context)
|
||||
}
|
||||
return java.util.List.of()
|
||||
}
|
||||
|
||||
override fun <R : BuildRootDescriptor> findAllParentDescriptors(file: File, context: CompileContext?): Collection<R> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return fileToDescriptors.get(file.toPath())?.let { java.util.List.of(it as R) } ?: java.util.List.of()
|
||||
}
|
||||
|
||||
override fun clearTempRoots(context: CompileContext): Collection<BuildRootDescriptor> = java.util.List.of()
|
||||
|
||||
override fun findJavaRootDescriptor(context: CompileContext?, file: File): JavaSourceRootDescriptor? {
|
||||
return fileToDescriptors.get(file.toPath())
|
||||
}
|
||||
|
||||
override fun getRootFilter(descriptor: BuildRootDescriptor): FileFilter = FileFilters.EVERYTHING
|
||||
|
||||
override fun isFileAccepted(file: Path, descriptor: BuildRootDescriptor) = true
|
||||
|
||||
override fun isDirectoryAccepted(dir: Path, descriptor: BuildRootDescriptor) = true
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("NonExtendableApiUsage", "UnstableApiUsage", "ReplaceJavaStaticMethodWithKotlinAnalog")
|
||||
|
||||
package org.jetbrains.bazel.jvm.jps.impl
|
||||
|
||||
import com.intellij.openapi.util.Pair
|
||||
import org.jetbrains.bazel.jvm.jps.state.TargetStateContainer
|
||||
import org.jetbrains.bazel.jvm.jps.state.TargetStateProperty
|
||||
import org.jetbrains.jps.builders.BuildTarget
|
||||
import org.jetbrains.jps.builders.BuildTargetType
|
||||
import org.jetbrains.jps.cmdline.ProjectDescriptor
|
||||
import org.jetbrains.jps.incremental.CompileContext
|
||||
import org.jetbrains.jps.incremental.storage.BuildTargetConfiguration
|
||||
import org.jetbrains.jps.incremental.storage.BuildTargetStateManager
|
||||
|
||||
internal class BazelBuildTargetStateManager(
|
||||
val state: TargetStateContainer,
|
||||
) : BuildTargetStateManager {
|
||||
override fun getBuildTargetId(target: BuildTarget<*>): Int = 1
|
||||
|
||||
override fun getTargetConfiguration(target: BuildTarget<*>): BuildTargetConfiguration {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun getStaleTargetIds(type: BuildTargetType<*>): List<Pair<String, Int>> = java.util.List.of()
|
||||
|
||||
override fun cleanStaleTarget(type: BuildTargetType<*>, targetId: String) {
|
||||
}
|
||||
|
||||
override fun getLastSuccessfulRebuildDuration(): Long {
|
||||
return state.get(TargetStateProperty.LastSuccessfulRebuildDuration)
|
||||
}
|
||||
|
||||
override fun setLastSuccessfulRebuildDuration(duration: Long) {
|
||||
state.set(TargetStateProperty.LastSuccessfulRebuildDuration, duration)
|
||||
}
|
||||
|
||||
override fun getAverageBuildTime(type: BuildTargetType<*>): Long {
|
||||
return state.get(TargetStateProperty.AverageBuildTime)
|
||||
}
|
||||
|
||||
override fun setAverageBuildTime(type: BuildTargetType<*>, time: Long) {
|
||||
state.set(TargetStateProperty.AverageBuildTime, time)
|
||||
}
|
||||
|
||||
override fun save() {
|
||||
}
|
||||
|
||||
override fun clean() {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun storeNonExistentOutputRoots(target: BuildTarget<*>, context: CompileContext) {
|
||||
}
|
||||
|
||||
override fun isTargetDirty(target: BuildTarget<*>, projectDescriptor: ProjectDescriptor): Boolean = false
|
||||
|
||||
override fun invalidate(target: BuildTarget<*>) {
|
||||
// we do not invalidate as JPS does - if target configuration is changed, we delete output before build
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("ReplaceJavaStaticMethodWithKotlinAnalog", "NonExtendableApiUsage", "RemoveRedundantQualifierName", "ReplaceGetOrSet", "UnstableApiUsage")
|
||||
|
||||
package org.jetbrains.bazel.jvm.jps.impl
|
||||
|
||||
import com.dynatrace.hash4j.hashing.HashSink
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.jps.builders.BuildTarget
|
||||
import org.jetbrains.jps.builders.BuildTargetRegistry
|
||||
import org.jetbrains.jps.builders.TargetOutputIndex
|
||||
import org.jetbrains.jps.builders.java.JavaModuleBuildTargetType
|
||||
import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor
|
||||
import org.jetbrains.jps.builders.storage.BuildDataPaths
|
||||
import org.jetbrains.jps.cmdline.ProjectDescriptor
|
||||
import org.jetbrains.jps.incremental.CompileContext
|
||||
import org.jetbrains.jps.incremental.ModuleBuildTarget
|
||||
import org.jetbrains.jps.indices.IgnoredFileIndex
|
||||
import org.jetbrains.jps.indices.ModuleExcludeIndex
|
||||
import org.jetbrains.jps.model.JpsModel
|
||||
import org.jetbrains.jps.model.module.JpsModule
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
|
||||
internal class BazelModuleBuildTarget(
|
||||
outDir: Path,
|
||||
module: JpsModule,
|
||||
private val sources: List<Path>,
|
||||
) : ModuleBuildTarget(module, JavaModuleBuildTargetType.PRODUCTION) {
|
||||
private val outDirFile = outDir.toFile()
|
||||
private val outRoots = java.util.List.of(outDirFile)
|
||||
|
||||
override fun getOutputDir(): File = outDirFile
|
||||
|
||||
override fun getOutputRoots(context: CompileContext): Collection<File> = outRoots
|
||||
|
||||
override fun isTests() = false
|
||||
|
||||
override fun computeDependencies(targetRegistry: BuildTargetRegistry, outputIndex: TargetOutputIndex): Collection<BuildTarget<*>> {
|
||||
return java.util.List.of()
|
||||
}
|
||||
|
||||
override fun computeRootDescriptors(
|
||||
model: JpsModel,
|
||||
index: ModuleExcludeIndex,
|
||||
ignoredFileIndex: IgnoredFileIndex,
|
||||
dataPaths: BuildDataPaths,
|
||||
): List<JavaSourceRootDescriptor?> {
|
||||
return computeRootDescriptors()
|
||||
}
|
||||
|
||||
fun computeRootDescriptors(): List<JavaSourceRootDescriptor> {
|
||||
val roots = ArrayList<JavaSourceRootDescriptor>(sources.size)
|
||||
for (sourceRoot in sources) {
|
||||
roots.add(JavaSourceRootDescriptor.createJavaSourceRootDescriptor(sourceRoot, this))
|
||||
}
|
||||
return roots
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
override fun computeConfigurationDigest(projectDescriptor: ProjectDescriptor, hash: HashSink) {
|
||||
// handled by StorageInitializer and TargetDigest
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
package org.jetbrains.bazel.jvm.jps.impl
|
||||
@@ -9,19 +10,18 @@ import com.intellij.tracing.Tracer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.bazel.jvm.jps.JpsProjectBuilder
|
||||
import org.jetbrains.jps.builders.BuildTarget
|
||||
import org.jetbrains.jps.builders.impl.BuildTargetChunk
|
||||
import org.jetbrains.jps.incremental.*
|
||||
import org.jetbrains.jps.incremental.CompileContext
|
||||
import org.jetbrains.jps.incremental.CompileScope
|
||||
import org.jetbrains.jps.incremental.GlobalContextKey
|
||||
import org.jetbrains.jps.incremental.Utils
|
||||
import org.jetbrains.jps.incremental.messages.BuildMessage
|
||||
import org.jetbrains.jps.incremental.messages.BuildProgress
|
||||
import java.util.*
|
||||
import java.util.concurrent.CancellationException
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
private const val FLUSH_INVOCATIONS_TO_SKIP = 10
|
||||
private val LOG = Logger.getInstance(JpsProjectBuilder::class.java)
|
||||
|
||||
internal class BuildTaskLauncher(
|
||||
@@ -29,16 +29,12 @@ internal class BuildTaskLauncher(
|
||||
private val buildProgress: BuildProgress,
|
||||
private val builder: JpsProjectBuilder,
|
||||
) {
|
||||
private val exception = AtomicReference<Throwable?>()
|
||||
private val tasks: MutableList<BuildChunkTask>
|
||||
private val flushCommand: Runnable
|
||||
|
||||
init {
|
||||
val span = Tracer.start("BuildTaskLauncher constructor")
|
||||
val projectDescriptor = context.projectDescriptor
|
||||
val targetIndex = projectDescriptor.buildTargetIndex
|
||||
flushCommand = Utils.asCountedRunnable(FLUSH_INVOCATIONS_TO_SKIP, Runnable { projectDescriptor.dataManager.flush(true) })
|
||||
|
||||
val targetIndex = context.projectDescriptor.buildTargetIndex
|
||||
val chunks = targetIndex.getSortedTargetChunks(context)
|
||||
tasks = ArrayList<BuildChunkTask>(chunks.size)
|
||||
val targetToTask = HashMap<BuildTarget<*>, BuildChunkTask>(chunks.size)
|
||||
@@ -88,17 +84,11 @@ internal class BuildTaskLauncher(
|
||||
}
|
||||
|
||||
suspend fun buildInParallel() {
|
||||
val buildSpan = Tracer.start("Parallel build")
|
||||
coroutineScope {
|
||||
queueTasks(tasks = tasks.filter { it.isReady }, isDebugLogEnabled = LOG.isDebugEnabled, coroutineScope = this)
|
||||
}
|
||||
|
||||
val throwable = exception.get()
|
||||
if (throwable is ProjectBuildException) {
|
||||
throw throwable
|
||||
}
|
||||
else if (throwable != null) {
|
||||
throw ProjectBuildException(throwable)
|
||||
}
|
||||
buildSpan.complete()
|
||||
}
|
||||
|
||||
private fun queueTasks(tasks: List<BuildChunkTask>, isDebugLogEnabled: Boolean, coroutineScope: CoroutineScope) {
|
||||
@@ -122,26 +112,17 @@ internal class BuildTaskLauncher(
|
||||
) {
|
||||
try {
|
||||
try {
|
||||
if (exception.get() == null) {
|
||||
val isAffectedSpan = Tracer.start("isAffected")
|
||||
val affected = isBuildChunkAffected(scope = context.scope, chunk = task.chunk)
|
||||
isAffectedSpan.complete()
|
||||
if (affected) {
|
||||
builder.buildTargetsChunk(context = chunkLocalContext, chunk = task.chunk, buildProgress = buildProgress)
|
||||
}
|
||||
val isAffectedSpan = Tracer.start("isAffected")
|
||||
val affected = isBuildChunkAffected(scope = context.scope, chunk = task.chunk)
|
||||
isAffectedSpan.complete()
|
||||
if (affected) {
|
||||
builder.buildTargetChunk(context = chunkLocalContext, chunk = task.chunk, buildProgress = buildProgress)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
context.projectDescriptor.dataManager.closeSourceToOutputStorages(task.chunk)
|
||||
flushCommand.run()
|
||||
}
|
||||
}
|
||||
catch (e: CancellationException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
exception.compareAndSet(null, e)
|
||||
}
|
||||
finally {
|
||||
if (isDebugLogEnabled) {
|
||||
LOG.debug("Finished compilation of ${task.chunk}")
|
||||
@@ -158,8 +139,10 @@ internal class BuildTaskLauncher(
|
||||
internal class BuildChunkTask(@JvmField val chunk: BuildTargetChunk) {
|
||||
private val notBuildDependenciesCount = AtomicInteger(0)
|
||||
private val notBuiltDependencies = HashSet<BuildChunkTask>()
|
||||
|
||||
@JvmField
|
||||
val tasksDependsOnThis = ArrayList<BuildChunkTask>()
|
||||
|
||||
@JvmField
|
||||
var index = 0
|
||||
|
||||
|
||||
@@ -0,0 +1,882 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("ReplaceJavaStaticMethodWithKotlinAnalog", "UnstableApiUsage", "ReplaceGetOrSet", "HardCodedStringLiteral")
|
||||
|
||||
package org.jetbrains.bazel.jvm.jps.impl
|
||||
|
||||
import com.intellij.openapi.util.text.Formats
|
||||
import com.intellij.tracing.Tracer
|
||||
import com.intellij.util.containers.CollectionFactory
|
||||
import com.intellij.util.containers.FileHashStrategy
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap
|
||||
import org.jetbrains.bazel.jvm.jps.ConsoleMessageHandler
|
||||
import org.jetbrains.jps.ModuleChunk
|
||||
import org.jetbrains.jps.builders.BuildRootDescriptor
|
||||
import org.jetbrains.jps.builders.BuildTarget
|
||||
import org.jetbrains.jps.builders.FileProcessor
|
||||
import org.jetbrains.jps.builders.ModuleBasedTarget
|
||||
import org.jetbrains.jps.builders.impl.BuildOutputConsumerImpl
|
||||
import org.jetbrains.jps.builders.impl.BuildTargetChunk
|
||||
import org.jetbrains.jps.builders.impl.DirtyFilesHolderBase
|
||||
import org.jetbrains.jps.builders.java.JavaBuilderUtil
|
||||
import org.jetbrains.jps.builders.java.JavaModuleBuildTargetType
|
||||
import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor
|
||||
import org.jetbrains.jps.builders.storage.BuildDataCorruptedException
|
||||
import org.jetbrains.jps.builders.storage.SourceToOutputMapping
|
||||
import org.jetbrains.jps.cmdline.ProjectDescriptor
|
||||
import org.jetbrains.jps.incremental.*
|
||||
import org.jetbrains.jps.incremental.ModuleLevelBuilder.OutputConsumer
|
||||
import org.jetbrains.jps.incremental.fs.CompilationRound
|
||||
import org.jetbrains.jps.incremental.messages.*
|
||||
import org.jetbrains.jps.incremental.storage.BuildTargetConfiguration
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.file.FileSystem
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import java.util.function.Function
|
||||
import java.util.function.Predicate
|
||||
import kotlin.concurrent.Volatile
|
||||
|
||||
private val TARGET_WITH_CLEARED_OUTPUT = GlobalContextKey.create<MutableSet<BuildTarget<*>>>("_targets_with_cleared_output_")
|
||||
|
||||
private const val CLASSPATH_INDEX_FILE_NAME = "classpath.index"
|
||||
|
||||
// CLASSPATH_INDEX_FILE_NAME cannot be used because IDEA on run creates CLASSPATH_INDEX_FILE_NAME only if some module class is loaded,
|
||||
// so, not possible to distinguish case
|
||||
// "classpath.index doesn't exist because deleted on module file change" vs. "classpath.index doesn't exist because was not created"
|
||||
private const val UNMODIFIED_MARK_FILE_NAME = ".unmodified"
|
||||
|
||||
internal class JpsProjectBuilder(
|
||||
private val builderRegistry: BuilderRegistry,
|
||||
private val messageHandler: ConsoleMessageHandler,
|
||||
private val isCleanBuild: Boolean,
|
||||
) {
|
||||
private val totalModuleLevelBuilderCount = builderRegistry.moduleLevelBuilderCount
|
||||
private val elapsedTimeNanosByBuilder = ConcurrentHashMap<Builder, AtomicLong>()
|
||||
private val numberOfSourcesProcessedByBuilder = ConcurrentHashMap<Builder, AtomicInteger>()
|
||||
|
||||
suspend fun build(context: CompileContextImpl) {
|
||||
try {
|
||||
val buildSpan = Tracer.start("IncProjectBuilder.runBuild")
|
||||
runBuild(context)
|
||||
buildSpan.complete()
|
||||
}
|
||||
catch (e: StopBuildException) {
|
||||
// some builder decided to stop the build - report optional progress message if any
|
||||
e.message?.takeIf { it.isNotEmpty() }?.let {
|
||||
messageHandler.processMessage(ProgressMessage(it))
|
||||
}
|
||||
}
|
||||
catch (e: BuildDataCorruptedException) {
|
||||
messageHandler.warn("Internal caches are corrupted or have outdated format, forcing project rebuild: $e")
|
||||
throw RebuildRequestedException(e)
|
||||
}
|
||||
catch (e: ProjectBuildException) {
|
||||
val cause = e.cause
|
||||
if (cause is IOException || cause is BuildDataCorruptedException || (cause is RuntimeException && cause.cause is IOException)) {
|
||||
messageHandler.warn("Internal caches are corrupted or have outdated format, forcing project rebuild: $e")
|
||||
throw RebuildRequestedException(cause)
|
||||
}
|
||||
else {
|
||||
// should stop the build with error
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun runBuild(context: CompileContextImpl) {
|
||||
context.setDone(0.0f)
|
||||
|
||||
context.addBuildListener(ChainedTargetsBuildListener(context))
|
||||
|
||||
// deletes class loader classpath index files for changed output roots
|
||||
context.addBuildListener(object : BuildListener {
|
||||
override fun filesGenerated(event: FileGeneratedEvent) {
|
||||
val paths = event.paths
|
||||
val fs = FileSystems.getDefault()
|
||||
if (paths.size == 1) {
|
||||
deleteFiles(paths.iterator().next().first, fs)
|
||||
return
|
||||
}
|
||||
|
||||
val outputs = HashSet<String>()
|
||||
for (pair in paths) {
|
||||
val root = pair.getFirst()
|
||||
if (outputs.add(root)) {
|
||||
deleteFiles(root, fs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteFiles(rootPath: String, fs: FileSystem) {
|
||||
val root = fs.getPath(rootPath)
|
||||
try {
|
||||
Files.deleteIfExists(root.resolve(CLASSPATH_INDEX_FILE_NAME))
|
||||
Files.deleteIfExists(root.resolve(UNMODIFIED_MARK_FILE_NAME))
|
||||
}
|
||||
catch (_: IOException) {
|
||||
}
|
||||
}
|
||||
})
|
||||
require(builderRegistry.targetBuilders.isEmpty())
|
||||
val allModuleLevelBuildersBuildStartedSpan = Tracer.start("All ModuleLevelBuilder.buildStarted")
|
||||
for (builder in builderRegistry.moduleLevelBuilders) {
|
||||
builder.buildStarted(context)
|
||||
}
|
||||
allModuleLevelBuildersBuildStartedSpan.complete()
|
||||
|
||||
var buildProgress: BuildProgress? = null
|
||||
try {
|
||||
val projectDescriptor = context.projectDescriptor
|
||||
val sortedTargetChunks = projectDescriptor.buildTargetIndex.getSortedTargetChunks(context)
|
||||
buildProgress = BuildProgress(
|
||||
projectDescriptor.dataManager,
|
||||
projectDescriptor.buildTargetIndex,
|
||||
sortedTargetChunks,
|
||||
Predicate { isBuildChunkAffected(scope = context.scope, chunk = it) }
|
||||
)
|
||||
|
||||
require(builderRegistry.beforeTasks.isEmpty())
|
||||
|
||||
val checkingSourcesSpan = Tracer.start("Building targets")
|
||||
BuildTaskLauncher(context = context, buildProgress = buildProgress, builder = this).buildInParallel()
|
||||
checkingSourcesSpan.complete()
|
||||
|
||||
require(builderRegistry.afterTasks.isEmpty())
|
||||
sendElapsedTimeMessages(context)
|
||||
}
|
||||
finally {
|
||||
if (buildProgress != null) {
|
||||
buildProgress.updateExpectedAverageTime()
|
||||
if (isCleanBuild && !Utils.errorsDetected(context) && !context.cancelStatus.isCanceled) {
|
||||
context.projectDescriptor.dataManager.targetStateManager.setLastSuccessfulRebuildDuration(buildProgress.absoluteBuildTime)
|
||||
}
|
||||
}
|
||||
|
||||
for (builder in builderRegistry.moduleLevelBuilders) {
|
||||
builder.buildFinished(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendElapsedTimeMessages(context: CompileContext) {
|
||||
elapsedTimeNanosByBuilder.entries
|
||||
.stream()
|
||||
.map<BuilderStatisticsMessage?> { entry ->
|
||||
val processedSourcesRef = numberOfSourcesProcessedByBuilder.get(entry!!.key)
|
||||
val processedSources = processedSourcesRef?.get() ?: 0
|
||||
BuilderStatisticsMessage(entry.key.presentableName, processedSources, entry.value.get() / 1000000)
|
||||
}
|
||||
.sorted(Comparator.comparing<BuilderStatisticsMessage?, String?>(Function { obj: BuilderStatisticsMessage? -> obj!!.builderName }))
|
||||
.forEach { buildMessage: BuilderStatisticsMessage? -> context.processMessage(buildMessage) }
|
||||
}
|
||||
|
||||
private fun runBuildersForChunk(context: CompileContext, chunk: BuildTargetChunk, buildProgress: BuildProgress): Boolean {
|
||||
val targets: Set<BuildTarget<*>> = chunk.targets
|
||||
if (targets.size > 1) {
|
||||
val moduleTargets = LinkedHashSet<ModuleBuildTarget>()
|
||||
for (target in targets) {
|
||||
if (target is ModuleBuildTarget) {
|
||||
moduleTargets.add(target)
|
||||
}
|
||||
else {
|
||||
val targetsString = targets.joinToString(separator = ", ") { it.presentableName }
|
||||
val message = "Cannot build \"${target.presentableName}\" because it is included into a circular dependency ($targetsString))"
|
||||
context.processMessage(CompilerMessage("", BuildMessage.Kind.ERROR, message))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return runModuleLevelBuilders(ModuleInfoAwareCompileContextCopy(context, moduleTargets), ModuleChunk(moduleTargets), buildProgress)
|
||||
}
|
||||
|
||||
val target = targets.iterator().next()
|
||||
if (target is ModuleBuildTarget) {
|
||||
@Suppress("RemoveRedundantQualifierName")
|
||||
val mbt = java.util.Set.of(target)
|
||||
return runModuleLevelBuilders(ModuleInfoAwareCompileContextCopy(context, mbt), ModuleChunk(mbt), buildProgress)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
completeRecompiledSourcesSet(context, targets as Collection<BuildTarget<BuildRootDescriptor>>)
|
||||
|
||||
/*
|
||||
In general, the set of files corresponding to changed source file may be different
|
||||
Need this, for example, to keep up with case changes in file names for case-insensitive OSes:
|
||||
deleting the output before copying is the only way to ensure the case of the output file's name is exactly the same as source file's case
|
||||
*/
|
||||
cleanOldOutputs(context, target)
|
||||
|
||||
val builders = BuilderRegistry.getInstance().targetBuilders
|
||||
var builderCount = 0
|
||||
for (builder in builders) {
|
||||
buildTarget(target, context, builder)
|
||||
builderCount++
|
||||
buildProgress.updateProgress(target, (builderCount.toDouble()) / builders.size, context)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun processDeletedPaths(context: CompileContext, targets: Set<BuildTarget<*>>): Boolean {
|
||||
var doneSomething = false
|
||||
// cleanup outputs
|
||||
val targetToRemovedSources = HashMap<BuildTarget<*>, MutableCollection<String>>()
|
||||
|
||||
val dirsToDelete = HashSet<Path>()
|
||||
for (target in targets) {
|
||||
val deletedPaths = context.projectDescriptor.fsState.getAndClearDeletedPaths(target)
|
||||
if (deletedPaths.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
|
||||
targetToRemovedSources.put(target, deletedPaths)
|
||||
if (isTargetOutputCleared(context, target)) {
|
||||
continue
|
||||
}
|
||||
|
||||
val dataManager = context.projectDescriptor.dataManager
|
||||
val buildTargetId = dataManager.targetStateManager.getBuildTargetId(target)
|
||||
val shouldPruneEmptyDirs = target is ModuleBasedTarget<*>
|
||||
val sourceToOutputStorage = dataManager.getSourceToOutputMap(target)
|
||||
val logger = context.loggingManager.projectBuilderLogger
|
||||
// actually delete outputs associated with removed paths
|
||||
for (deletedSource in deletedPaths.sorted()) {
|
||||
// deleting outputs corresponding to a non-existing source
|
||||
val outputs = sourceToOutputStorage.getOutputs(deletedSource)
|
||||
if (outputs != null && !outputs.isEmpty()) {
|
||||
val deletedOutputPaths = ArrayList<String>()
|
||||
val outputToSourceRegistry = dataManager.outputToTargetMapping
|
||||
for (output in outputToSourceRegistry.removeTargetAndGetSafeToDeleteOutputs(outputs, buildTargetId, sourceToOutputStorage)) {
|
||||
val deleted = BuildOperations.deleteRecursivelyAndCollectDeleted(Path.of(output), deletedOutputPaths, if (shouldPruneEmptyDirs) dirsToDelete else null)
|
||||
if (deleted) {
|
||||
doneSomething = true
|
||||
}
|
||||
}
|
||||
if (!deletedOutputPaths.isEmpty()) {
|
||||
if (logger.isEnabled) {
|
||||
logger.logDeletedFiles(deletedOutputPaths)
|
||||
}
|
||||
context.processMessage(FileDeletedEvent(deletedOutputPaths))
|
||||
}
|
||||
}
|
||||
|
||||
if (target is ModuleBuildTarget) {
|
||||
// check if the deleted source was associated with a form
|
||||
val sourceToFormMap = dataManager.getSourceToFormMap(target)
|
||||
val boundForms = sourceToFormMap.getOutputs(deletedSource)
|
||||
if (boundForms != null) {
|
||||
for (formPath in boundForms) {
|
||||
val formFile = Path.of(formPath)
|
||||
if (Files.exists(formFile)) {
|
||||
FSOperations.markDirty(context, CompilationRound.CURRENT, formFile.toFile())
|
||||
}
|
||||
}
|
||||
sourceToFormMap.remove(deletedSource)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetToRemovedSources.isEmpty()) {
|
||||
val existing = Utils.REMOVED_SOURCES_KEY.get(context)
|
||||
if (existing != null) {
|
||||
for (entry in existing.entries) {
|
||||
val paths = targetToRemovedSources.get(entry.key)
|
||||
if (paths == null) {
|
||||
targetToRemovedSources.put(entry.key, entry.value)
|
||||
}
|
||||
else {
|
||||
paths.addAll(entry.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
Utils.REMOVED_SOURCES_KEY.set(context, targetToRemovedSources)
|
||||
}
|
||||
|
||||
FSOperations.pruneEmptyDirs(context, dirsToDelete)
|
||||
return doneSomething
|
||||
}
|
||||
|
||||
private fun <R : BuildRootDescriptor, T : BuildTarget<R>> buildTarget(target: T, context: CompileContext, builder: TargetBuilder<*, *>) {
|
||||
if (builder.getTargetTypes().contains(target.getTargetType())) {
|
||||
val holder = object : DirtyFilesHolderBase<R, T>(context) {
|
||||
override fun processDirtyFiles(processor: FileProcessor<R, T>) {
|
||||
context.projectDescriptor.fsState.processFilesToRecompile(context, target, processor)
|
||||
}
|
||||
}
|
||||
val outputConsumer = BuildOutputConsumerImpl(target, context)
|
||||
val start = System.nanoTime()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(builder as TargetBuilder<R, T>).build(target, holder, outputConsumer, context)
|
||||
storeBuilderStatistics(builder, System.nanoTime() - start, outputConsumer.numberOfProcessedSources)
|
||||
outputConsumer.fireFileGeneratedEvent()
|
||||
context.checkCanceled()
|
||||
}
|
||||
}
|
||||
|
||||
// return true if changed something, false otherwise
|
||||
private fun runModuleLevelBuilders(context: CompileContext, chunk: ModuleChunk, buildProgress: BuildProgress): Boolean {
|
||||
for (category in BuilderCategory.entries) {
|
||||
for (builder in builderRegistry.getBuilders(category)) {
|
||||
builder.chunkBuildStarted(context, chunk)
|
||||
}
|
||||
}
|
||||
|
||||
completeRecompiledSourcesSet(context, chunk.targets)
|
||||
|
||||
var doneSomething = false
|
||||
var rebuildFromScratchRequested = false
|
||||
var nextPassRequired: Boolean
|
||||
val outputConsumer = ChunkBuildOutputConsumerImpl(context)
|
||||
try {
|
||||
val fsState = context.projectDescriptor.fsState
|
||||
val dirtyFilesHolder = object : DirtyFilesHolderBase<JavaSourceRootDescriptor, ModuleBuildTarget>(context) {
|
||||
override fun processDirtyFiles(processor: FileProcessor<JavaSourceRootDescriptor, ModuleBuildTarget>) {
|
||||
for (target in chunk.targets) {
|
||||
fsState.processFilesToRecompile(context, target, processor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
nextPassRequired = false
|
||||
fsState.beforeNextRoundStart(context, chunk)
|
||||
|
||||
if (!JavaBuilderUtil.isForcedRecompilationAllJavaModules(context.scope)) {
|
||||
val cleanedSources = BuildOperations.cleanOutputsCorrespondingToChangedFiles(context, dirtyFilesHolder)
|
||||
for (entry in cleanedSources.entries) {
|
||||
val files = entry.value.keys
|
||||
if (files.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
|
||||
val mapping = context.projectDescriptor.dataManager.getSourceToOutputMap(entry.key)
|
||||
for (srcFile in files) {
|
||||
val outputs = entry.value.get(srcFile)!!
|
||||
mapping.setOutputs(srcFile.path, outputs)
|
||||
if (!outputs.isEmpty()) {
|
||||
messageHandler.info("Some outputs were not removed for ${srcFile.path} source file: $outputs")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var buildersPassed = 0
|
||||
BUILDER_CATEGORY_LOOP@ for (category in BuilderCategory.entries) {
|
||||
val builders = builderRegistry.getBuilders(category)
|
||||
if (category == BuilderCategory.CLASS_POST_PROCESSOR) {
|
||||
// ensure changes from instruments are visible to class post-processors
|
||||
saveInstrumentedClasses(outputConsumer)
|
||||
}
|
||||
if (builders.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
for (builder in builders) {
|
||||
outputConsumer.setCurrentBuilderName(builder.presentableName)
|
||||
processDeletedPaths(context, chunk.targets)
|
||||
val start = System.nanoTime()
|
||||
val processedSourcesBefore = outputConsumer.getNumberOfProcessedSources()
|
||||
val buildResult = builder.build(context, chunk, dirtyFilesHolder, outputConsumer)
|
||||
storeBuilderStatistics(
|
||||
builder = builder,
|
||||
elapsedTime = System.nanoTime() - start,
|
||||
processedFiles = outputConsumer.getNumberOfProcessedSources() - processedSourcesBefore,
|
||||
)
|
||||
|
||||
doneSomething = doneSomething or (buildResult != ModuleLevelBuilder.ExitCode.NOTHING_DONE)
|
||||
|
||||
if (buildResult == ModuleLevelBuilder.ExitCode.ABORT) {
|
||||
throw StopBuildException("Builder ${builder.presentableName} requested build stop")
|
||||
}
|
||||
context.checkCanceled()
|
||||
if (buildResult == ModuleLevelBuilder.ExitCode.ADDITIONAL_PASS_REQUIRED) {
|
||||
nextPassRequired = true
|
||||
}
|
||||
else if (buildResult == ModuleLevelBuilder.ExitCode.CHUNK_REBUILD_REQUIRED) {
|
||||
if (!rebuildFromScratchRequested && !JavaBuilderUtil.isForcedRecompilationAllJavaModules(context.scope)) {
|
||||
notifyChunkRebuildRequested(context, chunk, builder)
|
||||
// allow rebuild from scratch only once per chunk
|
||||
rebuildFromScratchRequested = true
|
||||
try {
|
||||
// forcibly mark all files in the chunk dirty
|
||||
fsState.clearContextRoundData(context)
|
||||
FSOperations.markDirty(context, CompilationRound.NEXT, chunk, null)
|
||||
// reverting to the beginning
|
||||
nextPassRequired = true
|
||||
outputConsumer.clear()
|
||||
break@BUILDER_CATEGORY_LOOP
|
||||
}
|
||||
catch (e: Exception) {
|
||||
throw ProjectBuildException(e)
|
||||
}
|
||||
}
|
||||
else {
|
||||
messageHandler.debug("Builder ${builder.presentableName} requested second chunk rebuild")
|
||||
}
|
||||
}
|
||||
|
||||
buildersPassed++
|
||||
for (target in chunk.targets) {
|
||||
buildProgress.updateProgress(target, (buildersPassed.toDouble()) / totalModuleLevelBuilderCount, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
outputConsumer.setCurrentBuilderName(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
val moreToCompile = JavaBuilderUtil.updateMappingsOnRoundCompletion(context, dirtyFilesHolder, chunk)
|
||||
if (moreToCompile) {
|
||||
nextPassRequired = true
|
||||
}
|
||||
JavaBuilderUtil.clearDataOnRoundCompletion(context)
|
||||
}
|
||||
} while (nextPassRequired)
|
||||
}
|
||||
finally {
|
||||
saveInstrumentedClasses(outputConsumer)
|
||||
outputConsumer.fireFileGeneratedEvents()
|
||||
outputConsumer.clear()
|
||||
for (category in BuilderCategory.entries) {
|
||||
for (builder in builderRegistry.getBuilders(category)) {
|
||||
builder.chunkBuildFinished(context, chunk)
|
||||
}
|
||||
}
|
||||
if (Utils.errorsDetected(context)) {
|
||||
context.processMessage(CompilerMessage("", BuildMessage.Kind.JPS_INFO, "Errors occurred while compiling module ${chunk.presentableShortName}"))
|
||||
}
|
||||
}
|
||||
|
||||
return doneSomething
|
||||
}
|
||||
|
||||
private fun ensureFsStateInitialized(context: CompileContext, target: BuildTarget<*>) {
|
||||
val fsState = context.projectDescriptor.fsState
|
||||
if (isCleanBuild) {
|
||||
val targetRoots = (context.projectDescriptor.buildRootIndex as BazelBuildRootIndex).descriptors
|
||||
fsState.getDelta(target).clearRecompile(targetRoots)
|
||||
for (rootDescriptor in targetRoots) {
|
||||
// if it is a full project rebuild, all storages are already completely cleared;
|
||||
// so passing null as stampStorage because there is no need to access the storage to clear non-existing data
|
||||
fsState.markDirty(
|
||||
/* context = */ context,
|
||||
/* round = */ CompilationRound.CURRENT,
|
||||
/* file = */ rootDescriptor.file,
|
||||
/* buildRootDescriptor = */ rootDescriptor,
|
||||
/* stampStorage = */ null,
|
||||
/* saveEventStamp = */ false,
|
||||
)
|
||||
}
|
||||
FSOperations.addCompletelyMarkedDirtyTarget(context, target)
|
||||
fsState.markInitialScanPerformed(target)
|
||||
}
|
||||
else if (!fsState.isInitialScanPerformed(target)) {
|
||||
BuildOperations.initTargetFSState(context, target, false)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun buildTargetChunk(context: CompileContext, chunk: BuildTargetChunk, buildProgress: BuildProgress) {
|
||||
val buildSpan = Tracer.start { "Building ${chunk.presentableName}" }
|
||||
val fsState = context.projectDescriptor.fsState
|
||||
var doneSomething: Boolean
|
||||
try {
|
||||
context.setCompilationStartStamp(chunk.targets, System.currentTimeMillis())
|
||||
|
||||
sendBuildingTargetMessages(chunk.targets, BuildingTargetProgressMessage.Event.STARTED)
|
||||
Utils.ERRORS_DETECTED_KEY.set(context, false)
|
||||
|
||||
for (target in chunk.targets) {
|
||||
ensureFsStateInitialized(context = context, target = target)
|
||||
}
|
||||
|
||||
doneSomething = processDeletedPaths(context, chunk.targets)
|
||||
|
||||
fsState.beforeChunkBuildStart(context, chunk)
|
||||
|
||||
val runBuildersSpan = Tracer.start { "runBuilders " + chunk.presentableName }
|
||||
doneSomething = doneSomething or runBuildersForChunk(context, chunk, buildProgress)
|
||||
runBuildersSpan.complete()
|
||||
|
||||
fsState.clearContextRoundData(context)
|
||||
fsState.clearContextChunk(context)
|
||||
|
||||
if (doneSomething) {
|
||||
BuildOperations.markTargetsUpToDate(context, chunk)
|
||||
}
|
||||
}
|
||||
catch (e: BuildDataCorruptedException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: ProjectBuildException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
val message = StringBuilder()
|
||||
message.append(chunk.presentableName).append(": ").append(e.javaClass.getName())
|
||||
e.message?.let {
|
||||
message.append(": ").append(it)
|
||||
}
|
||||
throw ProjectBuildException(message.toString(), e)
|
||||
}
|
||||
finally {
|
||||
buildProgress.onTargetChunkFinished(chunk, context)
|
||||
try {
|
||||
// restore deleted paths that were not processed by 'integrate'
|
||||
val map = Utils.REMOVED_SOURCES_KEY.get(context)
|
||||
if (map != null) {
|
||||
for (entry in map.entries) {
|
||||
val paths = entry.value
|
||||
if (paths != null) {
|
||||
for (path in paths) {
|
||||
fsState.registerDeleted(context, entry.key, Path.of(path), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Utils.REMOVED_SOURCES_KEY.set(context, null)
|
||||
sendBuildingTargetMessages(chunk.targets, BuildingTargetProgressMessage.Event.FINISHED)
|
||||
buildSpan.complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendBuildingTargetMessages(targets: Set<BuildTarget<*>>, event: BuildingTargetProgressMessage.Event) {
|
||||
messageHandler.processMessage(BuildingTargetProgressMessage(targets, event))
|
||||
}
|
||||
|
||||
private fun storeBuilderStatistics(builder: Builder, elapsedTime: Long, processedFiles: Int) {
|
||||
elapsedTimeNanosByBuilder.computeIfAbsent(builder) { AtomicLong() }.addAndGet(elapsedTime)
|
||||
numberOfSourcesProcessedByBuilder.computeIfAbsent(builder) { AtomicInteger() }.addAndGet(processedFiles)
|
||||
}
|
||||
}
|
||||
|
||||
private class ChunkBuildOutputConsumerImpl(private val context: CompileContext) : OutputConsumer {
|
||||
private val target2Consumer = HashMap<BuildTarget<*>, BuildOutputConsumerImpl>()
|
||||
private val classes = HashMap<String, CompiledClass>()
|
||||
private val targetToClassesMap = HashMap<BuildTarget<*>, MutableCollection<CompiledClass>>()
|
||||
private val outputToBuilderNameMap = Object2ObjectMaps.synchronize(Object2ObjectOpenCustomHashMap<File, String>(FileHashStrategy))
|
||||
|
||||
@Volatile
|
||||
private var currentBuilderName: String? = null
|
||||
|
||||
fun setCurrentBuilderName(builderName: String?) {
|
||||
currentBuilderName = builderName
|
||||
}
|
||||
|
||||
override fun getTargetCompiledClasses(target: BuildTarget<*>): Collection<CompiledClass> {
|
||||
return Collections.unmodifiableCollection(targetToClassesMap.get(target) ?: return emptyList())
|
||||
}
|
||||
|
||||
override fun getCompiledClasses(): MutableMap<String, CompiledClass> = Collections.unmodifiableMap(classes)
|
||||
|
||||
override fun lookupClassBytes(className: String?): BinaryContent? = classes.get(className)?.content
|
||||
|
||||
override fun registerCompiledClass(target: BuildTarget<*>?, compiled: CompiledClass) {
|
||||
val className = compiled.className
|
||||
if (className != null) {
|
||||
classes.put(className, compiled)
|
||||
if (target != null) {
|
||||
var classes = targetToClassesMap.get(target)
|
||||
if (classes == null) {
|
||||
classes = ArrayList<CompiledClass>()
|
||||
targetToClassesMap.put(target, classes)
|
||||
}
|
||||
classes.add(compiled)
|
||||
}
|
||||
}
|
||||
if (target != null) {
|
||||
registerOutputFile(target = target, outputFile = compiled.outputFile, sourcePaths = compiled.sourceFilesPaths)
|
||||
}
|
||||
}
|
||||
|
||||
override fun registerOutputFile(target: BuildTarget<*>, outputFile: File, sourcePaths: MutableCollection<String?>) {
|
||||
val currentBuilder = currentBuilderName
|
||||
if (currentBuilder != null) {
|
||||
val previousBuilder = outputToBuilderNameMap.put(outputFile, currentBuilder)
|
||||
if (previousBuilder != null && previousBuilder != currentBuilder) {
|
||||
val source = if (sourcePaths.isEmpty()) null else sourcePaths.iterator().next()
|
||||
context.processMessage(CompilerMessage(
|
||||
currentBuilder, BuildMessage.Kind.ERROR, "Output file \"${outputFile}\" has already been registered by \"$previousBuilder\"", source
|
||||
))
|
||||
}
|
||||
}
|
||||
var consumer = target2Consumer.get(target)
|
||||
if (consumer == null) {
|
||||
consumer = BuildOutputConsumerImpl(target, context)
|
||||
target2Consumer.put(target, consumer)
|
||||
}
|
||||
consumer.registerOutputFile(outputFile, sourcePaths)
|
||||
}
|
||||
|
||||
fun fireFileGeneratedEvents() {
|
||||
for (consumer in target2Consumer.values) {
|
||||
consumer.fireFileGeneratedEvent()
|
||||
}
|
||||
}
|
||||
|
||||
fun getNumberOfProcessedSources(): Int {
|
||||
var total = 0
|
||||
for (consumer in target2Consumer.values) {
|
||||
total += consumer.numberOfProcessedSources
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
target2Consumer.clear()
|
||||
classes.clear()
|
||||
targetToClassesMap.clear()
|
||||
outputToBuilderNameMap.clear()
|
||||
}
|
||||
}
|
||||
|
||||
private class ChainedTargetsBuildListener(private val context: CompileContextImpl) : BuildListener {
|
||||
override fun filesGenerated(event: FileGeneratedEvent) {
|
||||
val projectDescriptor = context.projectDescriptor
|
||||
val fsState = projectDescriptor.fsState
|
||||
for (pair in event.paths) {
|
||||
val relativePath = pair.getSecond()
|
||||
val file = if (relativePath == ".") File(pair.getFirst()) else File(pair.getFirst(), relativePath)
|
||||
for (buildRootDescriptor in projectDescriptor.buildRootIndex.findAllParentDescriptors<BuildRootDescriptor>(file, context)) {
|
||||
val target = buildRootDescriptor.target
|
||||
if (event.sourceTarget != target) {
|
||||
fsState.markDirty(context, file, buildRootDescriptor, projectDescriptor.dataManager.getFileStampStorage(target), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun filesDeleted(event: FileDeletedEvent) {
|
||||
val fsState = context.projectDescriptor.fsState
|
||||
val rootIndex = context.projectDescriptor.buildRootIndex as BazelBuildRootIndex
|
||||
for (path in event.filePaths) {
|
||||
val file = Path.of(path)
|
||||
val rootDescriptor = rootIndex.fileToDescriptors.get(file) ?: continue
|
||||
fsState.registerDeleted(context, rootDescriptor.target, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateEstimatedBuildTime(
|
||||
projectDescriptor: ProjectDescriptor,
|
||||
target: ModuleBuildTarget,
|
||||
messageHandler: ConsoleMessageHandler,
|
||||
): Long {
|
||||
// compute estimated times for dirty targets
|
||||
var affectedTargets = 0
|
||||
val avgTimeToBuild = projectDescriptor.dataManager.targetStateManager.getAverageBuildTime(target.targetType)
|
||||
val estimatedBuildTime = if (avgTimeToBuild > 0) {
|
||||
affectedTargets = 1
|
||||
// 1. in general case, this time should include dependency analysis and cache update times
|
||||
// 2. need to check isAffected() since some targets (like artifacts) may be unaffected even for rebuild
|
||||
avgTimeToBuild
|
||||
}
|
||||
else {
|
||||
0L
|
||||
}
|
||||
messageHandler.info("Affected build targets count: $affectedTargets")
|
||||
return estimatedBuildTime
|
||||
}
|
||||
|
||||
internal fun reportRebuiltModules(context: CompileContextImpl) {
|
||||
val modules = BuildTargetConfiguration.MODULES_WITH_TARGET_CONFIG_CHANGED_KEY.get(context)
|
||||
if (modules.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val text = "${modules.joinToString { m -> "'" + m.name + "'" }} was fully rebuilt due to project configuration changes"
|
||||
context.processMessage(CompilerMessage("", BuildMessage.Kind.INFO, text))
|
||||
}
|
||||
|
||||
internal fun reportUnprocessedChanges(context: CompileContextImpl, moduleTarget: ModuleBuildTarget) {
|
||||
if (context.projectDescriptor.fsState.hasUnprocessedChanges(context, moduleTarget)) {
|
||||
context.processMessage(UnprocessedFSChangesNotification())
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTargetOutputCleared(context: CompileContext, target: BuildTarget<*>?): Boolean {
|
||||
synchronized(TARGET_WITH_CLEARED_OUTPUT) {
|
||||
val data = context.getUserData(TARGET_WITH_CLEARED_OUTPUT)
|
||||
return data != null && data.contains(target)
|
||||
}
|
||||
}
|
||||
|
||||
private class ModuleInfoAwareCompileContextCopy(
|
||||
private val context: CompileContext,
|
||||
private val moduleTargets: Collection<ModuleBuildTarget>,
|
||||
) : CompileContext by context {
|
||||
override fun processMessage(message: BuildMessage) {
|
||||
if (message is CompilerMessage) {
|
||||
for (target in moduleTargets) {
|
||||
message.addModuleName(target.module.name)
|
||||
}
|
||||
}
|
||||
context.processMessage(message)
|
||||
}
|
||||
|
||||
@Suppress("OVERRIDE_DEPRECATION")
|
||||
override fun isProjectRebuild() = false
|
||||
|
||||
override fun isCanceled(): Boolean = context.isCanceled
|
||||
}
|
||||
|
||||
/**
|
||||
* if an output file is generated from multiple sources, make sure all of them are added for recompilation
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private fun <T : BuildTarget<R>, R : BuildRootDescriptor> completeRecompiledSourcesSet(context: CompileContext, targets: Collection<T>) {
|
||||
val scope = context.scope
|
||||
for (target in targets) {
|
||||
if (scope.isBuildForced(target)) {
|
||||
// assuming build is either forced for all targets in a chunk or for none of them
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val projectDescriptor = context.projectDescriptor
|
||||
val affectedOutputs = CollectionFactory.createFilePathSet()
|
||||
val affectedSources = CollectionFactory.createFilePathSet()
|
||||
|
||||
val mappings = ArrayList<SourceToOutputMapping>()
|
||||
for (target in targets) {
|
||||
projectDescriptor.fsState.processFilesToRecompile(context, target, object : FileProcessor<R, T> {
|
||||
private var srcToOut: SourceToOutputMapping? = null
|
||||
|
||||
override fun apply(target: T, file: File, root: R): Boolean {
|
||||
val src = file.invariantSeparatorsPath
|
||||
if (!affectedSources.add(src)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// lazy init
|
||||
var srcToOut = this.srcToOut
|
||||
if (srcToOut == null) {
|
||||
srcToOut = projectDescriptor.dataManager.getSourceToOutputMap(target)
|
||||
mappings.add(srcToOut)
|
||||
this.srcToOut = srcToOut
|
||||
}
|
||||
|
||||
val outs = srcToOut.getOutputs(src) ?: return true
|
||||
// Temporary hack for KTIJ-197
|
||||
// Change of only one input of *.kotlin_module files didn't trigger recompilation of all inputs in old behavior.
|
||||
// Now it does. It isn't yet obvious whether it is right or wrong behavior. Let's leave old behavior for a
|
||||
// while for safety and keeping kotlin incremental JPS tests green
|
||||
outs.filterTo(affectedOutputs) { "kotlin_module" != it.substringAfterLast('.') }
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (affectedOutputs.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
for (srcToOut in mappings) {
|
||||
val cursor = srcToOut.cursor()
|
||||
while (cursor.hasNext()) {
|
||||
val src = cursor.next()
|
||||
if (!affectedSources.contains(src)) {
|
||||
for (out in cursor.outputPaths) {
|
||||
if (affectedOutputs.contains(out)) {
|
||||
FSOperations.markDirtyIfNotDeleted(context, CompilationRound.CURRENT, Path.of(src))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : BuildRootDescriptor?> cleanOldOutputs(context: CompileContext, target: BuildTarget<T>) {
|
||||
if (!context.scope.isBuildForced(target)) {
|
||||
BuildOperations.cleanOutputsCorrespondingToChangedFiles(context, object : DirtyFilesHolderBase<T, BuildTarget<T>>(context) {
|
||||
override fun processDirtyFiles(processor: FileProcessor<T, BuildTarget<T>>) {
|
||||
context.projectDescriptor.fsState.processFilesToRecompile(context, target, processor)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyChunkRebuildRequested(context: CompileContext, chunk: ModuleChunk, builder: ModuleLevelBuilder) {
|
||||
var infoMessage = "Builder \"${builder.presentableName}\" requested rebuild of module chunk \"${chunk.name}\""
|
||||
var kind = BuildMessage.Kind.JPS_INFO
|
||||
val scope = context.scope
|
||||
for (target in chunk.targets) {
|
||||
if (!scope.isWholeTargetAffected(target)) {
|
||||
infoMessage += ".\n"
|
||||
infoMessage += "Consider building whole project or rebuilding the module."
|
||||
kind = BuildMessage.Kind.INFO
|
||||
break
|
||||
}
|
||||
}
|
||||
context.processMessage(CompilerMessage("", kind, infoMessage))
|
||||
}
|
||||
|
||||
private fun saveInstrumentedClasses(outputConsumer: ChunkBuildOutputConsumerImpl) {
|
||||
for (compiledClass in outputConsumer.compiledClasses.values) {
|
||||
if (compiledClass.isDirty) {
|
||||
compiledClass.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun checkRebuildRequired(
|
||||
scope: CompileScope,
|
||||
projectDescriptor: ProjectDescriptor,
|
||||
moduleTarget: ModuleBuildTarget,
|
||||
isDebugEnabled: Boolean,
|
||||
messageHandler: ConsoleMessageHandler,
|
||||
): Boolean {
|
||||
val targetStateManager = projectDescriptor.dataManager.targetStateManager
|
||||
val timeThreshold = targetStateManager.getLastSuccessfulRebuildDuration() * 95 / 100 // 95% of last registered clean rebuild time
|
||||
if (timeThreshold <= 0) {
|
||||
if (isDebugEnabled) {
|
||||
messageHandler.debug("Rebuild heuristic: no stats available")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// check that this is a whole-project incremental build
|
||||
// checking only JavaModuleBuildTargetType because these target types directly correspond to project modules
|
||||
val type = JavaModuleBuildTargetType.PRODUCTION
|
||||
if (!scope.isAllTargetsOfTypeAffected(type)) {
|
||||
if (isDebugEnabled) {
|
||||
messageHandler.debug("Rebuild heuristic: skipping the check because some targets are excluded from compilation scope," +
|
||||
" e.g. targets of type ${type.typeId}")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// compute estimated times for dirty targets
|
||||
val estimatedWorkTime = calculateEstimatedBuildTime(projectDescriptor, moduleTarget, messageHandler)
|
||||
if (isDebugEnabled) {
|
||||
messageHandler.debug("Rebuild heuristic: estimated build time / timeThreshold : $estimatedWorkTime / $timeThreshold")
|
||||
}
|
||||
|
||||
if (estimatedWorkTime < timeThreshold) {
|
||||
return false
|
||||
}
|
||||
|
||||
val message = """
|
||||
Too many files require recompilation, forcing full rebuild.
|
||||
* Estimated build duration (linear): ${Formats.formatDuration(estimatedWorkTime)}
|
||||
* Last successful rebuild duration (linear): ${Formats.formatDuration(targetStateManager.getLastSuccessfulRebuildDuration())}
|
||||
* Rebuild heuristic time threshold: ${Formats.formatDuration(timeThreshold)}
|
||||
""".trimIndent()
|
||||
messageHandler.info(message)
|
||||
return true
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.bazel.jvm.jps.impl
|
||||
|
||||
import com.intellij.openapi.util.io.FileFilters
|
||||
@@ -7,7 +8,7 @@ import java.io.File
|
||||
import java.io.FileFilter
|
||||
import java.nio.file.Path
|
||||
|
||||
object BazelModuleExcludeIndex : ModuleExcludeIndex {
|
||||
internal object NoopModuleExcludeIndex : ModuleExcludeIndex {
|
||||
override fun isExcluded(file: File) = false
|
||||
|
||||
override fun isExcludedFromModule(file: File, module: JpsModule) = false
|
||||
@@ -0,0 +1,84 @@
|
||||
@file:Suppress("UnstableApiUsage", "HardCodedStringLiteral", "ReplaceJavaStaticMethodWithKotlinAnalog", "ReplaceGetOrSet")
|
||||
|
||||
package org.jetbrains.bazel.jvm.jps.impl
|
||||
|
||||
import org.jetbrains.bazel.jvm.jps.state.loadTargetState
|
||||
import org.jetbrains.jps.builders.BuildTarget
|
||||
import org.jetbrains.jps.builders.BuildTargetIndex
|
||||
import org.jetbrains.jps.builders.BuildTargetRegistry.ModuleTargetSelector
|
||||
import org.jetbrains.jps.builders.BuildTargetType
|
||||
import org.jetbrains.jps.builders.impl.BuildDataPathsImpl
|
||||
import org.jetbrains.jps.builders.impl.BuildTargetChunk
|
||||
import org.jetbrains.jps.builders.logging.BuildLoggingManager
|
||||
import org.jetbrains.jps.cmdline.ProjectDescriptor
|
||||
import org.jetbrains.jps.incremental.CompileContext
|
||||
import org.jetbrains.jps.incremental.ModuleBuildTarget
|
||||
import org.jetbrains.jps.incremental.fs.BuildFSState
|
||||
import org.jetbrains.jps.incremental.relativizer.PathRelativizerService
|
||||
import org.jetbrains.jps.incremental.storage.BuildDataManager
|
||||
import org.jetbrains.jps.incremental.storage.BuildDataVersionManager
|
||||
import org.jetbrains.jps.incremental.storage.StorageManager
|
||||
import org.jetbrains.jps.indices.IgnoredFileIndex
|
||||
import org.jetbrains.jps.model.JpsModel
|
||||
import org.jetbrains.jps.model.module.JpsModule
|
||||
import java.nio.file.Path
|
||||
|
||||
internal fun loadJpsProject(
|
||||
storageManager: StorageManager,
|
||||
dataStorageRoot: Path,
|
||||
fsState: BuildFSState,
|
||||
jpsModel: JpsModel,
|
||||
moduleTarget: BazelModuleBuildTarget,
|
||||
relativizer: PathRelativizerService,
|
||||
): ProjectDescriptor {
|
||||
val dataPaths = BuildDataPathsImpl(dataStorageRoot)
|
||||
val dataManager = BuildDataManager.createSingleDb(
|
||||
/* dataPaths = */ dataPaths,
|
||||
/* targetStateManager = */ BazelBuildTargetStateManager(loadTargetState(storageManager)),
|
||||
/* relativizer = */ relativizer,
|
||||
/* versionManager = */ NoopBuildDataVersionManager,
|
||||
/* storageManager = */ storageManager,
|
||||
)
|
||||
return ProjectDescriptor(
|
||||
/* model = */ jpsModel,
|
||||
/* fsState = */ fsState,
|
||||
/* dataManager = */ dataManager,
|
||||
/* loggingManager = */ BuildLoggingManager.DEFAULT,
|
||||
/* moduleExcludeIndex = */ NoopModuleExcludeIndex,
|
||||
/* buildTargetIndex = */ BazelBuildTargetIndex(moduleTarget),
|
||||
/* buildRootIndex = */ BazelBuildRootIndex(moduleTarget),
|
||||
/* ignoredFileIndex = */ NoopIgnoredFileIndex,
|
||||
)
|
||||
}
|
||||
|
||||
private object NoopIgnoredFileIndex : IgnoredFileIndex {
|
||||
override fun isIgnored(path: String) = false
|
||||
}
|
||||
|
||||
private object NoopBuildDataVersionManager : BuildDataVersionManager {
|
||||
override fun versionDiffers() = false
|
||||
|
||||
override fun saveVersion() {
|
||||
}
|
||||
}
|
||||
|
||||
private class BazelBuildTargetIndex(moduleTarget: ModuleBuildTarget) : BuildTargetIndex {
|
||||
private var targetChunks = java.util.List.of(BuildTargetChunk(java.util.Set.of(moduleTarget)))
|
||||
private var targets = java.util.List.of(moduleTarget)
|
||||
|
||||
override fun getSortedTargetChunks(context: CompileContext): List<BuildTargetChunk> = targetChunks
|
||||
|
||||
override fun isDummy(target: BuildTarget<*>) = false
|
||||
|
||||
@Suppress("OVERRIDE_DEPRECATION", "removal")
|
||||
override fun getDependenciesRecursively(target: BuildTarget<*>, context: CompileContext): Set<BuildTarget<*>> = emptySet()
|
||||
|
||||
override fun getDependencies(target: BuildTarget<*>, context: CompileContext): Collection<BuildTarget<*>> = emptyList()
|
||||
|
||||
override fun getModuleBasedTargets(module: JpsModule, selector: ModuleTargetSelector): List<ModuleBuildTarget> = targets
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : BuildTarget<*>> getAllTargets(type: BuildTargetType<T>) = targets as List<T>
|
||||
|
||||
override fun getAllTargets(): List<ModuleBuildTarget> = targets
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
package org.jetbrains.bazel.jvm.jps.impl
|
||||
|
||||
import org.jetbrains.jps.incremental.relativizer.PathRelativizer
|
||||
import org.jetbrains.jps.incremental.relativizer.PathRelativizerService
|
||||
import org.jetbrains.jps.incremental.storage.PathTypeAwareRelativizer
|
||||
import org.jetbrains.jps.incremental.storage.RelativePathType
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.invariantSeparatorsPathString
|
||||
|
||||
private const val BASE_ID_PREFIX = "#"
|
||||
private const val OUT_ID_PREFIX = "@"
|
||||
|
||||
internal fun createPathRelativizer(baseDir: Path, classOutDir: Path): PathRelativizerService {
|
||||
val baseDirPrefix = "${baseDir.invariantSeparatorsPathString}/"
|
||||
// Bazel may use paths with `../`
|
||||
val parentOfBaseDirPrefix = "${baseDir.parent.invariantSeparatorsPathString}/"
|
||||
val outBaseDirPrefix = "${classOutDir.invariantSeparatorsPathString}/"
|
||||
|
||||
val typeAwareRelativizer = object : PathTypeAwareRelativizer {
|
||||
override fun toRelative(path: String, type: RelativePathType): String {
|
||||
val p = path.replace(File.separatorChar, '/')
|
||||
when (type) {
|
||||
RelativePathType.SOURCE -> {
|
||||
return when {
|
||||
p.startsWith(baseDirPrefix) -> p.substring(baseDirPrefix.length)
|
||||
p.startsWith(parentOfBaseDirPrefix) -> "../" + p.substring(parentOfBaseDirPrefix.length)
|
||||
else -> error("Unexpected path: $p")
|
||||
}
|
||||
}
|
||||
RelativePathType.OUTPUT -> {
|
||||
require(p.startsWith(outBaseDirPrefix)) { "Unexpected path: $p" }
|
||||
return p.substring(outBaseDirPrefix.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toRelative(path: Path, type: RelativePathType): String {
|
||||
return toRelative(path.invariantSeparatorsPathString, type)
|
||||
}
|
||||
|
||||
override fun toAbsolute(path: String, type: RelativePathType): String {
|
||||
return when (type) {
|
||||
RelativePathType.SOURCE -> baseDirPrefix + path
|
||||
RelativePathType.OUTPUT -> outBaseDirPrefix + path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PathRelativizerService(arrayOf(object : PathRelativizer {
|
||||
override fun toRelativePath(path: String): String? {
|
||||
return when {
|
||||
path.startsWith(outBaseDirPrefix) -> OUT_ID_PREFIX + path.substring(outBaseDirPrefix.length)
|
||||
path.startsWith(baseDirPrefix) -> BASE_ID_PREFIX + path.substring(baseDirPrefix.length)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun toAbsolutePath(path: String): String? {
|
||||
return when {
|
||||
path.startsWith(OUT_ID_PREFIX) -> outBaseDirPrefix + path.substring(OUT_ID_PREFIX.length)
|
||||
path.startsWith(BASE_ID_PREFIX) -> baseDirPrefix + path.substring(BASE_ID_PREFIX.length)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}), typeAwareRelativizer)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
package org.jetbrains.bazel.jvm.jps
|
||||
|
||||
import com.intellij.openapi.diagnostic.DefaultLogger
|
||||
import com.intellij.openapi.diagnostic.LogLevel
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
|
||||
// write to System.err as Bazel expects for worker log
|
||||
internal class BazelLogger(private val category: String) : Logger() {
|
||||
private var level = LogLevel.DEBUG
|
||||
|
||||
override fun isDebugEnabled(): Boolean = level >= LogLevel.DEBUG
|
||||
|
||||
override fun isTraceEnabled(): Boolean = level >= LogLevel.TRACE
|
||||
|
||||
override fun trace(message: String) {
|
||||
if (isTraceEnabled()) {
|
||||
System.err.println("TRACE[$category]: $message")
|
||||
}
|
||||
}
|
||||
|
||||
override fun trace(t: Throwable?) {
|
||||
if (t != null && isTraceEnabled()) {
|
||||
System.err.println("TRACE[$category]: ")
|
||||
t.printStackTrace(System.err)
|
||||
}
|
||||
}
|
||||
|
||||
override fun debug(message: String, t: Throwable?) {
|
||||
doPrint(LogLevel.DEBUG, message, t)
|
||||
}
|
||||
|
||||
private fun doPrint(level: LogLevel, message: String, t: Throwable?) {
|
||||
if (this.level >= level) {
|
||||
return
|
||||
}
|
||||
|
||||
var text = "${level.name}[$category]: $message"
|
||||
if (t != null) {
|
||||
ensureNotControlFlow(t)
|
||||
text += " " + t.stackTraceToString()
|
||||
}
|
||||
System.err.println(text)
|
||||
}
|
||||
|
||||
override fun info(message: String, t: Throwable?) {
|
||||
doPrint(LogLevel.INFO, message, t)
|
||||
}
|
||||
|
||||
override fun warn(message: String, t: Throwable?) {
|
||||
doPrint(LogLevel.WARNING, message, t)
|
||||
}
|
||||
|
||||
override fun error(message: String, t: Throwable?, vararg details: String) {
|
||||
var t = t
|
||||
t = ensureNotControlFlow(t)
|
||||
System.err.println("ERROR: " + message + DefaultLogger.detailsToString(*details) + DefaultLogger.attachmentsToString(t))
|
||||
t?.printStackTrace(System.err)
|
||||
throw AssertionError(message, t)
|
||||
}
|
||||
|
||||
override fun setLevel(level: LogLevel) {
|
||||
this.level = level
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
load("@rules_java//java:defs.bzl", "java_binary")
|
||||
load("@rules_jvm//:jvm.bzl", "jvm_import")
|
||||
load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
|
||||
|
||||
# separate to avoid adding dependency on proguard and zip to JPS worker impl
|
||||
|
||||
kt_jvm_library(
|
||||
name = "packager",
|
||||
srcs = glob(["*.kt"]),
|
||||
kotlinc_opts = "//:rules_jvm_bootstrap_kotlinc_options",
|
||||
deps = [
|
||||
"@lib//:kotlin-stdlib",
|
||||
"//:kotlinx-coroutines-core",
|
||||
"@lib//:asm",
|
||||
"//zip:build-zip",
|
||||
"//src/jps-builder:jps-standalone",
|
||||
"//:kotlin-metadata",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("HardCodedStringLiteral")
|
||||
|
||||
package org.jetbrains.bazel.jvm.jps
|
||||
|
||||
import org.jetbrains.jps.incremental.MessageHandler
|
||||
import org.jetbrains.jps.incremental.messages.BuildMessage
|
||||
import org.jetbrains.jps.incremental.messages.CompilerMessage
|
||||
import java.io.Writer
|
||||
|
||||
class ConsoleMessageHandler(@PublishedApi @JvmField internal val out: Writer) : MessageHandler {
|
||||
private var hasErrors = false
|
||||
|
||||
fun warn(message: String) {
|
||||
out.appendLine("WARN: $message")
|
||||
}
|
||||
|
||||
fun error(message: String) {
|
||||
out.appendLine("ERROR: $message")
|
||||
}
|
||||
|
||||
fun info(message: String) {
|
||||
out.appendLine("INFO: $message")
|
||||
}
|
||||
|
||||
fun debug(message: String) {
|
||||
out.appendLine("DEBUG: $message")
|
||||
}
|
||||
|
||||
inline fun measureTime(label: String, block: () -> Unit) {
|
||||
val duration = kotlin.time.measureTime(block)
|
||||
out.appendLine("TIME: $label: $duration")
|
||||
}
|
||||
|
||||
override fun processMessage(message: BuildMessage) {
|
||||
val messageText = when (message) {
|
||||
is CompilerMessage -> {
|
||||
when {
|
||||
message.sourcePath == null -> message.messageText
|
||||
message.line < 0 -> message.sourcePath + ": " + message.messageText
|
||||
else -> message.sourcePath + "(" + message.line + ":" + message.column + "): " + message.messageText
|
||||
}
|
||||
}
|
||||
|
||||
else -> message.messageText
|
||||
}
|
||||
|
||||
if (messageText.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (message.kind == BuildMessage.Kind.ERROR) {
|
||||
out.appendLine("Error: $messageText")
|
||||
hasErrors = true
|
||||
}
|
||||
else if (message.kind !== BuildMessage.Kind.PROGRESS || !messageText.startsWith("Compiled") && !messageText.startsWith("Copying")) {
|
||||
out.appendLine(messageText)
|
||||
}
|
||||
}
|
||||
|
||||
fun hasErrors(): Boolean = hasErrors
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.jetbrains.bazel.jvm.jps
|
||||
|
||||
import org.jetbrains.org.objectweb.asm.AnnotationVisitor
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
|
||||
private const val KIND_FIELD_NAME = "k"
|
||||
private const val METADATA_EXTRA_INT_FIELD_NAME = "xi"
|
||||
private const val METADATA_VERSION_FIELD_NAME = "mv"
|
||||
private const val METADATA_DATA_FIELD_NAME = "d1"
|
||||
private const val METADATA_STRINGS_FIELD_NAME = "d2"
|
||||
private const val METADATA_EXTRA_STRING_FIELD_NAME = "xs"
|
||||
private const val METADATA_PACKAGE_NAME_FIELD_NAME = "pn"
|
||||
|
||||
internal class KotlinAnnotationVisitor(private val resultConsumer: (Metadata) -> Unit) : AnnotationVisitor(Opcodes.API_VERSION) {
|
||||
private var kind: Int = 1
|
||||
private var metadataVersion: IntArray = intArrayOf()
|
||||
private var data1: MutableList<String> = mutableListOf()
|
||||
private var data2: MutableList<String> = mutableListOf()
|
||||
private var extraString: String = ""
|
||||
private var packageName: String = ""
|
||||
private var extraInt: Int = 0
|
||||
|
||||
override fun visit(name: String, value: Any?) {
|
||||
when (name) {
|
||||
KIND_FIELD_NAME -> kind = value as Int
|
||||
METADATA_EXTRA_INT_FIELD_NAME -> extraInt = value as Int
|
||||
METADATA_VERSION_FIELD_NAME -> metadataVersion = value as IntArray
|
||||
METADATA_EXTRA_STRING_FIELD_NAME -> extraString = value as String
|
||||
METADATA_PACKAGE_NAME_FIELD_NAME -> packageName = value as String
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitArray(name: String): AnnotationVisitor? {
|
||||
val destination = when (name) {
|
||||
METADATA_DATA_FIELD_NAME -> data1
|
||||
METADATA_STRINGS_FIELD_NAME -> data2
|
||||
else -> return null
|
||||
}
|
||||
return object : AnnotationVisitor(Opcodes.API_VERSION) {
|
||||
override fun visit(name: String?, value: Any?) {
|
||||
destination.add(value as String)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitEnd() {
|
||||
resultConsumer(Metadata(
|
||||
kind = kind,
|
||||
metadataVersion = metadataVersion,
|
||||
data1 = data1.toTypedArray(),
|
||||
data2 = data2.toTypedArray(),
|
||||
extraString = extraString,
|
||||
packageName = packageName,
|
||||
extraInt = extraInt,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a KotlinClassHeader to an existing Kotlin Metadata annotation visitor.
|
||||
*/
|
||||
internal fun visitKotlinMetadata(annotationVisitor: AnnotationVisitor, header: Metadata) {
|
||||
annotationVisitor.visit(KIND_FIELD_NAME, header.kind)
|
||||
annotationVisitor.visit(METADATA_VERSION_FIELD_NAME, header.metadataVersion)
|
||||
|
||||
if (header.data1.isNotEmpty()) {
|
||||
val arrayVisitor = annotationVisitor.visitArray(METADATA_DATA_FIELD_NAME)
|
||||
for (v in header.data1) {
|
||||
arrayVisitor.visit(null, v)
|
||||
}
|
||||
arrayVisitor.visitEnd()
|
||||
}
|
||||
|
||||
if (header.data2.isNotEmpty()) {
|
||||
val arrayVisitor = annotationVisitor.visitArray(METADATA_STRINGS_FIELD_NAME)
|
||||
for (v in header.data2) {
|
||||
arrayVisitor.visit(null, v)
|
||||
}
|
||||
arrayVisitor.visitEnd()
|
||||
}
|
||||
|
||||
if (header.extraString.isNotEmpty()) {
|
||||
annotationVisitor.visit(METADATA_EXTRA_STRING_FIELD_NAME, header.extraString)
|
||||
}
|
||||
|
||||
if (header.packageName.isNotEmpty()) {
|
||||
annotationVisitor.visit(METADATA_PACKAGE_NAME_FIELD_NAME, header.packageName)
|
||||
}
|
||||
|
||||
if (header.extraInt != 0) {
|
||||
annotationVisitor.visit(METADATA_EXTRA_INT_FIELD_NAME, header.extraInt)
|
||||
}
|
||||
|
||||
annotationVisitor.visitEnd()
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
package org.jetbrains.bazel.jvm.jps
|
||||
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
import org.jetbrains.org.objectweb.asm.tree.AnnotationNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.FieldNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
import kotlin.metadata.*
|
||||
import kotlin.metadata.jvm.KotlinClassMetadata
|
||||
import kotlin.metadata.jvm.getterSignature
|
||||
import kotlin.metadata.jvm.localDelegatedProperties
|
||||
import kotlin.metadata.jvm.signature
|
||||
|
||||
/**
|
||||
* ClassVisitor that strips non-public methods/fields and Kotlin `internal` methods.
|
||||
*/
|
||||
internal class AbiClassVisitor(
|
||||
classVisitor: ClassVisitor,
|
||||
private val classesToBeDeleted: MutableSet<String>,
|
||||
) : ClassVisitor(Opcodes.API_VERSION, classVisitor) {
|
||||
// tracks if this class has any public API members
|
||||
var isApiClass: Boolean = false
|
||||
private set
|
||||
|
||||
private val classAnnotations = mutableListOf<Pair<AnnotationNode, Boolean>>()
|
||||
private val fields = mutableListOf<FieldNode>()
|
||||
private val methods = mutableListOf<MethodNode>()
|
||||
private var kotlinMetadata: Pair<String, KotlinClassMetadata>? = null
|
||||
|
||||
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
|
||||
// parse @Metadata for Kotlin-specific visibility information
|
||||
@Suppress("SpellCheckingInspection")
|
||||
if (descriptor == "Lkotlin/Metadata;") {
|
||||
return KotlinAnnotationVisitor {
|
||||
kotlinMetadata = descriptor to KotlinClassMetadata.readStrict(it)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val annotationNode = AnnotationNode(api, descriptor)
|
||||
classAnnotations.add(annotationNode to visible)
|
||||
return annotationNode
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String?, interfaces: Array<String>?) {
|
||||
if (access and Opcodes.ACC_PUBLIC == 0) {
|
||||
classesToBeDeleted.add(name)
|
||||
}
|
||||
else {
|
||||
isApiClass = true
|
||||
super.visit(version, access, name, signature, superName, interfaces)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitField(
|
||||
access: Int,
|
||||
name: String?,
|
||||
descriptor: String?,
|
||||
signature: String?,
|
||||
value: Any?,
|
||||
): FieldVisitor? {
|
||||
if (access and Opcodes.ACC_PUBLIC == 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
val fieldNode = FieldNode(api, access, name, descriptor, signature, value)
|
||||
fields.add(fieldNode)
|
||||
return fieldNode
|
||||
}
|
||||
|
||||
override fun visitMethod(
|
||||
access: Int,
|
||||
name: String,
|
||||
descriptor: String?,
|
||||
signature: String?,
|
||||
exceptions: Array<String>?,
|
||||
): MethodVisitor? {
|
||||
// retain public methods and exclude Kotlin `internal` methods
|
||||
val isPublic = access and Opcodes.ACC_PUBLIC != 0
|
||||
val isInternal = isKotlinInternal(name)
|
||||
if (!isPublic || isInternal) {
|
||||
return null
|
||||
}
|
||||
|
||||
val methodNode = MethodNode(api, access, name, descriptor, signature, exceptions)
|
||||
methods.add(methodNode)
|
||||
return methodNode
|
||||
}
|
||||
|
||||
override fun visitEnd() {
|
||||
classAnnotations.sortBy { it.first.desc }
|
||||
fields.sortBy { it.name }
|
||||
methods.sortBy { it.name }
|
||||
|
||||
kotlinMetadata?.let { (descriptor, header) ->
|
||||
transformAndWriteKotlinMetadata(
|
||||
metadata = header,
|
||||
descriptor = descriptor,
|
||||
classVisitor = cv,
|
||||
classesToBeDeleted = classesToBeDeleted,
|
||||
)
|
||||
}
|
||||
|
||||
for ((annotation, visible) in classAnnotations) {
|
||||
val annotationVisitor = cv.visitAnnotation(annotation.desc, visible)
|
||||
annotation.accept(annotationVisitor)
|
||||
}
|
||||
|
||||
for (field in fields) {
|
||||
field.accept(cv)
|
||||
}
|
||||
|
||||
for (method in methods) {
|
||||
//val exceptionsArray = if (method.exceptions == null) null else method.exceptions.toTypedArray()
|
||||
//val methodVisitor = cv.visitMethod(method.access, method.name, method.desc, method.signature, exceptionsArray)
|
||||
//method.accept(methodVisitor)
|
||||
|
||||
val mv = cv.visitMethod(method.access, method.name, method.desc, method.signature, method.exceptions?.toTypedArray())
|
||||
val stripper = MethodBodyStripper(mv)
|
||||
stripper.visitCode()
|
||||
stripper.visitMaxs(0, 0)
|
||||
stripper.visitEnd()
|
||||
}
|
||||
|
||||
super.visitEnd()
|
||||
}
|
||||
|
||||
private fun isKotlinInternal(methodName: String?): Boolean {
|
||||
val kClass = (kotlinMetadata?.second as? KotlinClassMetadata.Class)?.kmClass ?: return false
|
||||
for (function in kClass.functions) {
|
||||
if (function.visibility == Visibility.INTERNAL && function.name == methodName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private class MethodBodyStripper(mv: MethodVisitor) : MethodVisitor(Opcodes.API_VERSION, mv) {
|
||||
override fun visitCode() {
|
||||
super.visitCode()
|
||||
mv.visitInsn(Opcodes.RETURN)
|
||||
}
|
||||
|
||||
override fun visitMaxs(maxStack: Int, maxLocals: Int) {
|
||||
// indicate no stack and locals required for stripped body
|
||||
super.visitMaxs(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun transformAndWriteKotlinMetadata(
|
||||
metadata: KotlinClassMetadata,
|
||||
descriptor: String,
|
||||
classVisitor: ClassVisitor,
|
||||
classesToBeDeleted: Set<String>,
|
||||
) {
|
||||
val treatInternalAsPrivate = false
|
||||
when (metadata) {
|
||||
is KotlinClassMetadata.Class -> {
|
||||
removePrivateDeclarationsForClass(
|
||||
klass = metadata.kmClass,
|
||||
removeCopyAlongWithConstructor = false,
|
||||
preserveDeclarationOrder = false,
|
||||
classesToBeDeleted = classesToBeDeleted,
|
||||
// todo try to set to true
|
||||
pruneClass = false,
|
||||
treatInternalAsPrivate = treatInternalAsPrivate,
|
||||
)
|
||||
}
|
||||
|
||||
is KotlinClassMetadata.FileFacade -> {
|
||||
removePrivateDeclarationsForPackage(
|
||||
kmPackage = metadata.kmPackage,
|
||||
preserveDeclarationOrder = false,
|
||||
pruneClass = false,
|
||||
treatInternalAsPrivate = treatInternalAsPrivate,
|
||||
)
|
||||
}
|
||||
|
||||
is KotlinClassMetadata.MultiFileClassPart -> {
|
||||
removePrivateDeclarationsForPackage(
|
||||
kmPackage = metadata.kmPackage,
|
||||
preserveDeclarationOrder = false,
|
||||
pruneClass = false,
|
||||
treatInternalAsPrivate = treatInternalAsPrivate,
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
|
||||
val annotationVisitor = classVisitor.visitAnnotation(descriptor, true)
|
||||
visitKotlinMetadata(annotationVisitor, metadata.write())
|
||||
}
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
private fun removePrivateDeclarationsForPackage(
|
||||
kmPackage: KmPackage,
|
||||
preserveDeclarationOrder: Boolean,
|
||||
pruneClass: Boolean,
|
||||
treatInternalAsPrivate: Boolean,
|
||||
) {
|
||||
removePrivateDeclarationsForDeclarationContainer(
|
||||
container = kmPackage as KmDeclarationContainer,
|
||||
copyFunShouldBeDeleted = false,
|
||||
preserveDeclarationOrder = preserveDeclarationOrder,
|
||||
pruneClass = pruneClass,
|
||||
treatInternalAsPrivate = treatInternalAsPrivate,
|
||||
)
|
||||
kmPackage.localDelegatedProperties.clear()
|
||||
}
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
private fun removePrivateDeclarationsForClass(
|
||||
klass: KmClass,
|
||||
removeCopyAlongWithConstructor: Boolean,
|
||||
preserveDeclarationOrder: Boolean,
|
||||
classesToBeDeleted: Set<String>,
|
||||
pruneClass: Boolean,
|
||||
treatInternalAsPrivate: Boolean,
|
||||
) {
|
||||
klass.constructors.removeIf { pruneClass || it.visibility.shouldRemove(treatInternalAsPrivate) }
|
||||
removePrivateDeclarationsForDeclarationContainer(
|
||||
container = klass as KmDeclarationContainer,
|
||||
copyFunShouldBeDeleted = klass.copyFunShouldBeDeleted(removeDataClassCopy = removeCopyAlongWithConstructor),
|
||||
preserveDeclarationOrder = preserveDeclarationOrder,
|
||||
pruneClass = pruneClass,
|
||||
treatInternalAsPrivate = treatInternalAsPrivate,
|
||||
)
|
||||
klass.nestedClasses.removeIf { "${klass.name}$$it" in classesToBeDeleted }
|
||||
klass.companionObject = klass.companionObject.takeUnless { "${klass.name}$$it" in classesToBeDeleted }
|
||||
klass.localDelegatedProperties.clear()
|
||||
}
|
||||
|
||||
private fun KmClass.copyFunShouldBeDeleted(removeDataClassCopy: Boolean): Boolean {
|
||||
return removeDataClassCopy && isData && constructors.none { !it.isSecondary }
|
||||
}
|
||||
|
||||
private fun Visibility.shouldRemove(treatInternalAsPrivate: Boolean): Boolean {
|
||||
return this == Visibility.PRIVATE || this == Visibility.PRIVATE_TO_THIS || this == Visibility.LOCAL || (treatInternalAsPrivate && this == Visibility.INTERNAL)
|
||||
}
|
||||
|
||||
private fun removePrivateDeclarationsForDeclarationContainer(
|
||||
container: KmDeclarationContainer,
|
||||
copyFunShouldBeDeleted: Boolean,
|
||||
preserveDeclarationOrder: Boolean,
|
||||
pruneClass: Boolean,
|
||||
treatInternalAsPrivate: Boolean,
|
||||
) {
|
||||
container.functions.removeIf {
|
||||
pruneClass || it.visibility.shouldRemove(treatInternalAsPrivate) || (copyFunShouldBeDeleted && it.name == "copy")
|
||||
}
|
||||
container.properties.removeIf {
|
||||
pruneClass || it.visibility.shouldRemove(treatInternalAsPrivate)
|
||||
}
|
||||
|
||||
if (!preserveDeclarationOrder) {
|
||||
container.functions.sortWith(compareBy(KmFunction::name, { it.signature.toString() }))
|
||||
container.properties.sortWith(compareBy(KmProperty::name, { it.getterSignature.toString() }))
|
||||
}
|
||||
|
||||
for (property in container.properties) {
|
||||
// whether the *non-const* property is initialized by a compile-time constant is not a part of the ABI.
|
||||
if (!property.isConst) {
|
||||
property.hasConstant = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
package org.jetbrains.bazel.jvm.jps
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.intellij.build.io.*
|
||||
import org.jetbrains.jps.incremental.storage.ExperimentalSourceToOutputMapping
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.ClassWriter
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.FileChannel
|
||||
import java.nio.file.*
|
||||
import java.nio.file.attribute.DosFileAttributeView
|
||||
import java.nio.file.attribute.PosixFileAttributeView
|
||||
import java.nio.file.attribute.PosixFilePermission
|
||||
import java.util.zip.ZipEntry
|
||||
|
||||
|
||||
suspend fun packageToJar(
|
||||
outJar: Path,
|
||||
abiJar: Path?,
|
||||
sourceToOutputMap: ExperimentalSourceToOutputMapping,
|
||||
classOutDir: Path,
|
||||
messageHandler: ConsoleMessageHandler
|
||||
) {
|
||||
if (abiJar == null) {
|
||||
withContext(Dispatchers.IO) {
|
||||
createJar(
|
||||
outJar = outJar,
|
||||
sourceToOutputMap = sourceToOutputMap,
|
||||
classOutDir = classOutDir,
|
||||
abiChannel = null,
|
||||
messageHandler = messageHandler,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val classChannel = Channel<Pair<ByteArray, ByteArray>>(capacity = 8)
|
||||
withContext(Dispatchers.IO) {
|
||||
launch {
|
||||
createJar(
|
||||
outJar = outJar,
|
||||
sourceToOutputMap = sourceToOutputMap,
|
||||
classOutDir = classOutDir,
|
||||
abiChannel = classChannel,
|
||||
messageHandler = messageHandler,
|
||||
)
|
||||
classChannel.close()
|
||||
}
|
||||
|
||||
writeZipUsingTempFile(abiJar, indexWriter = null) { stream ->
|
||||
val classesToBeDeleted = HashSet<String>()
|
||||
for ((name, classData) in classChannel) {
|
||||
val classWriter = ClassWriter(0)
|
||||
val abiClassVisitor = AbiClassVisitor(classVisitor = classWriter, classesToBeDeleted = classesToBeDeleted)
|
||||
ClassReader(classData).accept(abiClassVisitor, 0)
|
||||
if (!abiClassVisitor.isApiClass) {
|
||||
continue
|
||||
}
|
||||
|
||||
val abiData = classWriter.toByteArray()
|
||||
stream.writeDataRawEntry(ByteBuffer.wrap(abiData), name, abiData.size, abiData.size, ZipEntry.STORED, 0)
|
||||
}
|
||||
|
||||
if (classesToBeDeleted.isNotEmpty()) {
|
||||
messageHandler.out.appendLine("Classes to be deleted: ${classesToBeDeleted.size}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createJar(
|
||||
outJar: Path,
|
||||
sourceToOutputMap: ExperimentalSourceToOutputMapping,
|
||||
classOutDir: Path,
|
||||
abiChannel: Channel<Pair<ByteArray, ByteArray>>?,
|
||||
messageHandler: ConsoleMessageHandler,
|
||||
) {
|
||||
val packageIndexBuilder = PackageIndexBuilder()
|
||||
writeZipUsingTempFile(outJar, packageIndexBuilder.indexWriter) { stream ->
|
||||
// MVStore like a TreeMap, keys already sorted
|
||||
//val all = sourceToOutputMap.outputs().toList()
|
||||
//val unique = LinkedHashSet(all)
|
||||
//if (unique.size != all.size) {
|
||||
// messageHandler.out.appendLine("Duplicated outputs: ${all.groupingBy { it }
|
||||
// .eachCount()
|
||||
// .filter { it.value > 1 }
|
||||
// .keys
|
||||
// }")
|
||||
//}
|
||||
|
||||
for (path in sourceToOutputMap.outputs().toList()) {
|
||||
// duplicated - ignore it
|
||||
if (path.endsWith(".kotlin_module")) {
|
||||
continue
|
||||
}
|
||||
|
||||
packageIndexBuilder.addFile(name = path, addClassDir = false)
|
||||
try {
|
||||
val file = classOutDir.resolve(path)
|
||||
if (abiChannel != null && path.endsWith(".class")) {
|
||||
val name = path.toByteArray()
|
||||
val classData = stream.fileAndGetData(name, file)
|
||||
abiChannel.send(name to classData)
|
||||
}
|
||||
else {
|
||||
stream.file(nameString = path, file = file)
|
||||
}
|
||||
}
|
||||
catch (_: NoSuchFileException) {
|
||||
messageHandler.warn("output file exists in src-to-output mapping, but not found on disk: $path")
|
||||
}
|
||||
}
|
||||
packageIndexBuilder.writePackageIndex(stream = stream, addDirEntriesMode = AddDirEntriesMode.RESOURCE_ONLY)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun writeZipUsingTempFile(file: Path, indexWriter: IkvIndexBuilder?, task: (ZipArchiveOutputStream) -> Unit) {
|
||||
val tempFile = Files.createTempFile(file.parent, file.fileName.toString(), ".tmp")
|
||||
var moved = false
|
||||
try {
|
||||
ZipArchiveOutputStream(
|
||||
channel = FileChannel.open(tempFile, WRITE),
|
||||
zipIndexWriter = ZipIndexWriter(indexWriter),
|
||||
).use {
|
||||
task(it)
|
||||
}
|
||||
|
||||
try {
|
||||
moveAtomic(tempFile, file)
|
||||
}
|
||||
catch (e: AccessDeniedException) {
|
||||
makeFileWritable(file, e)
|
||||
moveAtomic(tempFile, file)
|
||||
}
|
||||
moved = true
|
||||
}
|
||||
finally {
|
||||
if (!moved) {
|
||||
Files.deleteIfExists(tempFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun moveAtomic(from: Path, to: Path) {
|
||||
try {
|
||||
Files.move(from, to, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE)
|
||||
}
|
||||
catch (_: AtomicMoveNotSupportedException) {
|
||||
Files.move(from, to, StandardCopyOption.REPLACE_EXISTING)
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeFileWritable(file: Path, cause: Throwable) {
|
||||
val posixView = Files.getFileAttributeView<PosixFileAttributeView?>(file, PosixFileAttributeView::class.java)
|
||||
if (posixView != null) {
|
||||
val permissions = posixView.readAttributes().permissions()
|
||||
permissions.add(PosixFilePermission.OWNER_WRITE)
|
||||
posixView.setPermissions(permissions)
|
||||
}
|
||||
|
||||
val dosView = Files.getFileAttributeView<DosFileAttributeView?>(file, DosFileAttributeView::class.java)
|
||||
@Suppress("IfThenToSafeAccess")
|
||||
if (dosView != null) {
|
||||
dosView.setReadOnly(false)
|
||||
}
|
||||
|
||||
throw UnsupportedOperationException("Unable to modify file attributes. Unsupported platform.", cause)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.jetbrains.bazel.jvm.jps.state
|
||||
|
||||
internal enum class TargetConfigurationDigestProperty(@JvmField val description: String) {
|
||||
COMPILER("kotlinc/javac configuration"),
|
||||
DEPENDENCY_PATH_LIST("dependency path list"),
|
||||
DEPENDENCY_DIGEST_LIST("dependency digest list");
|
||||
|
||||
companion object {
|
||||
val VERSION = versionDigest<TargetConfigurationDigestProperty>()
|
||||
}
|
||||
}
|
||||
|
||||
private fun emptyContainer(): LongArray {
|
||||
val list = LongArray(TargetConfigurationDigestProperty.entries.size + 1)
|
||||
list[list.lastIndex] = TargetConfigurationDigestProperty.VERSION
|
||||
return list
|
||||
}
|
||||
|
||||
@JvmInline
|
||||
internal value class TargetConfigurationDigestContainer(
|
||||
private val list: LongArray = emptyContainer(),
|
||||
) {
|
||||
fun get(kind: TargetConfigurationDigestProperty): Long = list[kind.ordinal]
|
||||
|
||||
fun set(kind: TargetConfigurationDigestProperty, hash: Long) {
|
||||
list[kind.ordinal] = hash
|
||||
}
|
||||
|
||||
fun asArray(): LongArray = list.copyOf()
|
||||
|
||||
val version: Long
|
||||
get() = list.last()
|
||||
|
||||
val rawSize: Int
|
||||
get() = list.size
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.bazel.jvm.jps.state
|
||||
|
||||
import com.dynatrace.hash4j.hashing.HashFunnel
|
||||
import com.dynatrace.hash4j.hashing.Hashing
|
||||
|
||||
internal inline fun <reified T : Enum<T>> hashFunnel(): HashFunnel<T> {
|
||||
return HashFunnel { obj, sink ->
|
||||
sink.putInt(obj.ordinal)
|
||||
sink.putString(obj.name)
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <reified T : Enum<T>> versionDigest(): Long {
|
||||
return Hashing.xxh3_64().hashStream().putOrderedIterable(enumValues<T>().asIterable(), hashFunnel<T>()).asLong
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.jetbrains.bazel.jvm.jps.state
|
||||
|
||||
internal enum class TargetStateProperty {
|
||||
AverageBuildTime,
|
||||
LastSuccessfulRebuildDuration,
|
||||
;
|
||||
|
||||
companion object {
|
||||
val VERSION = versionDigest<TargetStateProperty>()
|
||||
}
|
||||
}
|
||||
|
||||
private fun emptyContainer(): LongArray {
|
||||
val list = LongArray(TargetStateProperty.entries.size + 1) { -1 }
|
||||
list[list.lastIndex] = TargetStateProperty.VERSION
|
||||
return list
|
||||
}
|
||||
|
||||
@JvmInline
|
||||
internal value class TargetStateContainer(private val list: LongArray = emptyContainer()) {
|
||||
fun get(kind: TargetStateProperty): Long = list[kind.ordinal]
|
||||
|
||||
fun set(kind: TargetStateProperty, value: Long) {
|
||||
list[kind.ordinal] = value
|
||||
}
|
||||
|
||||
fun asArray(): LongArray = list.copyOf()
|
||||
|
||||
val isCorrect: Boolean
|
||||
get() = list.size == (TargetStateProperty.entries.size + 1) && list.last() == TargetStateProperty.VERSION
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("UnstableApiUsage", "ReplaceGetOrSet")
|
||||
|
||||
package org.jetbrains.bazel.jvm.jps.state
|
||||
|
||||
import org.h2.mvstore.MVMap
|
||||
import org.h2.mvstore.MVStore
|
||||
import org.h2.mvstore.type.LongDataType
|
||||
import org.jetbrains.bazel.jvm.jps.impl.BazelBuildTargetStateManager
|
||||
import org.jetbrains.jps.incremental.storage.StorageManager
|
||||
import org.jetbrains.jps.incremental.storage.dataTypes.LongListKeyDataType
|
||||
|
||||
private const val targetStateMapName = "target-state"
|
||||
|
||||
private const val DIGEST_LIST_KEY = 1L
|
||||
private const val DURATION_KEY = 2L
|
||||
|
||||
private val targetStateMapBuilder = run {
|
||||
val mapBuilder = MVMap.Builder<Long, LongArray>()
|
||||
mapBuilder.setKeyType(LongDataType.INSTANCE)
|
||||
mapBuilder.setValueType(LongListKeyDataType)
|
||||
mapBuilder
|
||||
}
|
||||
|
||||
// returns a reason to force rebuild
|
||||
internal fun checkConfiguration(
|
||||
store: MVStore,
|
||||
targetDigests: TargetConfigurationDigestContainer,
|
||||
): String? {
|
||||
val map = try {
|
||||
store.openMap(targetStateMapName, targetStateMapBuilder)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
return "Cannot open map $targetStateMapName: ${e.stackTraceToString()}"
|
||||
}
|
||||
|
||||
val storedDigest = map.get(DIGEST_LIST_KEY)?.let { TargetConfigurationDigestContainer(it) }
|
||||
when {
|
||||
storedDigest == null -> {
|
||||
return "Configuration digest not found"
|
||||
}
|
||||
storedDigest.rawSize != targetDigests.rawSize -> {
|
||||
return "Configuration digest size mismatch: expected ${targetDigests.rawSize}, got ${storedDigest.rawSize}"
|
||||
}
|
||||
storedDigest.version != TargetConfigurationDigestProperty.VERSION -> {
|
||||
return "Configuration digest format version mismatch: expected ${TargetConfigurationDigestProperty.VERSION}, got ${storedDigest.version}"
|
||||
}
|
||||
}
|
||||
|
||||
for (kind in TargetConfigurationDigestProperty.entries) {
|
||||
val storedHash = storedDigest.get(kind)
|
||||
val hash = targetDigests.get(kind)
|
||||
if (hash != storedHash) {
|
||||
return "Configuration digest mismatch (${kind.description}): expected $hash, got $storedHash"
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
internal fun saveTargetState(
|
||||
targetDigests: TargetConfigurationDigestContainer,
|
||||
manager: BazelBuildTargetStateManager,
|
||||
storageManager: StorageManager
|
||||
) {
|
||||
val map = storageManager.openMap(targetStateMapName, targetStateMapBuilder).map
|
||||
map.operate(DIGEST_LIST_KEY, targetDigests.asArray(), PutIfChanged)
|
||||
map.operate(DURATION_KEY, manager.state.asArray(), PutIfChanged)
|
||||
}
|
||||
|
||||
private object PutIfChanged : MVMap.DecisionMaker<LongArray>() {
|
||||
override fun decide(existingValue: LongArray?, providedValue: LongArray): MVMap.Decision {
|
||||
return if (providedValue.contentEquals(existingValue)) MVMap.Decision.ABORT else MVMap.Decision.PUT
|
||||
}
|
||||
}
|
||||
|
||||
internal fun loadTargetState(storageManager: StorageManager): TargetStateContainer {
|
||||
val map = storageManager.openMap(targetStateMapName, targetStateMapBuilder).map
|
||||
// copyOf - do not mutate data in store directly
|
||||
return map.get(DURATION_KEY)?.let { TargetStateContainer(it.copyOf()) }?.takeIf { it.isCorrect } ?: TargetStateContainer()
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.bazel.jvm.kotlin
|
||||
|
||||
import com.google.devtools.build.lib.worker.WorkerProtocol
|
||||
import org.jetbrains.bazel.jvm.WorkRequest
|
||||
import org.jetbrains.bazel.jvm.WorkRequestExecutor
|
||||
import org.jetbrains.bazel.jvm.processRequests
|
||||
import java.io.Writer
|
||||
@@ -14,12 +14,12 @@ object KotlinBuildWorker : WorkRequestExecutor {
|
||||
processRequests(startupArgs, this, debugLogClassifier = "kotlin")
|
||||
}
|
||||
|
||||
override suspend fun execute(request: WorkerProtocol.WorkRequest, writer: Writer, baseDir: Path): Int {
|
||||
val sources = request.inputsList.asSequence()
|
||||
override suspend fun execute(request: WorkRequest, writer: Writer, baseDir: Path): Int {
|
||||
val sources = request.inputs.asSequence()
|
||||
.filter { it.path.endsWith(".kt") || it.path.endsWith(".java") }
|
||||
.map { baseDir.resolve(it.path) }
|
||||
.map { baseDir.resolve(it.path).normalize() }
|
||||
.toList()
|
||||
return buildKotlin(workingDir = baseDir, args = parseArgs(request.argumentsList), out = writer, sources = sources)
|
||||
return buildKotlin(workingDir = baseDir, args = parseArgs(request.arguments), out = writer, sources = sources)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ internal suspend fun compileKotlinForJvm(
|
||||
kotlinArgs.classpath = createClasspath(args, workingDir)
|
||||
|
||||
kotlinArgs.moduleName = info.moduleName
|
||||
kotlinArgs.destination = workingDir.resolve(args.mandatorySingle(JvmBuilderFlags.OUTPUT)).toString()
|
||||
kotlinArgs.destination = workingDir.resolve(args.mandatorySingle(JvmBuilderFlags.OUT)).toString()
|
||||
|
||||
val pluginConfigurations = configurePlugins(args = args, workingDir = workingDir, label = info.label)
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ internal fun configurePlugins(
|
||||
}
|
||||
}
|
||||
|
||||
args.optionalSingle(JvmBuilderFlags.KOTLIN_OUTPUT_JDEPS)?.let { workingDir.resolve(it) }?.let { jdeps ->
|
||||
args.optionalSingle(JvmBuilderFlags.JDEPS_OUT)?.let { workingDir.resolve(it) }?.let { jdeps ->
|
||||
val options = mutableListOf(
|
||||
cliOptionValue("output", jdeps.toString()),
|
||||
cliOptionValue("target_label", label),
|
||||
@@ -101,7 +101,7 @@ internal fun configurePlugins(
|
||||
))
|
||||
}
|
||||
|
||||
args.optionalSingle(JvmBuilderFlags.ABI_JAR)?.let { workingDir.resolve(it) }?.let { abiJar ->
|
||||
args.optionalSingle(JvmBuilderFlags.ABI_OUT)?.let { workingDir.resolve(it) }?.let { abiJar ->
|
||||
addPlugin(RegisteredPluginInfo(
|
||||
componentRegistrar = null,
|
||||
compilerPluginRegistrar = JvmAbiComponentRegistrar(),
|
||||
|
||||
@@ -5,218 +5,225 @@
|
||||
|
||||
package org.jetbrains.kotlin.jvm.abi
|
||||
|
||||
import kotlin.metadata.*
|
||||
import kotlin.metadata.jvm.*
|
||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.*
|
||||
import org.jetbrains.org.objectweb.asm.AnnotationVisitor
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import kotlin.metadata.*
|
||||
import kotlin.metadata.jvm.*
|
||||
|
||||
/**
|
||||
* Wrap the visitor for a Kotlin Metadata annotation to strip out private and local
|
||||
* functions, properties, and type aliases as well as local delegated properties.
|
||||
*/
|
||||
fun abiMetadataProcessor(
|
||||
annotationVisitor: AnnotationVisitor,
|
||||
removeDataClassCopyIfConstructorIsPrivate: Boolean,
|
||||
preserveDeclarationOrder: Boolean,
|
||||
classesToBeDeleted: Set<String>,
|
||||
pruneClass: Boolean,
|
||||
treatInternalAsPrivate: Boolean,
|
||||
): AnnotationVisitor =
|
||||
kotlinClassHeaderVisitor { header ->
|
||||
// kotlinx-metadata only supports writing Kotlin metadata of version >= 1.4, so we need to
|
||||
// update the metadata version if we encounter older metadata annotations.
|
||||
val metadataVersion = header.metadataVersion.takeIf { v ->
|
||||
val major = v.getOrNull(0) ?: 0
|
||||
val minor = v.getOrNull(1) ?: 0
|
||||
major > 1 || major == 1 && minor >= 4
|
||||
} ?: intArrayOf(1, 4)
|
||||
annotationVisitor: AnnotationVisitor,
|
||||
removeDataClassCopyIfConstructorIsPrivate: Boolean,
|
||||
preserveDeclarationOrder: Boolean,
|
||||
classesToBeDeleted: Set<String>,
|
||||
pruneClass: Boolean,
|
||||
treatInternalAsPrivate: Boolean,
|
||||
): AnnotationVisitor {
|
||||
return kotlinClassHeaderVisitor { header ->
|
||||
// kotlinx-metadata only supports writing Kotlin metadata of version >= 1.4, so we need to
|
||||
// update the metadata version if we encounter older metadata annotations.
|
||||
val metadataVersion = header.metadataVersion.takeIf { v ->
|
||||
val major = v.getOrNull(0) ?: 0
|
||||
val minor = v.getOrNull(1) ?: 0
|
||||
major > 1 || major == 1 && minor >= 4
|
||||
} ?: intArrayOf(1, 4)
|
||||
|
||||
val newHeader = runCatching {
|
||||
KotlinClassMetadata.transform(header) { metadata ->
|
||||
when (metadata) {
|
||||
is KotlinClassMetadata.Class -> {
|
||||
metadata.kmClass.removePrivateDeclarations(
|
||||
removeDataClassCopyIfConstructorIsPrivate,
|
||||
preserveDeclarationOrder,
|
||||
classesToBeDeleted,
|
||||
pruneClass,
|
||||
treatInternalAsPrivate,
|
||||
)
|
||||
}
|
||||
is KotlinClassMetadata.FileFacade -> {
|
||||
metadata.kmPackage.removePrivateDeclarations(preserveDeclarationOrder, pruneClass, treatInternalAsPrivate)
|
||||
}
|
||||
is KotlinClassMetadata.MultiFileClassPart -> {
|
||||
metadata.kmPackage.removePrivateDeclarations(preserveDeclarationOrder, pruneClass, treatInternalAsPrivate)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}.getOrElse { cause ->
|
||||
// TODO: maybe jvm-abi-gen should throw this exception by default, and not only in tests.
|
||||
if (System.getProperty("idea.is.unit.test").toBoolean()) {
|
||||
val actual = "${metadataVersion[0]}.${metadataVersion[1]}"
|
||||
val expected = JvmMetadataVersion.LATEST_STABLE_SUPPORTED.toString()
|
||||
throw AssertionError(
|
||||
"jvm-abi-gen can't process class file with the new metadata version because the version of kotlinx-metadata-jvm " +
|
||||
"it depends on is too old.\n" +
|
||||
"Class file has metadata version $actual, but default metadata version of kotlinx-metadata-jvm is " +
|
||||
"$expected, so it can process class files with metadata version up to +1 from that (because of " +
|
||||
"Kotlin/JVM's one-version forward compatibility policy).\n" +
|
||||
"To fix this error, ensure that jvm-abi-gen depends on the latest version of kotlinx-metadata-jvm.\n" +
|
||||
"If this happens during the update of the default language version in the project, make sure that " +
|
||||
"a version of kotlinx-metadata-jvm has been published that supports this version, and update " +
|
||||
"\"versions.kotlinx-metadata-jvm\" in `gradle/versions.properties`.",
|
||||
cause
|
||||
)
|
||||
}
|
||||
header
|
||||
val newHeader = runCatching {
|
||||
KotlinClassMetadata.transform(header) { metadata ->
|
||||
when (metadata) {
|
||||
is KotlinClassMetadata.Class -> {
|
||||
metadata.kmClass.removePrivateDeclarations(
|
||||
removeDataClassCopyIfConstructorIsPrivate,
|
||||
preserveDeclarationOrder,
|
||||
classesToBeDeleted,
|
||||
pruneClass,
|
||||
treatInternalAsPrivate,
|
||||
)
|
||||
}
|
||||
|
||||
is KotlinClassMetadata.FileFacade -> {
|
||||
metadata.kmPackage.removePrivateDeclarations(preserveDeclarationOrder, pruneClass, treatInternalAsPrivate)
|
||||
}
|
||||
|
||||
is KotlinClassMetadata.MultiFileClassPart -> {
|
||||
metadata.kmPackage.removePrivateDeclarations(preserveDeclarationOrder, pruneClass, treatInternalAsPrivate)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
// Write out the stripped annotation
|
||||
annotationVisitor.visitKotlinMetadata(newHeader)
|
||||
}
|
||||
}.getOrElse { cause ->
|
||||
// TODO: maybe jvm-abi-gen should throw this exception by default, and not only in tests.
|
||||
if (System.getProperty("idea.is.unit.test").toBoolean()) {
|
||||
val actual = "${metadataVersion[0]}.${metadataVersion[1]}"
|
||||
val expected = JvmMetadataVersion.LATEST_STABLE_SUPPORTED.toString()
|
||||
throw AssertionError(
|
||||
"jvm-abi-gen can't process class file with the new metadata version because the version of kotlinx-metadata-jvm " +
|
||||
"it depends on is too old.\n" +
|
||||
"Class file has metadata version $actual, but default metadata version of kotlinx-metadata-jvm is " +
|
||||
"$expected, so it can process class files with metadata version up to +1 from that (because of " +
|
||||
"Kotlin/JVM's one-version forward compatibility policy).\n" +
|
||||
"To fix this error, ensure that jvm-abi-gen depends on the latest version of kotlinx-metadata-jvm.\n" +
|
||||
"If this happens during the update of the default language version in the project, make sure that " +
|
||||
"a version of kotlinx-metadata-jvm has been published that supports this version, and update " +
|
||||
"\"versions.kotlinx-metadata-jvm\" in `gradle/versions.properties`.",
|
||||
cause
|
||||
)
|
||||
}
|
||||
header
|
||||
}
|
||||
|
||||
// Write out the stripped annotation
|
||||
annotationVisitor.visitKotlinMetadata(newHeader)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse a KotlinClassHeader from an existing Kotlin Metadata annotation visitor.
|
||||
*/
|
||||
private fun kotlinClassHeaderVisitor(body: (Metadata) -> Unit): AnnotationVisitor =
|
||||
object : AnnotationVisitor(Opcodes.API_VERSION) {
|
||||
var kind: Int = 1
|
||||
var metadataVersion: IntArray = intArrayOf()
|
||||
var data1: MutableList<String> = mutableListOf()
|
||||
var data2: MutableList<String> = mutableListOf()
|
||||
var extraString: String? = null
|
||||
var packageName: String? = null
|
||||
var extraInt: Int = 0
|
||||
private fun kotlinClassHeaderVisitor(body: (Metadata) -> Unit): AnnotationVisitor {
|
||||
return object : AnnotationVisitor(Opcodes.API_VERSION) {
|
||||
var kind: Int = 1
|
||||
var metadataVersion: IntArray = intArrayOf()
|
||||
var data1: MutableList<String> = mutableListOf()
|
||||
var data2: MutableList<String> = mutableListOf()
|
||||
var extraString: String? = null
|
||||
var packageName: String? = null
|
||||
var extraInt: Int = 0
|
||||
|
||||
override fun visit(name: String, value: Any?) {
|
||||
when (name) {
|
||||
KIND_FIELD_NAME -> kind = value as Int
|
||||
METADATA_EXTRA_INT_FIELD_NAME -> extraInt = value as Int
|
||||
METADATA_VERSION_FIELD_NAME -> metadataVersion = value as IntArray
|
||||
METADATA_EXTRA_STRING_FIELD_NAME -> extraString = value as String
|
||||
METADATA_PACKAGE_NAME_FIELD_NAME -> packageName = value as String
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitArray(name: String): AnnotationVisitor? {
|
||||
val destination = when (name) {
|
||||
METADATA_DATA_FIELD_NAME -> data1
|
||||
METADATA_STRINGS_FIELD_NAME -> data2
|
||||
else -> return null
|
||||
}
|
||||
return object : AnnotationVisitor(Opcodes.API_VERSION) {
|
||||
override fun visit(name: String?, value: Any?) {
|
||||
destination += value as String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitEnd() {
|
||||
body(
|
||||
Metadata(
|
||||
kind,
|
||||
metadataVersion,
|
||||
data1.toTypedArray(),
|
||||
data2.toTypedArray(),
|
||||
extraString,
|
||||
packageName,
|
||||
extraInt
|
||||
)
|
||||
)
|
||||
}
|
||||
override fun visit(name: String, value: Any?) {
|
||||
when (name) {
|
||||
KIND_FIELD_NAME -> kind = value as Int
|
||||
METADATA_EXTRA_INT_FIELD_NAME -> extraInt = value as Int
|
||||
METADATA_VERSION_FIELD_NAME -> metadataVersion = value as IntArray
|
||||
METADATA_EXTRA_STRING_FIELD_NAME -> extraString = value as String
|
||||
METADATA_PACKAGE_NAME_FIELD_NAME -> packageName = value as String
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitArray(name: String): AnnotationVisitor? {
|
||||
val destination = when (name) {
|
||||
METADATA_DATA_FIELD_NAME -> data1
|
||||
METADATA_STRINGS_FIELD_NAME -> data2
|
||||
else -> return null
|
||||
}
|
||||
return object : AnnotationVisitor(Opcodes.API_VERSION) {
|
||||
override fun visit(name: String?, value: Any?) {
|
||||
destination += value as String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitEnd() {
|
||||
body(
|
||||
Metadata(
|
||||
kind,
|
||||
metadataVersion,
|
||||
data1.toTypedArray(),
|
||||
data2.toTypedArray(),
|
||||
extraString,
|
||||
packageName,
|
||||
extraInt
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a KotlinClassHeader to an existing Kotlin Metadata annotation visitor.
|
||||
*/
|
||||
private fun AnnotationVisitor.visitKotlinMetadata(header: Metadata) {
|
||||
visit(KIND_FIELD_NAME, header.kind)
|
||||
visit(METADATA_VERSION_FIELD_NAME, header.metadataVersion)
|
||||
if (header.data1.isNotEmpty()) {
|
||||
visitArray(METADATA_DATA_FIELD_NAME).apply {
|
||||
header.data1.forEach { visit(null, it) }
|
||||
visitEnd()
|
||||
}
|
||||
visit(KIND_FIELD_NAME, header.kind)
|
||||
visit(METADATA_VERSION_FIELD_NAME, header.metadataVersion)
|
||||
if (header.data1.isNotEmpty()) {
|
||||
visitArray(METADATA_DATA_FIELD_NAME).apply {
|
||||
header.data1.forEach { visit(null, it) }
|
||||
visitEnd()
|
||||
}
|
||||
if (header.data2.isNotEmpty()) {
|
||||
visitArray(METADATA_STRINGS_FIELD_NAME).apply {
|
||||
header.data2.forEach { visit(null, it) }
|
||||
visitEnd()
|
||||
}
|
||||
}
|
||||
if (header.data2.isNotEmpty()) {
|
||||
visitArray(METADATA_STRINGS_FIELD_NAME).apply {
|
||||
header.data2.forEach { visit(null, it) }
|
||||
visitEnd()
|
||||
}
|
||||
if (header.extraString.isNotEmpty()) {
|
||||
visit(METADATA_EXTRA_STRING_FIELD_NAME, header.extraString)
|
||||
}
|
||||
if (header.packageName.isNotEmpty()) {
|
||||
visit(METADATA_PACKAGE_NAME_FIELD_NAME, header.packageName)
|
||||
}
|
||||
if (header.extraInt != 0) {
|
||||
visit(METADATA_EXTRA_INT_FIELD_NAME, header.extraInt)
|
||||
}
|
||||
visitEnd()
|
||||
}
|
||||
if (header.extraString.isNotEmpty()) {
|
||||
visit(METADATA_EXTRA_STRING_FIELD_NAME, header.extraString)
|
||||
}
|
||||
if (header.packageName.isNotEmpty()) {
|
||||
visit(METADATA_PACKAGE_NAME_FIELD_NAME, header.packageName)
|
||||
}
|
||||
if (header.extraInt != 0) {
|
||||
visit(METADATA_EXTRA_INT_FIELD_NAME, header.extraInt)
|
||||
}
|
||||
visitEnd()
|
||||
}
|
||||
|
||||
private fun KmClass.removePrivateDeclarations(
|
||||
removeCopyAlongWithConstructor: Boolean,
|
||||
preserveDeclarationOrder: Boolean,
|
||||
classesToBeDeleted: Set<String>,
|
||||
pruneClass: Boolean,
|
||||
treatInternalAsPrivate: Boolean,
|
||||
removeCopyAlongWithConstructor: Boolean,
|
||||
preserveDeclarationOrder: Boolean,
|
||||
classesToBeDeleted: Set<String>,
|
||||
pruneClass: Boolean,
|
||||
treatInternalAsPrivate: Boolean,
|
||||
) {
|
||||
constructors.removeIf { pruneClass || it.visibility.shouldRemove(treatInternalAsPrivate) }
|
||||
(this as KmDeclarationContainer).removePrivateDeclarations(
|
||||
copyFunShouldBeDeleted(removeCopyAlongWithConstructor),
|
||||
preserveDeclarationOrder,
|
||||
pruneClass,
|
||||
treatInternalAsPrivate,
|
||||
)
|
||||
nestedClasses.removeIf { "$name\$$it" in classesToBeDeleted }
|
||||
companionObject = companionObject.takeUnless { "$name\$$it" in classesToBeDeleted }
|
||||
localDelegatedProperties.clear()
|
||||
// TODO: do not serialize private type aliases once KT-17229 is fixed.
|
||||
constructors.removeIf { pruneClass || it.visibility.shouldRemove(treatInternalAsPrivate) }
|
||||
(this as KmDeclarationContainer).removePrivateDeclarations(
|
||||
copyFunShouldBeDeleted(removeCopyAlongWithConstructor),
|
||||
preserveDeclarationOrder,
|
||||
pruneClass,
|
||||
treatInternalAsPrivate,
|
||||
)
|
||||
nestedClasses.removeIf { "$name\$$it" in classesToBeDeleted }
|
||||
companionObject = companionObject.takeUnless { "$name\$$it" in classesToBeDeleted }
|
||||
localDelegatedProperties.clear()
|
||||
// TODO: do not serialize private type aliases once KT-17229 is fixed.
|
||||
}
|
||||
|
||||
private fun KmPackage.removePrivateDeclarations(
|
||||
preserveDeclarationOrder: Boolean,
|
||||
pruneClass: Boolean,
|
||||
treatInternalAsPrivate: Boolean,
|
||||
preserveDeclarationOrder: Boolean,
|
||||
pruneClass: Boolean,
|
||||
treatInternalAsPrivate: Boolean,
|
||||
) {
|
||||
(this as KmDeclarationContainer).removePrivateDeclarations(false, preserveDeclarationOrder, pruneClass, treatInternalAsPrivate)
|
||||
localDelegatedProperties.clear()
|
||||
// TODO: do not serialize private type aliases once KT-17229 is fixed.
|
||||
(this as KmDeclarationContainer).removePrivateDeclarations(false, preserveDeclarationOrder, pruneClass, treatInternalAsPrivate)
|
||||
localDelegatedProperties.clear()
|
||||
// TODO: do not serialize private type aliases once KT-17229 is fixed.
|
||||
}
|
||||
|
||||
private fun KmDeclarationContainer.removePrivateDeclarations(
|
||||
copyFunShouldBeDeleted: Boolean,
|
||||
preserveDeclarationOrder: Boolean,
|
||||
pruneClass: Boolean,
|
||||
treatInternalAsPrivate: Boolean,
|
||||
copyFunShouldBeDeleted: Boolean,
|
||||
preserveDeclarationOrder: Boolean,
|
||||
pruneClass: Boolean,
|
||||
treatInternalAsPrivate: Boolean,
|
||||
) {
|
||||
functions.removeIf { pruneClass || it.visibility.shouldRemove(treatInternalAsPrivate) || (copyFunShouldBeDeleted && it.name == "copy") }
|
||||
properties.removeIf { pruneClass || it.visibility.shouldRemove(treatInternalAsPrivate) }
|
||||
functions.removeIf { pruneClass || it.visibility.shouldRemove(treatInternalAsPrivate) || (copyFunShouldBeDeleted && it.name == "copy") }
|
||||
properties.removeIf { pruneClass || it.visibility.shouldRemove(treatInternalAsPrivate) }
|
||||
|
||||
if (!preserveDeclarationOrder) {
|
||||
functions.sortWith(compareBy(KmFunction::name, { it.signature.toString() }))
|
||||
properties.sortWith(compareBy(KmProperty::name, { it.getterSignature.toString() }))
|
||||
}
|
||||
if (!preserveDeclarationOrder) {
|
||||
functions.sortWith(compareBy(KmFunction::name, { it.signature.toString() }))
|
||||
properties.sortWith(compareBy(KmProperty::name, { it.getterSignature.toString() }))
|
||||
}
|
||||
|
||||
for (property in properties) {
|
||||
// Whether or not the *non-const* property is initialized by a compile-time constant is not a part of the ABI.
|
||||
if (!property.isConst) {
|
||||
property.hasConstant = false
|
||||
}
|
||||
for (property in properties) {
|
||||
// Whether or not the *non-const* property is initialized by a compile-time constant is not a part of the ABI.
|
||||
if (!property.isConst) {
|
||||
property.hasConstant = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KmClass.copyFunShouldBeDeleted(removeDataClassCopy: Boolean): Boolean =
|
||||
removeDataClassCopy && isData && constructors.none { !it.isSecondary }
|
||||
private fun KmClass.copyFunShouldBeDeleted(removeDataClassCopy: Boolean): Boolean {
|
||||
return removeDataClassCopy && isData && constructors.none { !it.isSecondary }
|
||||
}
|
||||
|
||||
private fun Visibility.shouldRemove(treatInternalAsPrivate: Boolean): Boolean {
|
||||
return this == Visibility.PRIVATE ||
|
||||
this == Visibility.PRIVATE_TO_THIS ||
|
||||
this == Visibility.LOCAL ||
|
||||
(treatInternalAsPrivate && this == Visibility.INTERNAL)
|
||||
return this == Visibility.PRIVATE ||
|
||||
this == Visibility.PRIVATE_TO_THIS ||
|
||||
this == Visibility.LOCAL ||
|
||||
(treatInternalAsPrivate && this == Visibility.INTERNAL)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.bazel.jvm
|
||||
|
||||
import com.google.devtools.build.lib.worker.WorkerProtocol
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.intellij.build.io.*
|
||||
@@ -16,8 +15,8 @@ object JvmWorker : WorkRequestExecutor {
|
||||
processRequests(startupArgs, this)
|
||||
}
|
||||
|
||||
override suspend fun execute(request: WorkerProtocol.WorkRequest, writer: Writer, baseDir: Path): Int {
|
||||
val args = request.argumentsList
|
||||
override suspend fun execute(request: WorkRequest, writer: Writer, baseDir: Path): Int {
|
||||
val args = request.arguments
|
||||
if (args.isEmpty()) {
|
||||
writer.appendLine("Command is not specified")
|
||||
return 1
|
||||
@@ -33,8 +32,8 @@ object JvmWorker : WorkRequestExecutor {
|
||||
when (taskKind) {
|
||||
"jar" -> {
|
||||
var stripPrefix = command[3]
|
||||
if (stripPrefix == "" && request.inputsList.isNotEmpty()) {
|
||||
val p = request.inputsList.first().path
|
||||
if (stripPrefix == "" && request.inputs.isNotEmpty()) {
|
||||
val p = request.inputs.first().path
|
||||
stripPrefix = command[4]
|
||||
val index = p.indexOf(stripPrefix)
|
||||
require(index != -1)
|
||||
@@ -42,7 +41,7 @@ object JvmWorker : WorkRequestExecutor {
|
||||
}
|
||||
createZip(
|
||||
outJar = Path.of(output),
|
||||
inputs = request.inputsList,
|
||||
inputs = request.inputs,
|
||||
baseDir = baseDir,
|
||||
stripPrefix = stripPrefix,
|
||||
)
|
||||
@@ -51,7 +50,7 @@ object JvmWorker : WorkRequestExecutor {
|
||||
}
|
||||
|
||||
"jdeps" -> {
|
||||
val inputs = request.inputsList.asSequence()
|
||||
val inputs = request.inputs.asSequence()
|
||||
.filter { it.path.endsWith(".jdeps") }
|
||||
.map { baseDir.resolve(it.path) }
|
||||
//Files.writeString(Path.of("${System.getProperty("user.home")}/f.txt"), inputs.joinToString("\n") { it.toString() })
|
||||
@@ -73,7 +72,7 @@ object JvmWorker : WorkRequestExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createZip(outJar: Path, inputs: List<WorkerProtocol.Input>, baseDir: Path, stripPrefix: String) {
|
||||
private suspend fun createZip(outJar: Path, inputs: Array<Input>, baseDir: Path, stripPrefix: String) {
|
||||
//Files.writeString(Path.of("${System.getProperty("user.home")}/f.txt"), stripPrefix + "\n" + inputs.joinToString("\n") { it.toString() })
|
||||
|
||||
val stripPrefixWithSlash = stripPrefix.let { if (it.isEmpty()) "" else "$it/" }
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import java.nio.file.StandardOpenOption
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
internal class AsyncLogger(file: Path, coroutineScope: CoroutineScope) {
|
||||
internal class AsyncFileLogger(file: Path, coroutineScope: CoroutineScope) {
|
||||
private val logChannel = Channel<String>(Channel.UNLIMITED)
|
||||
private val writer = Files.newOutputStream(file, StandardOpenOption.APPEND, StandardOpenOption.CREATE).bufferedWriter()
|
||||
|
||||
@@ -10,8 +10,9 @@ java_proto_library(
|
||||
kt_jvm_library(
|
||||
name = "worker-framework",
|
||||
srcs = glob(["*.kt"], exclude = ["*Test.kt"]),
|
||||
kotlinc_opts = "//:rules_jvm_bootstrap_kotlinc_options",
|
||||
deps = [
|
||||
":worker_protocol_java_proto",
|
||||
"@lib//:kotlin-stdlib",
|
||||
"//:kotlinx-coroutines-core",
|
||||
"//:protobuf-java",
|
||||
"@lib//:jetbrains-annotations",
|
||||
@@ -23,7 +24,9 @@ kt_jvm_library(
|
||||
name = "worker_test_lib",
|
||||
srcs = ["WorkRequestHandlerTest.kt"],
|
||||
associates = [":worker-framework"],
|
||||
kotlinc_opts = "//:rules_jvm_bootstrap_kotlinc_options",
|
||||
deps = [
|
||||
"@lib//:kotlin-stdlib",
|
||||
":worker_protocol_java_proto",
|
||||
"@junit_jupiter_api//jar",
|
||||
"@assertj//jar",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
package org.jetbrains.bazel.jvm
|
||||
|
||||
import com.google.devtools.build.lib.worker.WorkerProtocol.WorkRequest
|
||||
import com.google.protobuf.CodedOutputStream
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
@@ -12,7 +11,6 @@ import kotlinx.coroutines.sync.withLock
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
import org.jetbrains.bazel.jvm.WorkRequestState.*
|
||||
import java.io.*
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
@@ -25,13 +23,13 @@ fun interface WorkRequestExecutor {
|
||||
suspend fun execute(request: WorkRequest, writer: Writer, baseDir: Path): Int
|
||||
}
|
||||
|
||||
internal fun createLogger(classifier: String, coroutineScope: CoroutineScope): AsyncLogger? {
|
||||
internal fun createLogger(classifier: String, coroutineScope: CoroutineScope): AsyncFileLogger? {
|
||||
val dir = Path.of(System.getProperty("user.home"), "$classifier-worker")
|
||||
// changing env or flag leads to rebuild, so, we create logger if log dir exists
|
||||
if (!Files.isDirectory(dir)) {
|
||||
return null
|
||||
}
|
||||
return AsyncLogger(file = dir.resolve("log-" + System.currentTimeMillis() + ".txt"), coroutineScope = coroutineScope)
|
||||
return AsyncFileLogger(file = dir.resolve("log-" + System.currentTimeMillis() + ".txt"), coroutineScope = coroutineScope)
|
||||
}
|
||||
|
||||
fun processRequests(startupArgs: Array<String>, executor: WorkRequestExecutor, debugLogClassifier: String? = null) {
|
||||
@@ -51,16 +49,8 @@ fun processRequests(startupArgs: Array<String>, executor: WorkRequestExecutor, d
|
||||
}
|
||||
|
||||
try {
|
||||
val output = System.out
|
||||
val input = System.`in`
|
||||
// create workerIo only after creating messageProcessor, as messageProcessor must use original system input
|
||||
val workerIo = wrapStandardSystemStreams()
|
||||
WorkRequestHandler(
|
||||
requestExecutor = executor,
|
||||
input = input,
|
||||
out = output,
|
||||
logger = logger,
|
||||
).processRequests(workerIo)
|
||||
WorkRequestHandler(requestExecutor = executor, input = System.`in`, out = System.out, logger = logger)
|
||||
.processRequests()
|
||||
}
|
||||
finally {
|
||||
logger?.shutdown()
|
||||
@@ -101,7 +91,7 @@ internal class WorkRequestHandler internal constructor(
|
||||
* Must be quick and safe - executed in a read thread
|
||||
*/
|
||||
private val cancelHandler: ((Int) -> Unit)? = null,
|
||||
private val logger: AsyncLogger? = null,
|
||||
private val logger: AsyncFileLogger? = null,
|
||||
) {
|
||||
private val workingDir = Path.of(".").toAbsolutePath().normalize()
|
||||
|
||||
@@ -111,11 +101,11 @@ internal class WorkRequestHandler internal constructor(
|
||||
private val activeRequests = ConcurrentHashMap<Int, RequestState>()
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
internal suspend fun processRequests(workerIo: WorkerIo) {
|
||||
internal suspend fun processRequests() {
|
||||
val requestChannel = Channel<RequestState>(Channel.UNLIMITED)
|
||||
try {
|
||||
coroutineScope {
|
||||
startTaskProcessing(requestChannel, workerIo)
|
||||
startTaskProcessing(requestChannel)
|
||||
|
||||
readRequests(requestChannel)
|
||||
}
|
||||
@@ -136,22 +126,16 @@ internal class WorkRequestHandler internal constructor(
|
||||
}
|
||||
activeRequests.clear()
|
||||
}
|
||||
|
||||
try {
|
||||
// unwrap the system streams placing the original streams back
|
||||
workerIo.close()
|
||||
}
|
||||
catch (e: Exception) {
|
||||
System.err.println(e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun readRequests(requestChannel: Channel<RequestState>) {
|
||||
val inputListToReuse = ArrayList<Input>()
|
||||
val argListToReuse = ArrayList<String>()
|
||||
while (coroutineContext.isActive) {
|
||||
val request = try {
|
||||
runInterruptible(Dispatchers.IO) {
|
||||
WorkRequest.parseDelimitedFrom(input)
|
||||
readWorkRequestFromStream(input, inputListToReuse, argListToReuse)
|
||||
}
|
||||
}
|
||||
catch (e: InterruptedIOException) {
|
||||
@@ -179,7 +163,7 @@ internal class WorkRequestHandler internal constructor(
|
||||
if (cancelHandler != null) {
|
||||
cancelHandler(request.requestId)
|
||||
}
|
||||
logger?.log("request(id=$requestId}) cancelled before handling")
|
||||
logger?.log("request(id=$requestId) cancelled before handling")
|
||||
writeAndRemoveRequest(requestId = requestId, wasCancelled = true)
|
||||
}
|
||||
}
|
||||
@@ -204,10 +188,7 @@ internal class WorkRequestHandler internal constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.startTaskProcessing(
|
||||
requestChannel: Channel<RequestState>,
|
||||
workerIo: WorkerIo
|
||||
) {
|
||||
private fun CoroutineScope.startTaskProcessing(requestChannel: Channel<RequestState>) {
|
||||
repeat(Runtime.getRuntime().availableProcessors().coerceAtLeast(2)) {
|
||||
launch {
|
||||
for (item in requestChannel) {
|
||||
@@ -215,7 +196,7 @@ internal class WorkRequestHandler internal constructor(
|
||||
val stateRef = item.state
|
||||
when {
|
||||
stateRef.compareAndSet(NOT_STARTED, STARTED) -> {
|
||||
handleRequest(workerIo = workerIo, request = item.request, requestState = stateRef)
|
||||
handleRequest(request = item.request, requestState = stateRef)
|
||||
}
|
||||
else -> {
|
||||
val state = stateRef.get()
|
||||
@@ -289,20 +270,15 @@ internal class WorkRequestHandler internal constructor(
|
||||
* @throws IOException if there is an error talking to the server. Errors from calling the [][.callback] are reported with exit code 1.
|
||||
*/
|
||||
// visible for tests
|
||||
internal suspend fun handleRequest(workerIo: WorkerIo, request: WorkRequest, requestState: AtomicReference<WorkRequestState>) {
|
||||
internal suspend fun handleRequest(request: WorkRequest, requestState: AtomicReference<WorkRequestState>) {
|
||||
val baseDir = if (request.sandboxDir.isNullOrEmpty()) workingDir else workingDir.resolve(request.sandboxDir)
|
||||
var exitCode = 1
|
||||
val stringWriter = StringWriter()
|
||||
var errorToThrow: Throwable? = null
|
||||
val requestId = request.requestId
|
||||
try {
|
||||
//System.err.println("request: ${request.inputs.asSequence().take(4).joinToString { it.path }}, $baseDir, ${request.sandboxDir}")
|
||||
exitCode = requestExecutor.execute(request = request, writer = stringWriter, baseDir = baseDir)
|
||||
|
||||
// read out the captured string for the final WorkResponse output
|
||||
val captured = workerIo.readCapturedAsUtf8String()?.trim()
|
||||
if (!captured.isNullOrEmpty()) {
|
||||
stringWriter.write(captured)
|
||||
}
|
||||
}
|
||||
catch (e: CancellationException) {
|
||||
errorToThrow = e
|
||||
@@ -332,82 +308,4 @@ internal class WorkRequestHandler internal constructor(
|
||||
throw errorToThrow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that wraps the standard [System.in], [System.out], and [System.err]
|
||||
* with our own ByteArrayOutputStream that allows [WorkRequestHandler] to safely capture
|
||||
* outputs that can't be directly captured by the PrintStream associated with the work request.
|
||||
*
|
||||
* This is most useful when integrating JVM tools that write exceptions and logs directly to
|
||||
* [System.out] and [System.err], which would corrupt the persistent worker protocol.
|
||||
* We also redirect [System.in], just in case a tool should attempt to read it.
|
||||
*
|
||||
* WorkerIO implements [AutoCloseable] and will swap the original streams back into [System] once close has been called.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
internal class WorkerIo(
|
||||
/**
|
||||
* Returns the original input stream most commonly provided by [System.in]
|
||||
*/
|
||||
@JvmField val originalInputStream: InputStream?,
|
||||
/**
|
||||
* Returns the original output stream most commonly provided by [System.out]
|
||||
*/
|
||||
@JvmField val originalOutputStream: PrintStream?,
|
||||
/**
|
||||
* Returns the original error stream most commonly provided by [System.err]
|
||||
*/
|
||||
@JvmField val originalErrorStream: PrintStream?,
|
||||
private val capturedStream: ByteArrayOutputStream,
|
||||
private val restore: AutoCloseable) : AutoCloseable {
|
||||
/**
|
||||
* Returns the captured outputs as a UTF-8 string
|
||||
*/
|
||||
fun readCapturedAsUtf8String(): String? {
|
||||
if (capturedStream.size() == 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
val captureOutput = capturedStream.toString(StandardCharsets.UTF_8)
|
||||
capturedStream.reset()
|
||||
return captureOutput
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
restore.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the standard System streams and WorkerIO instance
|
||||
*/
|
||||
@VisibleForTesting
|
||||
internal fun wrapStandardSystemStreams(): WorkerIo {
|
||||
// save the original streams
|
||||
val originalInputStream = System.`in`
|
||||
val originalOutputStream = System.out
|
||||
val originalErrorStream = System.err
|
||||
|
||||
// replace the original streams with our own instances
|
||||
val capturedStream = ByteArrayOutputStream()
|
||||
val outputBuffer = PrintStream(capturedStream, true)
|
||||
val byteArrayInputStream = ByteArray(0).inputStream()
|
||||
System.setIn(byteArrayInputStream)
|
||||
System.setOut(outputBuffer)
|
||||
System.setErr(outputBuffer)
|
||||
|
||||
return WorkerIo(
|
||||
originalInputStream = originalInputStream,
|
||||
originalOutputStream = originalOutputStream,
|
||||
originalErrorStream = originalErrorStream,
|
||||
capturedStream = capturedStream,
|
||||
restore = AutoCloseable {
|
||||
System.setIn(originalInputStream)
|
||||
System.setOut(originalOutputStream)
|
||||
System.setErr(originalErrorStream)
|
||||
outputBuffer.close()
|
||||
byteArrayInputStream.close()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -15,11 +15,10 @@
|
||||
|
||||
package org.jetbrains.bazel.jvm
|
||||
|
||||
import com.google.devtools.build.lib.worker.WorkerProtocol.WorkRequest
|
||||
import com.google.devtools.build.lib.worker.WorkerProtocol
|
||||
import com.google.devtools.build.lib.worker.WorkerProtocol.WorkResponse
|
||||
import kotlinx.coroutines.*
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.Timeout
|
||||
import java.io.*
|
||||
@@ -35,26 +34,6 @@ import kotlin.coroutines.coroutineContext
|
||||
* Tests for the WorkRequestHandler
|
||||
*/
|
||||
class WorkRequestHandlerTest {
|
||||
companion object {
|
||||
private fun createTestWorkerIo(): WorkerIo {
|
||||
val captured = ByteArrayOutputStream()
|
||||
return WorkerIo(
|
||||
originalInputStream = System.`in`,
|
||||
originalOutputStream = System.out,
|
||||
originalErrorStream = System.err,
|
||||
capturedStream = captured,
|
||||
restore = captured
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val testWorkerIo = createTestWorkerIo()
|
||||
|
||||
@AfterEach
|
||||
fun after() {
|
||||
testWorkerIo.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Timeout(value = 10, unit = TimeUnit.SECONDS)
|
||||
fun normalWorkRequest() {
|
||||
@@ -65,9 +44,16 @@ class WorkRequestHandlerTest {
|
||||
out = out,
|
||||
)
|
||||
|
||||
val request = WorkRequest.newBuilder().addAllArguments(listOf("--sources", "A.java")).build()
|
||||
val request = WorkRequest(
|
||||
arguments = arrayOf("--sources", "A.java"),
|
||||
inputs = emptyArray(),
|
||||
requestId = 0,
|
||||
cancel = false,
|
||||
verbosity = 0,
|
||||
sandboxDir = null,
|
||||
)
|
||||
runBlocking {
|
||||
handler.handleRequest(workerIo = testWorkerIo, request = request, requestState = AtomicReference(WorkRequestState.STARTED))
|
||||
handler.handleRequest(request = request, requestState = AtomicReference(WorkRequestState.STARTED))
|
||||
}
|
||||
|
||||
val response = WorkResponse.parseDelimitedFrom(out.toByteArray().inputStream())
|
||||
@@ -88,7 +74,7 @@ class WorkRequestHandlerTest {
|
||||
|
||||
val request = newWorkRequest(listOf("--sources", "A.java"))
|
||||
runBlocking {
|
||||
handler.handleRequest(workerIo = testWorkerIo, request = request, requestState = AtomicReference(WorkRequestState.STARTED))
|
||||
handler.handleRequest(request = request, requestState = AtomicReference(WorkRequestState.STARTED))
|
||||
}
|
||||
|
||||
val response = WorkResponse.parseDelimitedFrom(out.toByteArray().inputStream())
|
||||
@@ -127,8 +113,8 @@ class WorkRequestHandlerTest {
|
||||
|
||||
useHandler(handler = handler, waitForProcessing = true, errorFilter = { it.message != "Intentional death!" }) {
|
||||
val args = listOf("--sources", "A.java")
|
||||
newWorkRequest(args).writeDelimitedTo(src)
|
||||
newWorkRequest(args, 43).writeDelimitedTo(src)
|
||||
WorkerProtocol.WorkRequest.newBuilder().addAllArguments(args).setRequestId(42).build().writeDelimitedTo(src)
|
||||
WorkerProtocol.WorkRequest.newBuilder().addAllArguments(args).setRequestId(43).build().writeDelimitedTo(src)
|
||||
|
||||
started.acquire(2)
|
||||
}
|
||||
@@ -149,9 +135,9 @@ class WorkRequestHandlerTest {
|
||||
)
|
||||
|
||||
val args = listOf("--sources", "A.java")
|
||||
val request = WorkRequest.newBuilder().addAllArguments(args).build()
|
||||
val request = newWorkRequest(args, requestId = 0)
|
||||
runBlocking {
|
||||
handler.handleRequest(testWorkerIo, request, AtomicReference(WorkRequestState.STARTED))
|
||||
handler.handleRequest(request, AtomicReference(WorkRequestState.STARTED))
|
||||
}
|
||||
|
||||
val response = WorkResponse.parseDelimitedFrom(out.toByteArray().inputStream())
|
||||
@@ -173,9 +159,9 @@ class WorkRequestHandlerTest {
|
||||
)
|
||||
|
||||
val args = listOf("--sources", "A.java")
|
||||
val request = WorkRequest.newBuilder().addAllArguments(args).setRequestId(342).build()
|
||||
val request = newWorkRequest(args, 342)
|
||||
runBlocking {
|
||||
handler.handleRequest(workerIo = testWorkerIo, request = request, requestState = AtomicReference(WorkRequestState.STARTED))
|
||||
handler.handleRequest(request = request, requestState = AtomicReference(WorkRequestState.STARTED))
|
||||
}
|
||||
|
||||
val response = WorkResponse.parseDelimitedFrom(ByteArrayInputStream(out.toByteArray()))
|
||||
@@ -206,8 +192,8 @@ class WorkRequestHandlerTest {
|
||||
)
|
||||
|
||||
useHandler(handler) {
|
||||
WorkRequest.newBuilder().setRequestId(42).build().writeDelimitedTo(src)
|
||||
WorkRequest.newBuilder().setRequestId(42).setCancel(true).build().writeDelimitedTo(src)
|
||||
WorkerProtocol.WorkRequest.newBuilder().setRequestId(42).build().writeDelimitedTo(src)
|
||||
WorkerProtocol.WorkRequest.newBuilder().setRequestId(42).setCancel(true).build().writeDelimitedTo(src)
|
||||
|
||||
val response = WorkResponse.parseDelimitedFrom(dest)
|
||||
|
||||
@@ -260,10 +246,10 @@ class WorkRequestHandlerTest {
|
||||
|
||||
// this thread just makes sure the WorkRequestHandler does work asynchronously
|
||||
useHandler(handler, failures, waitForProcessing = true) {
|
||||
WorkRequest.newBuilder().setRequestId(42).build().writeDelimitedTo(src)
|
||||
WorkerProtocol.WorkRequest.newBuilder().setRequestId(42).build().writeDelimitedTo(src)
|
||||
// make sure the handler is called before sending the cancel request, or we might process the cancellation entirely before that
|
||||
handlerCalled.acquire()
|
||||
WorkRequest.newBuilder().setRequestId(42).setCancel(true).build().writeDelimitedTo(src)
|
||||
WorkerProtocol.WorkRequest.newBuilder().setRequestId(42).setCancel(true).build().writeDelimitedTo(src)
|
||||
waitForCancel.release()
|
||||
|
||||
assertThat(WorkResponse.parseDelimitedFrom(dest).requestId).isEqualTo(42)
|
||||
@@ -308,9 +294,9 @@ class WorkRequestHandlerTest {
|
||||
)
|
||||
|
||||
useHandler(handler, failures, waitForProcessing = true) {
|
||||
WorkRequest.newBuilder().setRequestId(42).build().writeDelimitedTo(src)
|
||||
WorkRequest.newBuilder().setRequestId(42).setCancel(true).build().writeDelimitedTo(src)
|
||||
WorkRequest.newBuilder().setRequestId(42).setCancel(true).build().writeDelimitedTo(src)
|
||||
WorkerProtocol.WorkRequest.newBuilder().setRequestId(42).build().writeDelimitedTo(src)
|
||||
WorkerProtocol.WorkRequest.newBuilder().setRequestId(42).setCancel(true).build().writeDelimitedTo(src)
|
||||
WorkerProtocol.WorkRequest.newBuilder().setRequestId(42).setCancel(true).build().writeDelimitedTo(src)
|
||||
|
||||
waitForCancel.release()
|
||||
|
||||
@@ -347,9 +333,9 @@ class WorkRequestHandlerTest {
|
||||
|
||||
var r: WorkResponse? = null
|
||||
useHandler(handler, waitForProcessing = true) {
|
||||
WorkRequest.newBuilder().setRequestId(42).build().writeDelimitedTo(src)
|
||||
WorkerProtocol.WorkRequest.newBuilder().setRequestId(42).build().writeDelimitedTo(src)
|
||||
r = WorkResponse.parseDelimitedFrom(dest)
|
||||
WorkRequest.newBuilder().setRequestId(42).setCancel(true).build().writeDelimitedTo(src)
|
||||
WorkerProtocol.WorkRequest.newBuilder().setRequestId(42).setCancel(true).build().writeDelimitedTo(src)
|
||||
src.close()
|
||||
}
|
||||
|
||||
@@ -370,15 +356,15 @@ class WorkRequestHandlerTest {
|
||||
fun workRequestHandlerWithWorkRequestCallback() {
|
||||
val out = ByteArrayOutputStream()
|
||||
val handler = WorkRequestHandler(
|
||||
requestExecutor = { request, err, _ -> request.argumentsCount },
|
||||
requestExecutor = { request, err, _ -> request.arguments.size },
|
||||
ByteArrayInputStream(ByteArray(0)),
|
||||
out,
|
||||
)
|
||||
|
||||
val args = listOf("--sources", "B.java")
|
||||
val request = WorkRequest.newBuilder().addAllArguments(args).build()
|
||||
val request = newWorkRequest(args, requestId = 0)
|
||||
runBlocking {
|
||||
handler.handleRequest(workerIo = testWorkerIo, request = request, requestState = AtomicReference(WorkRequestState.STARTED))
|
||||
handler.handleRequest(request = request, requestState = AtomicReference(WorkRequestState.STARTED))
|
||||
}
|
||||
|
||||
val response = WorkResponse.parseDelimitedFrom(ByteArrayInputStream(out.toByteArray()))
|
||||
@@ -386,75 +372,10 @@ class WorkRequestHandlerTest {
|
||||
assertThat(response.exitCode).isEqualTo(2)
|
||||
assertThat(response.getOutput()).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Timeout(value = 10, unit = TimeUnit.SECONDS)
|
||||
fun workerIODoesWrapSystemStreams() {
|
||||
// Save the original streams
|
||||
val originalInputStream = System.`in`
|
||||
val originalOutputStream = System.out
|
||||
val originalErrorStream = System.err
|
||||
|
||||
// Swap in the test streams to assert against
|
||||
val byteArrayInputStream = ByteArray(0).inputStream()
|
||||
System.setIn(byteArrayInputStream)
|
||||
val outputBuffer = PrintStream(ByteArrayOutputStream(), true)
|
||||
System.setOut(outputBuffer)
|
||||
System.setErr(outputBuffer)
|
||||
|
||||
try {
|
||||
outputBuffer.use {
|
||||
byteArrayInputStream.use {
|
||||
wrapStandardSystemStreams().use { io ->
|
||||
// Assert that the WorkerIO returns the correct wrapped streams and the new System instance
|
||||
// has been swapped out with the wrapped one
|
||||
assertThat(io.originalInputStream).isSameAs(byteArrayInputStream)
|
||||
assertThat(System.`in`).isNotSameAs(byteArrayInputStream)
|
||||
|
||||
assertThat(io.originalOutputStream).isSameAs(outputBuffer)
|
||||
assertThat(System.out).isNotSameAs(outputBuffer)
|
||||
|
||||
assertThat(io.originalErrorStream).isSameAs(outputBuffer)
|
||||
assertThat(System.err).isNotSameAs(outputBuffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// swap back in the original streams
|
||||
System.setIn(originalInputStream)
|
||||
System.setOut(originalOutputStream)
|
||||
System.setErr(originalErrorStream)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Timeout(value = 10, unit = TimeUnit.SECONDS)
|
||||
fun workerIODoesCaptureStandardOutAndErrorStreams() {
|
||||
wrapStandardSystemStreams().use { io ->
|
||||
// assert that nothing has been captured in the new instance
|
||||
assertThat(io.readCapturedAsUtf8String()).isNullOrEmpty()
|
||||
|
||||
// Assert that the standard out/error stream redirect to our own streams
|
||||
print("This is a standard out message!")
|
||||
System.err.print("This is a standard error message!")
|
||||
assertThat(io.readCapturedAsUtf8String()).isEqualTo("This is a standard out message!This is a standard error message!")
|
||||
|
||||
// Assert that readCapturedAsUtf8String calls reset on the captured stream after a read
|
||||
assertThat(io.readCapturedAsUtf8String()).isNullOrEmpty()
|
||||
|
||||
print("out 1")
|
||||
System.err.print("err 1")
|
||||
print("out 2")
|
||||
System.err.print("err 2")
|
||||
assertThat(io.readCapturedAsUtf8String()).isEqualTo("out 1err 1out 2err 2")
|
||||
assertThat(io.readCapturedAsUtf8String()).isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun newWorkRequest(args: List<String>, id: Int = 42): WorkRequest {
|
||||
return WorkRequest.newBuilder().addAllArguments(args).setRequestId(id).build()
|
||||
private fun newWorkRequest(args: List<String>, requestId: Int = 42): WorkRequest {
|
||||
return WorkRequest(arguments = args.toTypedArray(), requestId = requestId, inputs = emptyArray(), cancel = false, verbosity = 0, sandboxDir = null)
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class)
|
||||
@@ -465,39 +386,37 @@ private inline fun useHandler(
|
||||
crossinline errorFilter: (Throwable) -> Boolean = { true },
|
||||
task: () -> Unit
|
||||
) {
|
||||
wrapStandardSystemStreams().use { workerIo ->
|
||||
val processor = GlobalScope.async(Dispatchers.Default) {
|
||||
try {
|
||||
handler.processRequests(workerIo)
|
||||
}
|
||||
catch (_: CancellationException) {
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
if (errorFilter(e)) {
|
||||
failures.add(e.message!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val processor = GlobalScope.async(Dispatchers.Default) {
|
||||
try {
|
||||
task()
|
||||
|
||||
if (waitForProcessing) {
|
||||
runBlocking {
|
||||
processor.await()
|
||||
}
|
||||
}
|
||||
|
||||
// checks that there weren't other unexpected failures
|
||||
assertThat(failures).isEmpty()
|
||||
handler.processRequests()
|
||||
}
|
||||
finally {
|
||||
runBlocking {
|
||||
if (processor.isCompleted) {
|
||||
processor.getCompletionExceptionOrNull()?.let { throw it }
|
||||
}
|
||||
processor.cancel()
|
||||
catch (_: CancellationException) {
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
if (errorFilter(e)) {
|
||||
failures.add(e.message!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
task()
|
||||
|
||||
if (waitForProcessing) {
|
||||
runBlocking {
|
||||
processor.await()
|
||||
}
|
||||
}
|
||||
|
||||
// checks that there weren't other unexpected failures
|
||||
assertThat(failures).isEmpty()
|
||||
}
|
||||
finally {
|
||||
runBlocking {
|
||||
if (processor.isCompleted) {
|
||||
processor.getCompletionExceptionOrNull()?.let { throw it }
|
||||
}
|
||||
processor.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package org.jetbrains.bazel.jvm
|
||||
|
||||
import com.google.protobuf.CodedInputStream
|
||||
import java.io.FilterInputStream
|
||||
import java.io.InputStream
|
||||
import kotlin.math.min
|
||||
|
||||
class Input(
|
||||
@JvmField val path: String,
|
||||
@JvmField val digest: ByteArray,
|
||||
)
|
||||
|
||||
class WorkRequest(
|
||||
@JvmField val arguments: Array<String>,
|
||||
@JvmField val inputs: Array<Input>,
|
||||
@JvmField val requestId: Int,
|
||||
@JvmField val cancel: Boolean,
|
||||
@JvmField val verbosity: Int,
|
||||
@JvmField val sandboxDir: String?
|
||||
)
|
||||
|
||||
internal fun readWorkRequestFromStream(
|
||||
input: InputStream,
|
||||
inputListToReuse: MutableList<Input>,
|
||||
argListToReuse: MutableList<String>
|
||||
): WorkRequest? {
|
||||
// read the length-prefixed WorkRequest
|
||||
val firstByte = input.read()
|
||||
if (firstByte == -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
val size = CodedInputStream.readRawVarint32(firstByte, input)
|
||||
|
||||
var requestId = 0
|
||||
var cancel = false
|
||||
var verbosity = 0
|
||||
var sandboxDir: String? = null
|
||||
|
||||
val codedInputStream = CodedInputStream.newInstance(LimitedInputStream(input, size))
|
||||
argListToReuse.clear()
|
||||
inputListToReuse.clear()
|
||||
while (true) {
|
||||
val tag = codedInputStream.readTag()
|
||||
if (tag == 0) {
|
||||
break
|
||||
}
|
||||
|
||||
when (tag.shr(3)) {
|
||||
1 -> argListToReuse.add(codedInputStream.readString())
|
||||
2 -> inputListToReuse.add(readInput(codedInputStream))
|
||||
3 -> requestId = codedInputStream.readInt32()
|
||||
4 -> cancel = codedInputStream.readBool()
|
||||
5 -> verbosity = codedInputStream.readInt32()
|
||||
6 -> sandboxDir = codedInputStream.readString()
|
||||
else -> codedInputStream.skipField(tag)
|
||||
}
|
||||
}
|
||||
|
||||
return WorkRequest(
|
||||
arguments = argListToReuse.toTypedArray(),
|
||||
inputs = inputListToReuse.toTypedArray(),
|
||||
requestId = requestId,
|
||||
cancel = cancel,
|
||||
verbosity = verbosity,
|
||||
sandboxDir = sandboxDir
|
||||
)
|
||||
}
|
||||
|
||||
private fun readInput(codedInputStream: CodedInputStream): Input {
|
||||
var path = ""
|
||||
var digest: ByteArray? = null
|
||||
|
||||
val messageSize = codedInputStream.readRawVarint32()
|
||||
val limit = codedInputStream.pushLimit(messageSize)
|
||||
while (!codedInputStream.isAtEnd) {
|
||||
val tag = codedInputStream.readTag()
|
||||
when (tag.shr(3)) {
|
||||
1 -> {
|
||||
path = codedInputStream.readString()
|
||||
}
|
||||
|
||||
2 -> {
|
||||
digest = codedInputStream.readByteArray()
|
||||
}
|
||||
|
||||
else -> {
|
||||
codedInputStream.skipField(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
codedInputStream.popLimit(limit)
|
||||
|
||||
return Input(path = path, digest = digest!!)
|
||||
}
|
||||
|
||||
private class LimitedInputStream(input: InputStream, private var limit: Int) : FilterInputStream(input) {
|
||||
override fun available(): Int = min(super.available(), limit)
|
||||
|
||||
override fun read(): Int {
|
||||
if (limit <= 0) {
|
||||
return -1
|
||||
}
|
||||
|
||||
val result = super.read()
|
||||
if (result >= 0) {
|
||||
--limit
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun read(b: ByteArray?, off: Int, len: Int): Int {
|
||||
var len = len
|
||||
if (limit <= 0) {
|
||||
return -1
|
||||
}
|
||||
|
||||
len = min(len, limit)
|
||||
val result = super.read(b, off, len)
|
||||
if (result >= 0) {
|
||||
limit -= result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun skip(n: Long): Long {
|
||||
val result = super.skip(min(n, limit.toLong())).toInt()
|
||||
if (result >= 0) {
|
||||
limit -= result
|
||||
}
|
||||
return result.toLong()
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
|
||||
kt_jvm_library(
|
||||
name = "build-zip",
|
||||
module_name = "intellij.idea.community.build.zip",
|
||||
kotlinc_opts = "//:rules_jvm_bootstrap_kotlinc_options",
|
||||
visibility = ["//visibility:public"],
|
||||
srcs = glob(["src/*.kt"]),
|
||||
deps = [
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.util.zip.ZipEntry
|
||||
import kotlin.math.min
|
||||
|
||||
val W_CREATE_NEW: EnumSet<StandardOpenOption> = EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)
|
||||
private val WRITE = EnumSet.of(StandardOpenOption.WRITE)
|
||||
val WRITE: EnumSet<StandardOpenOption> = EnumSet.of(StandardOpenOption.WRITE)
|
||||
private val READ = EnumSet.of(StandardOpenOption.READ)
|
||||
|
||||
// 1 MB
|
||||
@@ -43,6 +43,25 @@ fun ZipArchiveOutputStream.file(nameString: String, file: Path) {
|
||||
return
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
fun ZipArchiveOutputStream.fileAndGetData(name: ByteArray, file: Path): ByteArray {
|
||||
return FileChannel.open(file, READ).use { channel ->
|
||||
val size = channel.size()
|
||||
assert(size <= Int.MAX_VALUE)
|
||||
writeEntryHeaderWithoutCrc(name = name, size = size.toInt())
|
||||
transferFrom(channel, size.toLong())
|
||||
|
||||
val buffer = ByteBuffer.allocate(size.toInt())
|
||||
while (buffer.hasRemaining()) {
|
||||
val bytesRead = channel.read(buffer, 0)
|
||||
if (bytesRead == -1) {
|
||||
throw EOFException("End of stream reached before filling the buffer")
|
||||
}
|
||||
}
|
||||
buffer.array()
|
||||
}
|
||||
}
|
||||
|
||||
fun transformZipUsingTempFile(file: Path, indexWriter: IkvIndexBuilder?, task: (ZipFileWriter) -> Unit) {
|
||||
val tempFile = Files.createTempFile(file.parent, file.fileName.toString(), ".tmp")
|
||||
try {
|
||||
|
||||
+35
-56
@@ -7499,62 +7499,6 @@ java_import(
|
||||
visibility = ["//visibility:public"]
|
||||
)
|
||||
|
||||
java_library(
|
||||
name = "platform-build_scripts-code_optimizer-proguard",
|
||||
exports = [
|
||||
":proguard-base-7_5_0_http_import",
|
||||
":proguard-core-9_1_4_http_import",
|
||||
":kotlin-metadata-jvm-2_0_0_http_import",
|
||||
":annotations-24_0_0_http_import",
|
||||
":log4j-api-2_19_0_http_import",
|
||||
":log4j-core-2_19_0_http_import",
|
||||
":json-20231013_http_import",
|
||||
],
|
||||
visibility = ["//visibility:public"]
|
||||
)
|
||||
|
||||
kt_jvm_import(
|
||||
name = "proguard-base-7_5_0_http_import",
|
||||
jar = "@proguard-base-7_5_0_http//file",
|
||||
srcjar = "@proguard-base-7_5_0-sources_http//file"
|
||||
)
|
||||
|
||||
kt_jvm_import(
|
||||
name = "proguard-core-9_1_4_http_import",
|
||||
jar = "@proguard-core-9_1_4_http//file",
|
||||
srcjar = "@proguard-core-9_1_4-sources_http//file"
|
||||
)
|
||||
|
||||
kt_jvm_import(
|
||||
name = "kotlin-metadata-jvm-2_0_0_http_import",
|
||||
jar = "@kotlin-metadata-jvm-2_0_0_http//file",
|
||||
srcjar = "@kotlin-metadata-jvm-2_0_0-sources_http//file"
|
||||
)
|
||||
|
||||
kt_jvm_import(
|
||||
name = "annotations-24_0_0_http_import",
|
||||
jar = "@annotations-24_0_0_http//file",
|
||||
srcjar = "@annotations-24_0_0-sources_http//file"
|
||||
)
|
||||
|
||||
kt_jvm_import(
|
||||
name = "log4j-api-2_19_0_http_import",
|
||||
jar = "@log4j-api-2_19_0_http//file",
|
||||
srcjar = "@log4j-api-2_19_0-sources_http//file"
|
||||
)
|
||||
|
||||
kt_jvm_import(
|
||||
name = "log4j-core-2_19_0_http_import",
|
||||
jar = "@log4j-core-2_19_0_http//file",
|
||||
srcjar = "@log4j-core-2_19_0-sources_http//file"
|
||||
)
|
||||
|
||||
kt_jvm_import(
|
||||
name = "json-20231013_http_import",
|
||||
jar = "@json-20231013_http//file",
|
||||
srcjar = "@json-20231013-sources_http//file"
|
||||
)
|
||||
|
||||
java_library(
|
||||
name = "platform-build_scripts-jetbrains-apple-notary-api-kotlin-client",
|
||||
exports = [
|
||||
@@ -9113,6 +9057,41 @@ kt_jvm_import(
|
||||
visibility = ["//visibility:public"]
|
||||
)
|
||||
|
||||
java_library(
|
||||
name = "proguard",
|
||||
exports = [
|
||||
":proguard-base-7_6_1_http_import",
|
||||
":proguard-core-9_1_7_http_import",
|
||||
":log4j-api-2_24_2_http_import",
|
||||
":log4j-core-2_24_2_http_import",
|
||||
],
|
||||
visibility = ["//visibility:public"]
|
||||
)
|
||||
|
||||
kt_jvm_import(
|
||||
name = "proguard-base-7_6_1_http_import",
|
||||
jar = "@proguard-base-7_6_1_http//file",
|
||||
srcjar = "@proguard-base-7_6_1-sources_http//file"
|
||||
)
|
||||
|
||||
kt_jvm_import(
|
||||
name = "proguard-core-9_1_7_http_import",
|
||||
jar = "@proguard-core-9_1_7_http//file",
|
||||
srcjar = "@proguard-core-9_1_7-sources_http//file"
|
||||
)
|
||||
|
||||
kt_jvm_import(
|
||||
name = "log4j-api-2_24_2_http_import",
|
||||
jar = "@log4j-api-2_24_2_http//file",
|
||||
srcjar = "@log4j-api-2_24_2-sources_http//file"
|
||||
)
|
||||
|
||||
kt_jvm_import(
|
||||
name = "log4j-core-2_24_2_http_import",
|
||||
jar = "@log4j-core-2_24_2_http//file",
|
||||
srcjar = "@log4j-core-2_24_2-sources_http//file"
|
||||
)
|
||||
|
||||
java_import(
|
||||
name = "protobuf",
|
||||
jars = ["@protobuf-java-3_24_4-jb_2_http//file"],
|
||||
|
||||
+56
-84
@@ -10569,90 +10569,6 @@ http_file(
|
||||
downloaded_file_path = "oshi-core-6.6.0-sources.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "proguard-base-7_5_0_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/com/guardsquare/proguard-base/7.5.0/proguard-base-7.5.0.jar",
|
||||
sha256 = "04f507bf11e57d798d972b6640cd026302e0b54800cd75813dc4715da66f3ab5",
|
||||
downloaded_file_path = "proguard-base-7.5.0.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "proguard-core-9_1_4_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/com/guardsquare/proguard-core/9.1.4/proguard-core-9.1.4.jar",
|
||||
sha256 = "c51bb89ac5668280e3db7e4ab6c16f454ec5132ed356c840a4f930f16ef6ed51",
|
||||
downloaded_file_path = "proguard-core-9.1.4.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "kotlin-metadata-jvm-2_0_0_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-metadata-jvm/2.0.0/kotlin-metadata-jvm-2.0.0.jar",
|
||||
sha256 = "ad8f1c7dbc5ac46f5cbd2d2e5de39c56c9db65dd7de716a84e01ce208758aee6",
|
||||
downloaded_file_path = "kotlin-metadata-jvm-2.0.0.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "log4j-api-2_19_0_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/apache/logging/log4j/log4j-api/2.19.0/log4j-api-2.19.0.jar",
|
||||
sha256 = "5ccb24ad9f92e768d0bc456d3061a737951262df803e004d2cad096b75a88d60",
|
||||
downloaded_file_path = "log4j-api-2.19.0.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "log4j-core-2_19_0_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.19.0/log4j-core-2.19.0.jar",
|
||||
sha256 = "b4a1796fab7bfc36df015c1b4052459147997e8d215a7199d71d05f9e747e4f4",
|
||||
downloaded_file_path = "log4j-core-2.19.0.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "json-20231013_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/json/json/20231013/json-20231013.jar",
|
||||
sha256 = "0f18192df289114e17aa1a0d0a7f8372cc9f5c7e4f7e39adcf8906fe714fa7d3",
|
||||
downloaded_file_path = "json-20231013.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "proguard-base-7_5_0-sources_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/com/guardsquare/proguard-base/7.5.0/proguard-base-7.5.0-sources.jar",
|
||||
sha256 = "8be2f5fd5875b4ccd01d149059e7abacae56c9b0471212d0b80ceb8b211c360c",
|
||||
downloaded_file_path = "proguard-base-7.5.0-sources.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "proguard-core-9_1_4-sources_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/com/guardsquare/proguard-core/9.1.4/proguard-core-9.1.4-sources.jar",
|
||||
sha256 = "4ca2942f6e7ce02431ba3692b4b47962e6b726eee34ef5508653676eb8e37200",
|
||||
downloaded_file_path = "proguard-core-9.1.4-sources.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "kotlin-metadata-jvm-2_0_0-sources_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-metadata-jvm/2.0.0/kotlin-metadata-jvm-2.0.0-sources.jar",
|
||||
sha256 = "11cc619a970e643786053993b53286b6a0dc7f1bd787d99ed506c8d21e558424",
|
||||
downloaded_file_path = "kotlin-metadata-jvm-2.0.0-sources.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "log4j-api-2_19_0-sources_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/apache/logging/log4j/log4j-api/2.19.0/log4j-api-2.19.0-sources.jar",
|
||||
sha256 = "b068cfc8d7d975c6d8910c0a0650874ae6445ec77b2e1511f3910384dee29f7f",
|
||||
downloaded_file_path = "log4j-api-2.19.0-sources.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "log4j-core-2_19_0-sources_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.19.0/log4j-core-2.19.0-sources.jar",
|
||||
sha256 = "7f2c06b81cbf7fba6fc5e4f607c7b47cbd893c315cd2c237da65b941ca8b1278",
|
||||
downloaded_file_path = "log4j-core-2.19.0-sources.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "json-20231013-sources_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/json/json/20231013/json-20231013-sources.jar",
|
||||
sha256 = "fc65cb53ae955d07f62478485446a544a0aba1e0ab8522688ee875dbbf11766e",
|
||||
downloaded_file_path = "json-20231013-sources.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "apple-notary-api-kotlin-client-1_0_5_http",
|
||||
url = "https://cache-redirector.jetbrains.com/packages.jetbrains.team/maven/p/ij/intellij-dependencies/org/jetbrains/apple-notary-api-kotlin-client/1.0.5/apple-notary-api-kotlin-client-1.0.5.jar",
|
||||
@@ -11619,6 +11535,62 @@ http_file(
|
||||
downloaded_file_path = "poko-compiler-plugin-0.18.0-sources.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "proguard-base-7_6_1_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/com/guardsquare/proguard-base/7.6.1/proguard-base-7.6.1.jar",
|
||||
sha256 = "c2650e666c3443781dc5e9eaee5de8721593f00be44e9d87cec761406357c770",
|
||||
downloaded_file_path = "proguard-base-7.6.1.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "proguard-core-9_1_7_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/com/guardsquare/proguard-core/9.1.7/proguard-core-9.1.7.jar",
|
||||
sha256 = "db06fa9f6b43bb7d386420f854f41aa55a11023de36bad68e043c3ac3f33b8d5",
|
||||
downloaded_file_path = "proguard-core-9.1.7.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "log4j-api-2_24_2_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/apache/logging/log4j/log4j-api/2.24.2/log4j-api-2.24.2.jar",
|
||||
sha256 = "0ca3ecbd4c315bdd5f2ef6af127712df718c78334cce5bf6fb7b4aa17fdab126",
|
||||
downloaded_file_path = "log4j-api-2.24.2.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "log4j-core-2_24_2_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.24.2/log4j-core-2.24.2.jar",
|
||||
sha256 = "7a7b90db866c86a1093b3fde758ca28398ebb2a533da0d79a30cc0a78506b9df",
|
||||
downloaded_file_path = "log4j-core-2.24.2.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "proguard-base-7_6_1-sources_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/com/guardsquare/proguard-base/7.6.1/proguard-base-7.6.1-sources.jar",
|
||||
sha256 = "9aed0e666f457f755e0756176821ef1d31a928790450e8e18f5bb6dcd4a9bac5",
|
||||
downloaded_file_path = "proguard-base-7.6.1-sources.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "proguard-core-9_1_7-sources_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/com/guardsquare/proguard-core/9.1.7/proguard-core-9.1.7-sources.jar",
|
||||
sha256 = "495063fbf5a1989435ee15ed6c6caf39b7dc7af22dd495e38fc7e6e7c00f1fcc",
|
||||
downloaded_file_path = "proguard-core-9.1.7-sources.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "log4j-api-2_24_2-sources_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/apache/logging/log4j/log4j-api/2.24.2/log4j-api-2.24.2-sources.jar",
|
||||
sha256 = "c17fb95a3ac61a8ed77fa13170f0081f0e6cf4966fb747752e9c0afe3b9b5b15",
|
||||
downloaded_file_path = "log4j-api-2.24.2-sources.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "log4j-core-2_24_2-sources_http",
|
||||
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.24.2/log4j-core-2.24.2-sources.jar",
|
||||
sha256 = "ac9d7678ca2550758ddc2034af8f2fb97c36bf8ef2bf71d5ab22a6d3cf04c18e",
|
||||
downloaded_file_path = "log4j-core-2.24.2-sources.jar"
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "protobuf-java-3_24_4-jb_2_http",
|
||||
url = "https://cache-redirector.jetbrains.com/packages.jetbrains.team/maven/p/ij/intellij-dependencies/com/google/protobuf/protobuf-java/3.24.4-jb.2/protobuf-java-3.24.4-jb.2.jar",
|
||||
|
||||
@@ -7,7 +7,8 @@ jvm_test(
|
||||
srcs = glob(["src/**/*.kt", "src/**/*.java"], allow_empty = True),
|
||||
deps = [
|
||||
"@lib//:kotlin-stdlib",
|
||||
"@lib//:platform-build_scripts-code_optimizer-proguard",
|
||||
]
|
||||
"@lib//:proguard",
|
||||
],
|
||||
runtime_deps = ["@lib//:kotlin-metadata"]
|
||||
)
|
||||
### auto-generated section `build intellij.platform.buildScripts.codeOptimizer` end
|
||||
Reference in New Issue
Block a user