From ad856bd6350d06efc709ef1f07576db211712589 Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Mon, 3 Nov 2025 16:08:27 -0700 Subject: [PATCH 1/2] Add closure_ts_archive rule --- rules/defs.bzl | 2 + rules/private/closure_ts_archive.bzl | 78 ++++++++++++++++++++++++++++ rules/private/closure_ts_compile.bzl | 45 ++++++++-------- 3 files changed, 102 insertions(+), 23 deletions(-) create mode 100644 rules/private/closure_ts_archive.bzl diff --git a/rules/defs.bzl b/rules/defs.bzl index b26c9b3..374c1f5 100644 --- a/rules/defs.bzl +++ b/rules/defs.bzl @@ -1,3 +1,5 @@ load("//rules/private:closure_ts_compile.bzl", _closure_ts_compile = "closure_ts_compile") +load("//rules/private:closure_ts_archive.bzl", _closure_ts_archive = "closure_ts_archive") closure_ts_compile = _closure_ts_compile +closure_ts_archive = _closure_ts_archive diff --git a/rules/private/closure_ts_archive.bzl b/rules/private/closure_ts_archive.bzl new file mode 100644 index 0000000..f2aea22 --- /dev/null +++ b/rules/private/closure_ts_archive.bzl @@ -0,0 +1,78 @@ +"""closure_ts_archive builds zip files of closure js sources""" + +load(":closure_ts_compile.bzl", "ClosureTsCompileInfo") + +def _get_zip_runfiles_path(path): + if path.startswith("../"): + parts = path.split("/") + path = "/".join(parts[2:]) + return path + +def _zip_action(ctx): + srcs = ctx.files.srcs + + transitive_outs = [dep[ClosureTsCompileInfo].transitive_outs for dep in ctx.attr.deps] + direct_outs = [dep[ClosureTsCompileInfo].direct_outs for dep in ctx.attr.deps] + if ctx.attr.transitive: + inputs = depset(srcs, transitive = transitive_outs) + else: + inputs = depset(srcs, transitive = direct_outs) + + def map_zip_runfiles(file): + return "{}={}".format( + _get_zip_runfiles_path(file.short_path), + file.path, + ) + + manifest = ctx.actions.args() + manifest.use_param_file("@%s", use_always = True) + manifest.set_param_file_format("multiline") + manifest.add_all(inputs, map_each = map_zip_runfiles, allow_closure = True) + + zip_cli_args = ctx.actions.args() + zip_cli_args.add("cC") + zip_cli_args.add(ctx.outputs.zip) + + ctx.actions.run( + executable = ctx.executable._zipper, + arguments = [zip_cli_args, manifest], + inputs = inputs, + outputs = [ctx.outputs.zip], + use_default_shell_env = True, + mnemonic = "ClosureTsZip", + progress_message = "Building zip: %{label}", + ) + +def _closure_ts_archive_impl(ctx): + _zip_action(ctx) + + return [ + DefaultInfo( + files = depset([ctx.outputs.zip]), + ), + ] + +closure_ts_archive = rule( + implementation = _closure_ts_archive_impl, + attrs = { + "srcs": attr.label_list( + doc = "additional sources to add to the archive", + allow_files = True, + ), + "deps": attr.label_list( + doc = "closure ts compile dependencies to archive", + providers = [ClosureTsCompileInfo], + ), + "transitive": attr.bool( + doc = "archive transitive dependencies into a single archive", + ), + "_zipper": attr.label( + cfg = "exec", + executable = True, + default = "@bazel_tools//tools/zip:zipper", + ), + }, + outputs = { + "zip": "%{name}.js.zip", + }, +) diff --git a/rules/private/closure_ts_compile.bzl b/rules/private/closure_ts_compile.bzl index 989442f..dc2edeb 100644 --- a/rules/private/closure_ts_compile.bzl +++ b/rules/private/closure_ts_compile.bzl @@ -3,7 +3,10 @@ load("@bazel_skylib//lib:paths.bzl", "paths") ClosureTsCompileInfo = provider(doc = "info about a closure ts compile rule", fields = { - "sources": "DepSet: direct + transitive sources", + "direct_srcs": "DepSet: direct sources", + "transitive_srcs": "DepSet: transitive sources", + "direct_outs": "DepSet: direct outputs", + "transitive_outs": "DepSet: transitive outputs", }) def _output_for_ts_input(ctx, src): @@ -13,58 +16,54 @@ def _output_for_ts_input(ctx, src): def _output_for_externs(ctx): return ctx.actions.declare_file(ctx.label.name + ".externs.js") -def _tsickle_action(ctx): - direct = ctx.files.srcs - transitive = [dep[ClosureTsCompileInfo].sources for dep in ctx.attr.deps] +def _closure_ts_compile_impl(ctx): + direct_srcs = ctx.files.srcs + transitive_srcs = [dep[ClosureTsCompileInfo].transitive_srcs for dep in ctx.attr.deps] + transitive_outs = [dep[ClosureTsCompileInfo].transitive_outs for dep in ctx.attr.deps] args = ctx.actions.args() - args.add_all([src.short_path for src in direct]) + args.add_all(direct_srcs) env = { "BAZEL_BINDIR": ctx.bin_dir.path, - "BAZEL_WORKSPACE": ctx.label.workspace_name, "BAZEL_PACKAGE": ctx.label.package, } direct_ts = [] direct_dts = [] - for src in direct: + for src in direct_srcs: if src.basename.endswith(".d.ts"): direct_dts.append(src) else: direct_ts.append(src) - outputs = [_output_for_ts_input(ctx, f) for f in direct_ts] + direct_outs = [_output_for_ts_input(ctx, f) for f in direct_ts] if len(direct_dts) > 0: externs_output = _output_for_externs(ctx) - outputs.append(externs_output) - env["EXTERNS_PATH"] = externs_output.short_path + direct_outs.append(externs_output) + env["EXTERNS_PATH"] = externs_output.short_path.replace("../", "external/") - inputs = depset(direct = direct, transitive = transitive) + all_srcs = depset(direct = direct_srcs, transitive = transitive_srcs) + all_outs = depset(direct = direct_outs, transitive = transitive_outs) ctx.actions.run( mnemonic = "TsickleCompile", executable = ctx.executable._compiler, arguments = [args], - inputs = inputs, - outputs = outputs, + inputs = all_srcs, + outputs = direct_outs, env = env, ) - return struct( - inputs = inputs, - outputs = outputs, - ) - -def _closure_ts_compile_impl(ctx): - result = _tsickle_action(ctx) - return [ DefaultInfo( - files = depset(result.outputs), + files = depset(direct_outs), ), ClosureTsCompileInfo( - sources = result.inputs, + direct_srcs = direct_srcs, + transitive_srcs = all_srcs, + direct_outs = depset(direct_outs), + transitive_outs = all_outs, ), ] From 2ac058e90bb33f85d2493883ab32703df2b9d7f6 Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Mon, 3 Nov 2025 16:08:47 -0700 Subject: [PATCH 2/2] fix: strip external for moduleName --- tools/tsicklecompiler/index.ts | 45 ++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/tools/tsicklecompiler/index.ts b/tools/tsicklecompiler/index.ts index 42c8221..4f75899 100644 --- a/tools/tsicklecompiler/index.ts +++ b/tools/tsicklecompiler/index.ts @@ -53,13 +53,35 @@ function getTsCompilerOptions(): ts.CompilerOptions { }; } +/** + * stripExternalName is used to remove external workspace identifiers from bazel + * paths. This allows the filenames + * BAZEL_BIN/external/io_bazel_rules_closure+/google3/third_party/javascript/safevalues/builders/html_sanitizer/sanitizer_table/sanitizer_table.js + * and + * BAZEL_BIN/google3/third_party/javascript/safevalues/builders/html_sanitizer/sanitizer_table/sanitizer_table.js + * to generate the same moduleName. + * + * @param moduleName the module name to strip + * @returns + */ +function stripExternalName(moduleName: string): string { + if (!moduleName.startsWith('external.')) { + return moduleName; + } + const parts = moduleName.split('.'); + if (parts.length <= 2) { + return moduleName; + } + return parts.slice(2).join('.'); +} + function run( options: ts.CompilerOptions, fileNames: string[], writeFile: ts.WriteFileCallback, ): tsickle.EmitResult { - // Use absolute paths to determine what files to process since files may be imported using - // relative or absolute paths + // Use absolute paths to determine what files to process since files may be + // imported using relative or absolute paths fileNames = fileNames.map(i => path.resolve(i)); const compilerHost = ts.createCompilerHost(options); @@ -73,9 +95,20 @@ function run( return !filesToProcess.has(path.resolve(fileName)); }, shouldIgnoreWarningsForPath: (fileName: string) => false, - pathToModuleName: (context, fileName) => - tsickle.pathToModuleName(rootModulePath, context, fileName), - fileNameToModuleId: (fileName) => path.relative(rootModulePath, fileName), + pathToModuleName: (context, fileName) => { + const moduleName = stripExternalName(tsickle.pathToModuleName(rootModulePath, context, fileName)); + if (DEBUG) { + console.log(`pathToModuleName: ${fileName} => ${moduleName}`); + } + return moduleName; + }, + fileNameToModuleId: (fileName) => { + const moduleId = path.relative(rootModulePath, fileName); + if (DEBUG) { + console.log(`fileNameToModuleId: ${fileName} => ${moduleId}`); + } + return moduleId; + }, googmodule: true, transformDecorators: true, transformTypesToClosure: true, @@ -109,8 +142,6 @@ async function main() { const execRoot = getExecRoot(); const externsPath = getExternsPath(); - await listExecrootFiles(); - const args = process.argv.slice(2); const inputFiles = getInputFiles(execRoot, args); if (inputFiles.length === 0) {