Skip to content

Commit e67dba1

Browse files
motiz88meta-codesync[bot]
authored andcommitted
Add Buck CLI override for Kotlin feature flag defaults (#56558)
Summary: Pull Request resolved: #56558 The C++ feature flag default override mechanism (D101484355) only affects `ReactNativeFeatureFlagsDefaults.h`. When flags are read from the Kotlin side, `ReactNativeFeatureFlagsDefaults.kt` is used instead — its defaults are duplicated rather than read via JNI (see comment in the file: "more expensive than just duplicating the defaults here"). Here, we apply the same `--config react_native.feature_flag_defaults` override to the Kotlin file at build time, using the same genrule pattern. A regex-based Python script rewrites `override fun <name>(): <Type> = <value>` lines. Changelog: [Internal] Reviewed By: javache Differential Revision: D101974653 fbshipit-source-id: 0325d47d2f0b92065d9b5397d7d2d6d06486c970
1 parent 9140ff9 commit e67dba1

2 files changed

Lines changed: 139 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env fbpython
2+
# Copyright (c) Meta Platforms, Inc. and affiliates.
3+
#
4+
# This source code is licensed under the MIT license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
# pyre-strict
8+
9+
"""Rewrite default return values in ReactNativeFeatureFlagsDefaults.kt.
10+
11+
Reads the Kotlin source from --input, writes the transformed source to stdout.
12+
Overrides are passed as a JSON object via --overrides.
13+
Fails with a non-zero exit code if any requested flag is not found.
14+
"""
15+
16+
from __future__ import annotations
17+
18+
import argparse
19+
import json
20+
import re
21+
import sys
22+
23+
24+
def kotlin_literal(value: bool | int | float) -> str:
25+
if isinstance(value, bool):
26+
return "true" if value else "false"
27+
if isinstance(value, (int, float)):
28+
s = str(value)
29+
if isinstance(value, int) or "." not in s:
30+
s += ".0"
31+
return s
32+
raise ValueError(f"Unsupported value type {type(value).__name__} for override")
33+
34+
35+
def rewrite(source: bytes, overrides: dict[str, object]) -> bytes:
36+
text = source.decode("utf-8")
37+
for name, value in overrides.items():
38+
kotlin_type = "Boolean" if isinstance(value, bool) else "Double"
39+
pattern = rf"""
40+
(
41+
override \s+ fun \s+
42+
{re.escape(name)}
43+
\s* \( \s* \)
44+
\s* : \s* {kotlin_type}
45+
\s* = \s*
46+
)
47+
\S+
48+
"""
49+
text, n = re.subn(
50+
pattern,
51+
rf"\g<1>{kotlin_literal(value)}",
52+
text,
53+
count=1,
54+
flags=re.VERBOSE,
55+
)
56+
if n != 1:
57+
raise ValueError(f"{name} not matched")
58+
59+
return text.encode("utf-8")
60+
61+
62+
def main() -> None:
63+
parser = argparse.ArgumentParser()
64+
parser.add_argument("--overrides", default="{}")
65+
parser.add_argument("--input", required=True)
66+
args = parser.parse_args()
67+
68+
overrides: dict[str, object] = json.loads(args.overrides)
69+
with open(args.input, "rb") as f:
70+
source = f.read()
71+
72+
sys.stdout.buffer.write(rewrite(source, overrides))
73+
74+
75+
if __name__ == "__main__":
76+
main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
#
3+
# This source code is licensed under the MIT license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
# pyre-strict
7+
8+
from __future__ import annotations
9+
10+
import os
11+
import unittest
12+
13+
from rewrite_feature_flag_defaults import kotlin_literal, rewrite
14+
15+
16+
def _load_source() -> bytes:
17+
with open(os.environ["SOURCE_PATH"], "rb") as f:
18+
return f.read()
19+
20+
21+
class RewriteFeatureFlagDefaultsTest(unittest.TestCase):
22+
def setUp(self) -> None:
23+
self.source = _load_source()
24+
25+
def test_empty_overrides_is_passthrough(self) -> None:
26+
self.assertEqual(rewrite(self.source, {}), self.source)
27+
28+
def test_override_bool_to_true(self) -> None:
29+
result = rewrite(self.source, {"commonTestFlag": True})
30+
self.assertEqual(self._method_value(result, "commonTestFlag"), b"true")
31+
32+
def test_override_bool_to_false(self) -> None:
33+
result = rewrite(self.source, {"commonTestFlag": False})
34+
self.assertEqual(self._method_value(result, "commonTestFlag"), b"false")
35+
36+
def test_kotlin_literal_int_produces_double(self) -> None:
37+
self.assertEqual(kotlin_literal(42), "42.0")
38+
39+
def test_kotlin_literal_float(self) -> None:
40+
self.assertEqual(kotlin_literal(3.14), "3.14")
41+
42+
def test_unmatched_flag_raises(self) -> None:
43+
with self.assertRaises(ValueError):
44+
rewrite(self.source, {"bogusFlag": True})
45+
46+
def test_only_target_method_changes(self) -> None:
47+
result = rewrite(self.source, {"commonTestFlag": True})
48+
src_start, src_end = self._method_value_range(self.source, "commonTestFlag")
49+
res_start, res_end = self._method_value_range(result, "commonTestFlag")
50+
self.assertEqual(self.source[:src_start], result[:res_start])
51+
self.assertEqual(self.source[src_end:], result[res_end:])
52+
53+
def _method_value_range(self, source: bytes, name: str) -> tuple[int, int]:
54+
name_idx = source.find(name.encode())
55+
self.assertNotEqual(name_idx, -1, f"{name} not found in output")
56+
eq_idx = source.find(b"=", name_idx)
57+
eol_idx = source.find(b"\n", eq_idx)
58+
start = eq_idx + 2 # skip "= "
59+
return (start, eol_idx)
60+
61+
def _method_value(self, source: bytes, name: str) -> bytes:
62+
start, end = self._method_value_range(source, name)
63+
return source[start:end].strip()

0 commit comments

Comments
 (0)