Skip to content

Commit 53181aa

Browse files
michalgrwekesa360
authored andcommitted
Add jdwp streams implemented abstract class
[jdwp] make jdwp schema immutable and hashable [jdwp] make struct serialization synchronous Codegen dataclassess (facebookexperimental#79) * Wrote test to validate primitive type mapping * Added test for type alias definition * Built dataclasses code generator * Built test for dataclassess code generator * Changed to unittest * Updated struct dataclass function to support all types * feat: built dataclass generator * Added copyright * Added nested struct support * Added test for nested struct * Feat: update functions and put it in a class. * chore: update tests * Casted types to pass pyre check * Updated types * chore: Refactor typing.cast code * Added copyright * refactor: update tests to reflect new schema changes * fix: Fix pyre typing errors [jdwp] do not emit array length and union tag fields [jdwp] map union tags and array lengths to int [jdwp] fix type for union fields [jdwp] fix names of fields containing spaces [jdwp] generate projects.jdwp.runtime.structs [jdwp] add tests importing components of the runtime library implement async abstract methods, add TCP conn update files update conn and streams add buck deps
1 parent aaf01d1 commit 53181aa

17 files changed

+715
-59
lines changed

projects/jdwp/BUCK

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
python_binary(
44
name = "main",
55
main = "main.py",
6-
deps = [],
6+
deps = [":lib"],
77
)
88

99

1010
python_library(
1111
name = "lib",
1212
srcs = glob(["**/*.py"]),
1313
visibility = ["PUBLIC"],
14+
deps = [ "//projects/jdwp/runtime:runtime"]
1415
)

projects/jdwp/codegen/BUCK

+9
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ python_binary(
99
visibility=["//projects/jdwp/runtime/..."],
1010
)
1111

12+
python_binary(
13+
name="generate-dataclasses",
14+
main_module="projects.jdwp.codegen.dataclass_generator",
15+
deps=[
16+
":codegen",
17+
],
18+
visibility=["//projects/jdwp/runtime/..."],
19+
)
20+
1221
python_library(
1322
name="codegen",
1423
srcs=glob(["**/*.py"]),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
3+
import enum
4+
from textwrap import dedent
5+
from projects.jdwp.codegen.types import python_type_for
6+
import typing
7+
8+
from projects.jdwp.defs.schema import (
9+
Array,
10+
ArrayLength,
11+
Command,
12+
CommandSet,
13+
Field,
14+
Struct,
15+
TaggedUnion,
16+
UnionTag,
17+
)
18+
19+
20+
StructLink = typing.Tuple[Struct, Field, Struct]
21+
22+
23+
class StructGenerator:
24+
def __init__(self, root: Struct, name: str):
25+
self.__root = root
26+
self.__struct_to_name = compute_struct_names(root, name)
27+
28+
def __get_python_type_for(self, struct: Struct, field: Field) -> str:
29+
type = field.type
30+
match type:
31+
case Struct():
32+
return self.__struct_to_name[type]
33+
case Array():
34+
array_type = typing.cast(Array, type)
35+
return f"typing.List[{self.__struct_to_name[array_type.element_type]}]"
36+
case TaggedUnion():
37+
tagged_union_type = typing.cast(TaggedUnion, type)
38+
union_types = [
39+
self.__struct_to_name[case_struct]
40+
for (_, case_struct) in tagged_union_type.cases
41+
]
42+
union_types_str = ", ".join(union_types)
43+
return f"typing.Union[{union_types_str}]"
44+
case _:
45+
return python_type_for(type)
46+
47+
def __is_explicit_field(self, field: Field) -> bool:
48+
return not isinstance(field.type, (ArrayLength, UnionTag))
49+
50+
def __get_field_name(self, field: Field) -> str:
51+
words = field.name.split(" ")
52+
words = [words[0]] + [word.capitalize() for word in words[1:]]
53+
return "".join(words)
54+
55+
def __generate_dataclass(self, struct: Struct) -> str:
56+
name = self.__struct_to_name[struct]
57+
fields_def = "\n".join(
58+
f" {self.__get_field_name(field)}: {self.__get_python_type_for(struct, field)}"
59+
for field in struct.fields
60+
if self.__is_explicit_field(field)
61+
)
62+
class_def = f"@dataclasses.dataclass(frozen=True)\nclass {name}:\n{fields_def}"
63+
return dedent(class_def)
64+
65+
def generate(self) -> typing.Generator[str, None, None]:
66+
for _, _, nested in reversed(list(nested_structs(self.__root))):
67+
yield self.__generate_dataclass(nested)
68+
yield self.__generate_dataclass(self.__root)
69+
70+
71+
def format_enum_name(enum_value: enum.Enum) -> str:
72+
words = enum_value.name.split("_")
73+
formatted_name = "".join(word.capitalize() for word in words)
74+
return f"{formatted_name}Type"
75+
76+
77+
def nested_structs(root: Struct) -> typing.Generator[StructLink, None, None]:
78+
for field in root.fields:
79+
field_type = field.type
80+
match field_type:
81+
case Array():
82+
array_type = typing.cast(Array, field_type)
83+
yield root, field, array_type.element_type
84+
yield from nested_structs(array_type.element_type)
85+
case TaggedUnion():
86+
tagged_union_type = typing.cast(TaggedUnion, field_type)
87+
for _, struct in tagged_union_type.cases:
88+
yield root, field, struct
89+
yield from nested_structs(struct)
90+
case Struct():
91+
yield root, field, field_type
92+
yield from nested_structs(field_type)
93+
94+
95+
def compute_struct_names(root: Struct, name: str) -> typing.Mapping[Struct, str]:
96+
names = {root: name}
97+
for parent, field, nested in nested_structs(root):
98+
sanitized_field_name = "".join(
99+
word.capitalize() for word in field.name.split(" ")
100+
)
101+
type = field.type
102+
match type:
103+
case Struct():
104+
names[nested] = f"{names[parent]}{sanitized_field_name}"
105+
case Array():
106+
names[nested] = f"{names[parent]}{sanitized_field_name}Element"
107+
case TaggedUnion():
108+
tagged_union_type = typing.cast(TaggedUnion, type)
109+
for case_value, case_struct in tagged_union_type.cases:
110+
case_name = format_enum_name(case_value)
111+
names[
112+
case_struct
113+
] = f"{names[parent]}{sanitized_field_name}Case{case_name}"
114+
return names
115+
116+
117+
def generate_for_command(command: Command) -> typing.Generator[str, None, None]:
118+
if command.out:
119+
yield from StructGenerator(command.out, f"{command.name}Out").generate()
120+
if command.reply:
121+
yield from StructGenerator(command.reply, f"{command.name}Reply").generate()
122+
123+
124+
def generate_for_command_set(
125+
command_set: CommandSet,
126+
) -> typing.Generator[str, None, None]:
127+
for command in command_set.commands:
128+
yield from generate_for_command(command)
129+
130+
131+
def generate_for_all_command_sets() -> typing.Generator[str, None, None]:
132+
# TODO: refactor this once PR90 is merged
133+
from projects.jdwp.defs.command_sets.virtual_machine import VirtualMachine
134+
from projects.jdwp.defs.command_sets.reference_type import ReferenceType
135+
from projects.jdwp.defs.command_sets.event_request import EventRequest
136+
137+
yield from generate_for_command_set(VirtualMachine)
138+
yield from generate_for_command_set(ReferenceType)
139+
yield from generate_for_command_set(EventRequest)
140+
141+
142+
def main():
143+
print("import dataclasses")
144+
print("import typing")
145+
print("from projects.jdwp.runtime.type_aliases import *")
146+
147+
for struct_definition in generate_for_all_command_sets():
148+
print()
149+
print(struct_definition)
150+
151+
152+
if __name__ == "__main__":
153+
main()

projects/jdwp/codegen/types.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# Copyright (c) Meta Platforms, Inc. and affiliates.
22

3-
from projects.jdwp.defs.schema import OpaqueType, IdType, IntegralType, Type
3+
from projects.jdwp.defs.schema import (
4+
ArrayLength,
5+
OpaqueType,
6+
IdType,
7+
IntegralType,
8+
Type,
9+
UnionTag,
10+
)
411
import typing
512

613

@@ -17,7 +24,7 @@ def python_type_for(jdwp_type: Type) -> str:
1724
return __OPAQUE_TYPE_MAPPING[jdwp_type]
1825
case IdType():
1926
return jdwp_type.value[0].upper() + jdwp_type.value[1:] + "Type"
20-
case IntegralType():
27+
case IntegralType() | ArrayLength() | UnionTag():
2128
return "int"
2229
case _:
2330
raise Exception("not implemented")

projects/jdwp/defs/command_sets/event_request.py

+14-14
Original file line numberDiff line numberDiff line change
@@ -81,20 +81,20 @@
8181
"Modifier cases",
8282
TaggedUnion(
8383
__SetCommand_out_modKind,
84-
{
85-
ModifierKind.COUNT: CountModifier,
86-
ModifierKind.CONDITIONAL: ConditionalModifier,
87-
ModifierKind.THREAD_ONLY: ThreadOnlyModifier,
88-
ModifierKind.CLASS_ONLY: ClassOnlyModifier,
89-
ModifierKind.CLASS_MATCH: ClassMatchModifier,
90-
ModifierKind.CLASS_EXCLUDE: ClassExcludeModifier,
91-
ModifierKind.STEP: StepModifier,
92-
ModifierKind.LOCATION_ONLY: LocationOnlyModifier,
93-
ModifierKind.EXCEPTION_ONLY: ExceptionOnlyModifier,
94-
ModifierKind.FIELD_ONLY: FieldOnlyModifier,
95-
ModifierKind.INSTANCE_ONLY: InstanceOnlyModifier,
96-
ModifierKind.SOURCE_NAME_MATCH: SourceNameMatchModifier,
97-
},
84+
[
85+
(ModifierKind.COUNT, CountModifier),
86+
(ModifierKind.CONDITIONAL, ConditionalModifier),
87+
(ModifierKind.THREAD_ONLY, ThreadOnlyModifier),
88+
(ModifierKind.CLASS_ONLY, ClassOnlyModifier),
89+
(ModifierKind.CLASS_MATCH, ClassMatchModifier),
90+
(ModifierKind.CLASS_EXCLUDE, ClassExcludeModifier),
91+
(ModifierKind.STEP, StepModifier),
92+
(ModifierKind.LOCATION_ONLY, LocationOnlyModifier),
93+
(ModifierKind.EXCEPTION_ONLY, ExceptionOnlyModifier),
94+
(ModifierKind.FIELD_ONLY, FieldOnlyModifier),
95+
(ModifierKind.INSTANCE_ONLY, InstanceOnlyModifier),
96+
(ModifierKind.SOURCE_NAME_MATCH, SourceNameMatchModifier),
97+
],
9898
),
9999
"Modifier cases.",
100100
)

projects/jdwp/defs/schema.py

+15-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from __future__ import annotations
66
from dataclasses import dataclass
7-
from typing import Optional, Generic, TypeVar, Type as TypeAlias, Union
7+
from typing import Optional, Generic, Tuple, TypeVar, Type as TypeAlias, Union
88
from collections.abc import Mapping
99
from enum import Enum
1010
from collections.abc import Set, Sequence
@@ -41,7 +41,7 @@ class IdType(Enum):
4141
OBJECT_ID = "objectID"
4242
THREAD_ID = "threadID"
4343
THREAD_GROUP_ID = "threadGroupID"
44-
STRING_ID = "stringId"
44+
STRING_ID = "stringID"
4545
CLASS_LOADER_ID = "classLoaderID"
4646
CLASS_OBJECT_ID = "classObjectID"
4747
ARRAY_ID = "arrayID"
@@ -85,7 +85,10 @@ class TaggedUnion(Generic[EnumT]):
8585
"""Tagged Union class type"""
8686

8787
tag: Field[UnionTag[EnumT]]
88-
cases: Mapping[EnumT, Struct]
88+
cases: Sequence[Tuple[EnumT, Struct]]
89+
90+
def __post_init__(self):
91+
object.__setattr__(self, "cases", tuple(self.cases))
8992

9093

9194
@dataclass(frozen=True)
@@ -114,6 +117,9 @@ class Struct:
114117

115118
fields: Sequence[Field[Type]]
116119

120+
def __post_init__(self):
121+
object.__setattr__(self, "fields", tuple(self.fields))
122+
117123

118124
@dataclass(frozen=True)
119125
class Command:
@@ -125,6 +131,9 @@ class Command:
125131
reply: Optional[Struct]
126132
error: Set[ErrorType]
127133

134+
def __post_init__(self):
135+
object.__setattr__(self, "error", frozenset(self.error))
136+
128137

129138
@dataclass(frozen=True)
130139
class CommandSet:
@@ -133,3 +142,6 @@ class CommandSet:
133142
name: str
134143
id: int
135144
commands: Sequence[Command]
145+
146+
def __post_init__(self):
147+
object.__setattr__(self, "commands", tuple(self.commands))

projects/jdwp/main.py

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
11
# Copyright (c) Meta Platforms, Inc. and affiliates.
22

3+
import asyncio
4+
from .runtime.jvm_connection import JVMConnection
35

4-
def main():
5-
return None
6+
7+
async def main():
8+
host = "localhost"
9+
port = 8880
10+
11+
connection = JVMConnection(host, port)
12+
13+
await connection.connect()
14+
15+
await connection.handshake()
16+
17+
await connection.close()
618

719

820
if __name__ == "__main__":
9-
main()
21+
asyncio.run(main())

projects/jdwp/runtime/BUCK

+14-3
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,24 @@ genrule(
66
cmd = "$(exe //projects/jdwp/codegen:generate-new-types) > $OUT",
77
)
88

9+
genrule(
10+
name = "structs",
11+
out = "structs.py",
12+
cmd = "$(exe //projects/jdwp/codegen:generate-dataclasses) > $OUT",
13+
)
14+
915
python_library(
1016
name = "runtime",
1117
srcs = [
18+
":structs",
1219
":type-aliases",
20+
"async_streams.py",
21+
"jdwpstruct.py",
22+
"jdwp_streams.py",
23+
"jvm_connection.py",
1324
],
1425
visibility = ["PUBLIC", ],
15-
16-
17-
deps = [],
26+
deps = [
27+
"//projects/jdwp/defs:defs",
28+
],
1829
)

0 commit comments

Comments
 (0)