Skip to content

Commit 100aa95

Browse files
l46kokcopybara-github
authored andcommitted
Add CelCompilerTool java binary and compile_cel bzl library
PiperOrigin-RevId: 726254974
1 parent ba20bb2 commit 100aa95

File tree

11 files changed

+406
-0
lines changed

11 files changed

+406
-0
lines changed

WORKSPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ maven_install(
140140
"com.google.truth.extensions:truth-java8-extension:1.4.4",
141141
"com.google.truth.extensions:truth-proto-extension:1.4.4",
142142
"com.google.truth:truth:1.4.4",
143+
"info.picocli:picocli:4.7.6",
143144
"org.antlr:antlr4-runtime:" + ANTLR4_VERSION,
144145
"org.jspecify:jspecify:1.0.0",
145146
"org.threeten:threeten-extra:1.8.0",

common/annotations/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ package(
77

88
java_library(
99
name = "annotations",
10+
# used_by_android
1011
exports = ["//common/src/main/java/dev/cel/common/annotations"],
1112
)

common/internal/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ cel_android_library(
2323

2424
java_library(
2525
name = "converter",
26+
# used_by_android
2627
exports = ["//common/src/main/java/dev/cel/common/internal:converter"],
2728
)
2829

@@ -78,6 +79,7 @@ java_library(
7879

7980
java_library(
8081
name = "safe_string_formatter",
82+
# used_by_android
8183
exports = ["//common/src/main/java/dev/cel/common/internal:safe_string_formatter"],
8284
)
8385

common/src/main/java/dev/cel/common/annotations/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ ANNOTATION_SOURCES = [
1717
java_library(
1818
name = "annotations",
1919
srcs = ANNOTATION_SOURCES,
20+
# used_by_android
2021
tags = [
2122
],
2223
)

common/src/main/java/dev/cel/common/internal/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ java_library(
100100
"BidiConverter.java",
101101
"Converter.java",
102102
],
103+
# used_by_android
103104
tags = [
104105
],
105106
deps = [
@@ -264,6 +265,7 @@ java_library(
264265
java_library(
265266
name = "safe_string_formatter",
266267
srcs = ["SafeStringFormatter.java"],
268+
# used_by_android
267269
tags = [
268270
],
269271
deps = [
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
load("@rules_java//java:defs.bzl", "java_binary")
2+
3+
package(
4+
default_applicable_licenses = [
5+
"//:license",
6+
],
7+
default_visibility = [
8+
"//compiler/tools:__pkg__",
9+
],
10+
)
11+
12+
java_binary(
13+
name = "cel_compiler_tool",
14+
srcs = ["CelCompilerTool.java"],
15+
main_class = "dev.cel.compiler.tools.CelCompilerTool",
16+
neverlink = 1,
17+
deps = [
18+
"//common",
19+
"//common:compiler_common",
20+
"//common:options",
21+
"//common:proto_ast",
22+
"//compiler",
23+
"//compiler:compiler_builder",
24+
"//extensions",
25+
"//extensions:optional_library",
26+
"//parser:macro",
27+
"@cel_spec//proto/cel/expr:checked_java_proto",
28+
"@maven//:com_google_guava_guava",
29+
"@maven//:com_google_protobuf_protobuf_java",
30+
"@maven//:info_picocli_picocli",
31+
],
32+
)
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright 2025 Google LLC
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+
// https://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.
14+
15+
package dev.cel.compiler.tools;
16+
17+
import dev.cel.expr.CheckedExpr;
18+
import com.google.common.collect.ImmutableSet;
19+
import com.google.common.io.Files;
20+
import com.google.protobuf.DescriptorProtos.FileDescriptorSet;
21+
import com.google.protobuf.Descriptors.FileDescriptor;
22+
import com.google.protobuf.ExtensionRegistry;
23+
import dev.cel.common.CelAbstractSyntaxTree;
24+
import dev.cel.common.CelDescriptorUtil;
25+
import dev.cel.common.CelOptions;
26+
import dev.cel.common.CelProtoAbstractSyntaxTree;
27+
import dev.cel.common.CelValidationException;
28+
import dev.cel.compiler.CelCompiler;
29+
import dev.cel.compiler.CelCompilerBuilder;
30+
import dev.cel.compiler.CelCompilerFactory;
31+
import dev.cel.extensions.CelExtensions;
32+
import dev.cel.extensions.CelOptionalLibrary;
33+
import dev.cel.parser.CelStandardMacro;
34+
import java.io.File;
35+
import java.io.FileOutputStream;
36+
import java.io.IOException;
37+
import java.nio.file.Path;
38+
import java.nio.file.Paths;
39+
import java.util.concurrent.Callable;
40+
import picocli.CommandLine;
41+
import picocli.CommandLine.Option;
42+
43+
/**
44+
* CelCompilerTool is a binary that takes a CEL expression in string, compiles it into a
45+
* dev.cel.expr.CheckedExpr protobuf message, then writes the content to a .binary pb file.
46+
*/
47+
final class CelCompilerTool implements Callable<Integer> {
48+
49+
@Option(
50+
names = {"--cel_expression"},
51+
description = "CEL expression")
52+
private String celExpression = "";
53+
54+
@Option(
55+
names = {"--transitive_descriptor_set"},
56+
description = "Path to the transitive set of descriptors")
57+
private String transitiveDescriptorSetPath = "";
58+
59+
@Option(
60+
names = {"--output"},
61+
description = "Output path for the compiled binarypb")
62+
private String output = "";
63+
64+
private static final CelOptions CEL_OPTIONS = CelOptions.DEFAULT;
65+
66+
private static FileDescriptorSet load(String descriptorSetPath) {
67+
try {
68+
byte[] descriptorBytes = Files.toByteArray(new File(descriptorSetPath));
69+
return FileDescriptorSet.parseFrom(descriptorBytes, ExtensionRegistry.getEmptyRegistry());
70+
} catch (IOException e) {
71+
throw new IllegalArgumentException(
72+
"Failed to load FileDescriptorSet from path: " + descriptorSetPath, e);
73+
}
74+
}
75+
76+
private static CelAbstractSyntaxTree compile(
77+
String expression, String transitiveDescriptorSetPath) throws CelValidationException {
78+
CelCompilerBuilder compilerBuilder =
79+
CelCompilerFactory.standardCelCompilerBuilder()
80+
.setOptions(CEL_OPTIONS)
81+
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
82+
.addLibraries(
83+
CelExtensions.bindings(),
84+
CelExtensions.encoders(),
85+
CelExtensions.math(CEL_OPTIONS),
86+
CelExtensions.lists(),
87+
CelExtensions.protos(),
88+
CelExtensions.strings(),
89+
CelOptionalLibrary.INSTANCE);
90+
if (!transitiveDescriptorSetPath.isEmpty()) {
91+
ImmutableSet<FileDescriptor> transitiveFileDescriptors =
92+
CelDescriptorUtil.getFileDescriptorsFromFileDescriptorSet(
93+
load(transitiveDescriptorSetPath));
94+
compilerBuilder.addFileTypes(transitiveFileDescriptors);
95+
}
96+
97+
CelCompiler celCompiler = compilerBuilder.build();
98+
99+
return celCompiler.compile(expression).getAst();
100+
}
101+
102+
private static void writeCheckedExpr(CelAbstractSyntaxTree ast, String filePath)
103+
throws IOException {
104+
CheckedExpr checkedExpr = CelProtoAbstractSyntaxTree.fromCelAst(ast).toCheckedExpr();
105+
Path path = Paths.get(filePath);
106+
try (FileOutputStream output = new FileOutputStream(path.toFile())) {
107+
checkedExpr.writeTo(output);
108+
}
109+
}
110+
111+
@Override
112+
public Integer call() {
113+
try {
114+
CelAbstractSyntaxTree ast = compile(celExpression, transitiveDescriptorSetPath);
115+
writeCheckedExpr(ast, output);
116+
} catch (Exception e) {
117+
String errorMessage =
118+
String.format(
119+
"Expression [%s] failed to compile. Reason: %s", celExpression, e.getMessage());
120+
System.err.print(errorMessage);
121+
return -1;
122+
}
123+
124+
return 0;
125+
}
126+
127+
public static void main(String[] args) {
128+
CelCompilerTool compilerTool = new CelCompilerTool();
129+
CommandLine cmd = new CommandLine(compilerTool);
130+
cmd.setTrimQuotes(false);
131+
cmd.parseArgs(args);
132+
133+
int exitCode = cmd.execute(args);
134+
System.exit(exitCode);
135+
}
136+
137+
CelCompilerTool() {}
138+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
load("@rules_java//java:defs.bzl", "java_library")
2+
load("//:testing.bzl", "junit4_test_suites")
3+
load("//compiler/tools:compile_cel.bzl", "compile_cel")
4+
5+
package(default_applicable_licenses = [
6+
"//:license",
7+
])
8+
9+
compile_cel(
10+
name = "compiled_hello_world",
11+
expression = "'hello world'",
12+
)
13+
14+
compile_cel(
15+
name = "compiled_comprehension",
16+
expression = "[1,2,3].map(x, x + 1)",
17+
)
18+
19+
compile_cel(
20+
name = "compiled_proto_message",
21+
expression = "cel.expr.conformance.proto2.TestAllTypes{single_int32: 1}",
22+
proto_srcs = ["@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_proto"],
23+
)
24+
25+
compile_cel(
26+
name = "compiled_extensions",
27+
expression = "cel.bind(x, 10, math.greatest([1,x])) < int(' 11 '.trim()) && optional.none().orValue(true) && [].flatten() == []",
28+
proto_srcs = ["@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_proto"],
29+
)
30+
31+
filegroup(
32+
name = "compiled_exprs",
33+
srcs = [
34+
":compiled_comprehension",
35+
":compiled_extensions",
36+
":compiled_hello_world",
37+
":compiled_proto_message",
38+
],
39+
)
40+
41+
java_library(
42+
name = "tests",
43+
testonly = True,
44+
srcs = glob(["*Test.java"]),
45+
resources = [":compiled_exprs"],
46+
deps = [
47+
"//:java_truth",
48+
"//common:cel_ast",
49+
"//common:options",
50+
"//common:proto_ast",
51+
"//extensions",
52+
"//extensions:optional_library",
53+
"//runtime",
54+
"@cel_spec//proto/cel/expr:checked_java_proto",
55+
"@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto",
56+
"@maven//:com_google_guava_guava",
57+
"@maven//:com_google_protobuf_protobuf_java",
58+
"@maven//:junit_junit",
59+
],
60+
)
61+
62+
junit4_test_suites(
63+
name = "test_suites",
64+
sizes = [
65+
"small",
66+
],
67+
src_dir = "src/test/java",
68+
deps = [":tests"],
69+
)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2025 Google LLC
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+
// https://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.
14+
15+
package dev.cel.compiler.tools;
16+
17+
import static com.google.common.truth.Truth.assertThat;
18+
19+
import dev.cel.expr.CheckedExpr;
20+
import com.google.common.io.Resources;
21+
import com.google.protobuf.ExtensionRegistryLite;
22+
import dev.cel.common.CelAbstractSyntaxTree;
23+
import dev.cel.common.CelOptions;
24+
import dev.cel.common.CelProtoAbstractSyntaxTree;
25+
import dev.cel.expr.conformance.proto2.TestAllTypes;
26+
import dev.cel.extensions.CelExtensions;
27+
import dev.cel.extensions.CelOptionalLibrary;
28+
import dev.cel.runtime.CelRuntime;
29+
import dev.cel.runtime.CelRuntimeFactory;
30+
import java.net.URL;
31+
import java.util.List;
32+
import org.junit.Test;
33+
import org.junit.runner.RunWith;
34+
import org.junit.runners.JUnit4;
35+
36+
@RunWith(JUnit4.class)
37+
public class CelCompilerToolTest {
38+
private static final CelRuntime CEL_RUNTIME =
39+
CelRuntimeFactory.standardCelRuntimeBuilder()
40+
.addLibraries(
41+
CelExtensions.encoders(),
42+
CelExtensions.math(CelOptions.DEFAULT),
43+
CelExtensions.lists(),
44+
CelExtensions.strings(),
45+
CelOptionalLibrary.INSTANCE)
46+
.addMessageTypes(TestAllTypes.getDescriptor())
47+
.build();
48+
49+
@Test
50+
public void compiledCheckedExpr_string() throws Exception {
51+
CelAbstractSyntaxTree ast = readCheckedExpr("compiled_hello_world");
52+
53+
String result = (String) CEL_RUNTIME.createProgram(ast).eval();
54+
assertThat(result).isEqualTo("hello world");
55+
}
56+
57+
@Test
58+
@SuppressWarnings("unchecked")
59+
public void compiledCheckedExpr_comprehension() throws Exception {
60+
CelAbstractSyntaxTree ast = readCheckedExpr("compiled_comprehension");
61+
62+
List<Long> result = (List<Long>) CEL_RUNTIME.createProgram(ast).eval();
63+
assertThat(result).containsExactly(2L, 3L, 4L).inOrder();
64+
}
65+
66+
@Test
67+
public void compiledCheckedExpr_protoMessage() throws Exception {
68+
CelAbstractSyntaxTree ast = readCheckedExpr("compiled_proto_message");
69+
70+
TestAllTypes result = (TestAllTypes) CEL_RUNTIME.createProgram(ast).eval();
71+
assertThat(result).isEqualTo(TestAllTypes.newBuilder().setSingleInt32(1).build());
72+
}
73+
74+
@Test
75+
public void compiledCheckedExpr_extensions() throws Exception {
76+
CelAbstractSyntaxTree ast = readCheckedExpr("compiled_extensions");
77+
78+
assertThat(CEL_RUNTIME.createProgram(ast).eval()).isEqualTo(true);
79+
}
80+
81+
private static CelAbstractSyntaxTree readCheckedExpr(String compiledCelTarget) throws Exception {
82+
URL url = Resources.getResource(CelCompilerToolTest.class, compiledCelTarget + ".binarypb");
83+
byte[] checkedExprBytes = Resources.toByteArray(url);
84+
CheckedExpr checkedExpr =
85+
CheckedExpr.parseFrom(checkedExprBytes, ExtensionRegistryLite.getEmptyRegistry());
86+
return CelProtoAbstractSyntaxTree.fromCheckedExpr(checkedExpr).getAst();
87+
}
88+
}

compiler/tools/BUILD.bazel

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package(
2+
default_applicable_licenses = ["//:license"],
3+
default_visibility = ["//visibility:public"],
4+
)
5+
6+
alias(
7+
name = "cel_compiler_tool",
8+
actual = "//compiler/src/main/java/dev/cel/compiler/tools:cel_compiler_tool",
9+
)

0 commit comments

Comments
 (0)