1# Copyright (C) 2022 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14load("@bazel_skylib//lib:paths.bzl", "paths")
15
16SystemInfo = provider(fields = ["system", "java_info"])
17
18def _gen_module_info_java(ctx, jars_to_module_info, jars, module_info):
19    ctx.actions.run_shell(
20        inputs = jars,
21        outputs = [module_info],
22        command = "{} java.base {} > {}".format(
23            jars_to_module_info.path,
24            " ".join([jar.path for jar in jars]),
25            module_info.path,
26        ),
27        tools = [jars_to_module_info],
28    )
29
30def _gen_module_info_class(ctx, java_runtime, module_info, java_base_patch_jars, module_info_class):
31    ctx.actions.run_shell(
32        inputs = depset([module_info], transitive = [java_base_patch_jars]),
33        outputs = [module_info_class],
34        tools = java_runtime.files,
35        command = "{} -d {} --system=none --patch-module=java.base={} {}".format(
36            paths.join(java_runtime.java_home, "bin", "javac"),
37            module_info_class.dirname,
38            ":".join([jar.path for jar in java_base_patch_jars.to_list()]),
39            module_info.path,
40        ),
41    )
42
43def _gen_module_info_jar(ctx, soong_zip, module_info_class, module_info_jar):
44    args = ctx.actions.args()
45    args.add("-jar")
46    args.add("--symlinks=false")
47    args.add("-o", module_info_jar)
48    args.add("-C", module_info_class.dirname)
49    args.add("-f", module_info_class)
50    ctx.actions.run(
51        inputs = [module_info_class],
52        outputs = [module_info_jar],
53        arguments = [args],
54        executable = soong_zip,
55    )
56
57def _gen_merged_module_jar(ctx, merge_zips, module_info_jar, jars, merged_module_jar):
58    args = ctx.actions.args()
59    args.add("-j", merged_module_jar)
60    args.add_all(depset([module_info_jar], transitive = [jars]))
61    ctx.actions.run(
62        inputs = depset([module_info_jar], transitive = [jars]),
63        outputs = [merged_module_jar],
64        arguments = [args],
65        executable = merge_zips,
66    )
67
68def _gen_jmod(ctx, java_runtime, merged_module_jar, jmod):
69    ctx.actions.run_shell(
70        inputs = [merged_module_jar],
71        outputs = [jmod],
72        tools = java_runtime.files,
73        command = (
74            "{} create --module-version $({} --version) " +
75            "--target-platform android --class-path {} {}"
76        ).format(
77            paths.join(java_runtime.java_home, "bin", "jmod"),
78            paths.join(java_runtime.java_home, "bin", "jlink"),
79            merged_module_jar.path,
80            jmod.path,
81        ),
82    )
83
84def _gen_system(ctx, java_runtime, jmod, system):
85    ctx.actions.run_shell(
86        inputs = depset([jmod], transitive = [java_runtime.files]),
87        outputs = [system],
88        tools = java_runtime.files,
89        command = (
90            "rm -rf {} && " +
91            "{} --module-path {} --add-modules java.base --output {} " +
92            "--disable-plugin system-modules && " +
93            "cp {} {}/lib/"
94        ).format(
95            system.path,
96            paths.join(java_runtime.java_home, "bin", "jlink"),
97            jmod.dirname,
98            system.path,
99            paths.join(java_runtime.java_home, "lib", "jrt-fs.jar"),
100            system.path,
101        ),
102    )
103
104def _java_system_modules_impl(ctx):
105    java_info = java_common.merge([d[JavaInfo] for d in ctx.attr.deps])
106    module_info = ctx.actions.declare_file("%s/src/module-info.java" % ctx.label.name)
107    _gen_module_info_java(ctx, ctx.executable._jars_to_module_info, java_info.compile_jars.to_list(), module_info)
108
109    java_runtime = ctx.attr._runtime[java_common.JavaRuntimeInfo]
110    module_info_class = ctx.actions.declare_file("%s/class/module-info.class" % ctx.label.name)
111    _gen_module_info_class(ctx, java_runtime, module_info, java_info.compile_jars, module_info_class)
112
113    module_info_jar = ctx.actions.declare_file("%s/jar/classes.jar" % ctx.label.name)
114    _gen_module_info_jar(ctx, ctx.executable._soong_zip, module_info_class, module_info_jar)
115
116    merged_module_jar = ctx.actions.declare_file("%s/merged/module.jar" % ctx.label.name)
117    _gen_merged_module_jar(
118        ctx,
119        ctx.executable._merge_zips,
120        module_info_jar,
121        java_info.full_compile_jars,
122        merged_module_jar,
123    )
124
125    jmod = ctx.actions.declare_file("%s/jmod/java.base.jmod" % ctx.label.name)
126    _gen_jmod(ctx, java_runtime, merged_module_jar, jmod)
127
128    system = ctx.actions.declare_directory("%s/system" % ctx.label.name)
129    _gen_system(ctx, java_runtime, jmod, system)
130
131    return [
132        SystemInfo(
133            system = system,
134            java_info = java_info,
135        ),
136        DefaultInfo(files = depset([system])),
137    ]
138
139java_system_modules = rule(
140    implementation = _java_system_modules_impl,
141    attrs = {
142        "_jars_to_module_info": attr.label(
143            allow_files = True,
144            executable = True,
145            cfg = "exec",
146            default = "//build/soong/scripts:jars-to-module-info-java",
147        ),
148        "_soong_zip": attr.label(
149            cfg = "exec",
150            allow_single_file = True,
151            doc = "The tool soong_zip",
152            default = "//build/soong/zip/cmd:soong_zip",
153            executable = True,
154        ),
155        "_merge_zips": attr.label(
156            cfg = "exec",
157            allow_single_file = True,
158            doc = "The tool merge_zips.",
159            default = "//build/soong/cmd/merge_zips",
160            executable = True,
161        ),
162        "_runtime": attr.label(
163            default = Label("@bazel_tools//tools/jdk:current_java_runtime"),
164            cfg = "exec",
165            providers = [java_common.JavaRuntimeInfo],
166        ),
167        "deps": attr.label_list(
168            providers = [JavaInfo],
169            doc = "Libraries to be converted into a system module directory structure.",
170        ),
171    },
172    doc = """Generates a system module directory from Java libraries.
173
174Starting from version 1.9, Java requires a subset of java.* classes to be
175provided via system modules. This rule encapsulates the set of steps necessary
176to convert a jar file into the directory structure of system modules.
177""",
178)
179