1""" 2Copyright 2023 The Android Open Source Project 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15""" 16 17load("@bazel_skylib//lib:paths.bzl", "paths") 18 19def _remove_extension(p): 20 """Removes the extension from the path `p`. 21 22 Leading periods on the basename are ignored, so 23 `_strip_extension(".bashrc")` returns `".bashrc"`. 24 25 Args: 26 p: The path to modify. 27 28 Returns: 29 The path with the extension removed. 30 """ 31 32 # paths.split_extension() does all of the work. 33 return paths.split_extension(p)[0] 34 35# Expands an output path template for the given context and input file. 36def _expand_out_path_template(ctx, src_file): 37 # Each src_file has a short_path that looks like: 38 # 39 # <source-package-path>/<source-package-rel-path>/<base.ext> 40 # 41 # For some expansions, we want to strip of the source package path, and 42 # only use the rest for output file path in the expansion. 43 # 44 # There is also an option during expansion to just use the <base> or 45 # <base.ext> portion of the input path. 46 # 47 # This means there can be collisions if input files are taken from 48 # `filegroups` defined in different packages, if they happen to use 49 # the same relative path for that package. 50 # 51 # These conflcits are left to the user of this `gensrcs` rule to resolve for 52 # their use case, as at least Bazel will raise an error when they occur. 53 54 # Try to obtain the path to the package that defines `src_file`. It may or 55 # may not be defined by the same package this `gensrcs` rule is in. 56 # The `owner` label `package` attribute value is the closest we can get 57 # to that path, but it may not be correct in all cases, such as if the 58 # source path is itself for a generated file, where the generated file is 59 # under a build artifact path, and not in the source tree. 60 pkg_dirname = paths.dirname(src_file.short_path) 61 rel_dirname = pkg_dirname 62 if (src_file.is_source and src_file.owner and 63 src_file.short_path.startswith(src_file.owner.package + "/")): 64 rel_dirname = paths.dirname(paths.relativize( 65 src_file.short_path, 66 src_file.owner.package, 67 )) 68 69 base_inc_ext = src_file.basename 70 base_exc_ext = _remove_extension(base_inc_ext) 71 rel_path_base_inc_ext = paths.join(rel_dirname, base_inc_ext) 72 rel_path_base_exc_ext = paths.join(rel_dirname, base_exc_ext) 73 pkg_path_base_inc_ext = paths.join(pkg_dirname, base_inc_ext) 74 pkg_path_base_exc_ext = paths.join(pkg_dirname, base_exc_ext) 75 76 # Expand the output template 77 return ctx.attr.output \ 78 .replace("$(SRC:PKG/PATH/BASE.EXT)", pkg_path_base_inc_ext) \ 79 .replace("$(SRC:PKG/PATH/BASE)", pkg_path_base_exc_ext) \ 80 .replace("$(SRC:PATH/BASE.EXT)", rel_path_base_inc_ext) \ 81 .replace("$(SRC:PATH/BASE)", rel_path_base_exc_ext) \ 82 .replace("$(SRC:BASE.EXT)", base_inc_ext) \ 83 .replace("$(SRC:BASE)", base_exc_ext) \ 84 .replace("$(SRC)", rel_path_base_inc_ext) 85 86# A rule to generate files based on provided srcs and tools. 87def _gensrcs_impl(ctx): 88 # The next two assignments can be created by using ctx.resolve_command. 89 # TODO: Switch to using ctx.resolve_command when it is out of 90 # experimental. 91 command = ctx.expand_location(ctx.attr.cmd) 92 tools = [ 93 tool[DefaultInfo].files_to_run 94 for tool in ctx.attr.tools 95 ] 96 97 # Expand the shell command by substituting $(RULEDIR), which will be 98 # the same for any source file. 99 command = command.replace( 100 "$(RULEDIR)", 101 paths.join( 102 ctx.var["GENDIR"], 103 ctx.label.package, 104 ), 105 ) 106 107 src_files = ctx.files.srcs 108 out_files = [] 109 for src_file in src_files: 110 # Expand the output path template for this source file. 111 out_file_path = _expand_out_path_template(ctx, src_file) 112 113 # out_file is at output_file_path that is relative to 114 # <GENDIR>/<gensrc-package-dir>, hence, the fullpath to out_file is 115 # <GENDIR>/<gensrc-package-dir>/<out_file_path> 116 out_file = ctx.actions.declare_file(out_file_path) 117 118 # Expand the command template for this source file by performing 119 # substitution for $(SRC) and $(OUT). 120 shell_command = command \ 121 .replace("$(SRC)", src_file.path) \ 122 .replace("$(OUT)", out_file.path) 123 124 # Run the shell comand to generate the output from the input. 125 ctx.actions.run_shell( 126 tools = tools, 127 outputs = [out_file], 128 inputs = [src_file], 129 command = shell_command, 130 progress_message = "Generating %s from %s" % ( 131 out_file.path, 132 src_file.path, 133 ), 134 ) 135 out_files.append(out_file) 136 137 return [DefaultInfo( 138 files = depset(out_files), 139 )] 140 141gensrcs = rule( 142 implementation = _gensrcs_impl, 143 doc = "This rule generates files, where each of the `srcs` files is " + 144 "passed to `cmd` to generate an `output`.", 145 attrs = { 146 "srcs": attr.label_list( 147 # We allow srcs to directly reference files, instead of only 148 # allowing references to other rules such as filegroups. 149 allow_files = True, 150 # An empty srcs is likely an mistake. 151 allow_empty = False, 152 # srcs must be explicitly specified. 153 mandatory = True, 154 doc = "A list of source files to process", 155 ), 156 "output": attr.string( 157 # By default we generate an output filename based on the input 158 # filename (no extension). 159 default = "$(SRC)", 160 doc = "An output path template which is expanded to generate " + 161 "the output path given an source file. Portions " + 162 "of the source filename can be included in the expansion " + 163 "with one of: $(SRC:BASE), $(SRC:BASE.EXT), " + 164 "$(SRC:PATH/BASE), $(SRC:PATH/BASE), " + 165 "$(SRC:PKG/PATH/BASE), or $(SRC:PKG/PATH/BASE.ext). For " + 166 "example, specifying `output = " + 167 "\"includes/lib/$(SRC:BASE).h\"` would mean the input " + 168 "file `some_path/to/a.txt` generates `includes/lib/a.h`, " + 169 "while instead specifying `output = " + 170 "\"includes/lib/$(SRC:PATH/BASE.EXT).h\"` would expand " + 171 "to `includes/lib/some_path/to/a.txt.h`.", 172 ), 173 "cmd": attr.string( 174 # cmd must be explicitly specified. 175 mandatory = True, 176 doc = "The command to run. Subject to $(location) expansion. " + 177 "$(SRC) represents each input file provided in `srcs` " + 178 "while $(OUT) reprensents corresponding output file " + 179 "generated by the rule. $(RULEDIR) is intepreted the same " + 180 "as it is in genrule.", 181 ), 182 "tools": attr.label_list( 183 # We allow tools to directly reference files, as there could be a local script 184 # used as a tool. 185 allow_files = True, 186 doc = "A list of tool dependencies for this rule. " + 187 "The path of an individual `tools` target //x:y can be " + 188 "obtained using `$(location //x:y)`", 189 cfg = "exec", 190 ), 191 }, 192) 193