From bc2d04f30ddcabaa624479319f6dd54421f8767d Mon Sep 17 00:00:00 2001 From: Romain Naour Date: Tue, 3 Feb 2026 09:04:16 +0100 Subject: [PATCH] package/depot-tools: fix python 3.14 support Backport a fix to support python 3.14 [1]. depot-tools is used to build flutter package but it doesn't run due to deprecated ast.Str et al. (since Py3.8) that was removed from python 3.14. Fixes: https://gitlab.com/buildroot.org/buildroot/-/jobs/12944797062 [1] https://chromium.googlesource.com/chromium/tools/depot_tools/+/80d6ca1b1ac55fbd9dd8f506417824591251b8eb Cc: Adam Duskett Signed-off-by: Romain Naour Signed-off-by: Thomas Petazzoni --- ...precated-ast-types-with-ast.Constant.patch | 244 ++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 package/depot-tools/0002-Replace-deprecated-ast-types-with-ast.Constant.patch diff --git a/package/depot-tools/0002-Replace-deprecated-ast-types-with-ast.Constant.patch b/package/depot-tools/0002-Replace-deprecated-ast-types-with-ast.Constant.patch new file mode 100644 index 0000000000..09e88d04a2 --- /dev/null +++ b/package/depot-tools/0002-Replace-deprecated-ast-types-with-ast.Constant.patch @@ -0,0 +1,244 @@ +From 7d467257a7cd57e746885ea73ccbda903450b461 Mon Sep 17 00:00:00 2001 +From: jj +Date: Fri, 5 Dec 2025 11:47:42 -0800 +Subject: [PATCH] Replace deprecated ast types with ast.Constant + +ast.Str et al. have been deprecated since Py3.8 and have been +completely removed in 3.14. Replace their usage with ast.Constant. + +This change should not have any functional impact, since +according to [1]: +> Changed in version 3.8: Class ast.Constant is now used for +> all constants. +> Deprecated since version 3.8: Old classes ast.Num, ast.Str, +> [...] instantiating them will return an instance of a +> different class. + +[1] https://docs.python.org/3/library/ast.html + +Bug: 40283283 +Change-Id: I0ed8ef3910f921483bff118976f516c5a935e0fc +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/7228507 +Reviewed-by: Gavin Mak +Reviewed-by: Yiwei Zhang +Commit-Queue: jj + +Upstream: https://chromium.googlesource.com/chromium/tools/depot_tools/+/80d6ca1b1ac55fbd9dd8f506417824591251b8eb +[Romain: backport to 097e2072377] +Signed-off-by: Romain Naour +--- + gclient_eval.py | 72 ++++++++++++++++++++-------------- + tests/gclient_eval_unittest.py | 6 +++ + 2 files changed, 49 insertions(+), 29 deletions(-) + +diff --git a/gclient_eval.py b/gclient_eval.py +index f8a05d78a..94874e3ce 100644 +--- a/gclient_eval.py ++++ b/gclient_eval.py +@@ -101,6 +101,24 @@ def _NodeDictSchema(dict_schema): + return validate + + ++def _IsConstant(node): ++ return isinstance(node, ast.Constant) ++ ++ ++def _IsStringConstant(node): ++ return _IsConstant(node) and isinstance(node.value, str) ++ ++ ++def _IsNameConstant(node): ++ return _IsConstant(node) \ ++ and isinstance(node.value, (bool, type(None))) ++ ++ ++def _IsNumericConstant(node): ++ return _IsConstant(node) \ ++ and isinstance(node.value, (int, float, complex)) \ ++ and not isinstance(node.value, bool) ++ + # See https://github.com/keleshev/schema for docs how to configure schema. + _GCLIENT_DEPS_SCHEMA = _NodeDictSchema({ + schema.Optional(str): +@@ -316,18 +334,18 @@ def _gclient_eval(node_or_string, filename='', vars_dict=None): + node_or_string = node_or_string.body + + def _convert(node): +- if isinstance(node, ast.Str): ++ if _IsStringConstant(node): + if vars_dict is None: +- return node.s ++ return node.value + try: +- return node.s.format(**vars_dict) ++ return node.value.format(**vars_dict) + except KeyError as e: + raise KeyError( + '%s was used as a variable, but was not declared in the vars dict ' + '(file %r, line %s)' % + (e.args[0], filename, getattr(node, 'lineno', ''))) +- elif isinstance(node, ast.Num): +- return node.n ++ elif _IsNameConstant(node) or _IsNumericConstant(node): ++ return node.value + elif isinstance(node, ast.Tuple): + return tuple(map(_convert, node.elts)) + elif isinstance(node, ast.List): +@@ -349,9 +367,6 @@ def _gclient_eval(node_or_string, filename='', vars_dict=None): + 'invalid name %r (file %r, line %s)' % + (node.id, filename, getattr(node, 'lineno', ''))) + return _allowed_names[node.id] +- elif not sys.version_info[:2] < (3, 4) and isinstance( +- node, ast.NameConstant): # Since Python 3.4 +- return node.value + elif isinstance(node, ast.Call): + if (not isinstance(node.func, ast.Name) + or (node.func.id not in ('Str', 'Var'))): +@@ -366,8 +381,8 @@ def _gclient_eval(node_or_string, filename='', vars_dict=None): + ''))) + + if node.func.id == 'Str': +- if isinstance(node.args[0], ast.Str): +- return ConstantString(node.args[0].s) ++ if _IsStringConstant(node.args[0]): ++ return ConstantString(node.args[0].value) + raise ValueError( + 'Passed a non-string to Str() (file %r, line%s)' % + (filename, getattr(node, 'lineno', ''))) +@@ -594,8 +609,8 @@ def EvaluateCondition(condition, variables, referenced_variables=None): + main_node = main_node.body + + def _convert(node, allow_tuple=False): +- if isinstance(node, ast.Str): +- return node.s ++ if _IsStringConstant(node): ++ return node.value + + if isinstance(node, ast.Tuple) and allow_tuple: + return tuple(map(_convert, node.elts)) +@@ -627,8 +642,7 @@ def EvaluateCondition(condition, variables, referenced_variables=None): + # be evaluated. + return node.id + +- if not sys.version_info[:2] < (3, 4) and isinstance( +- node, ast.NameConstant): # Since Python 3.4 ++ if _IsNameConstant(node): + return node.value + + if isinstance(node, ast.BoolOp) and isinstance(node.op, ast.Or): +@@ -696,11 +710,11 @@ def _UpdateAstString(tokens, node, value): + node = node.args[0] + position = node.lineno, node.col_offset + quote_char = '' +- if isinstance(node, ast.Str): ++ if _IsStringConstant(node): + quote_char = tokens[position][1][0] + value = value.encode('unicode_escape').decode('utf-8') + tokens[position][1] = quote_char + value + quote_char +- node.s = value ++ node.value = value + + + def _ShiftLinesInTokens(tokens, delta, start): +@@ -793,11 +807,11 @@ def SetVar(gclient_dict, var_name, value): + + def _GetVarName(node): + if isinstance(node, ast.Call): +- return node.args[0].s ++ return node.args[0].value + +- if node.s.endswith('}'): +- last_brace = node.s.rfind('{') +- return node.s[last_brace + 1:-1] ++ if node.value.endswith('}'): ++ last_brace = node.value.rfind('{') ++ return node.value[last_brace + 1:-1] + return None + + +@@ -821,9 +835,9 @@ def SetGCS(gclient_dict, dep_name, new_objects): + keys_to_update = ('object_name', 'sha256sum', 'size_bytes', 'generation') + for index, object_node in enumerate(objects_node.elts): + for key, value in zip(object_node.keys, object_node.values): +- if key.s not in keys_to_update: ++ if key.value not in keys_to_update: + continue +- _UpdateAstString(tokens, value, new_objects[index][key.s]) ++ _UpdateAstString(tokens, value, new_objects[index][key.value]) + + node.SetNode('objects', new_objects, objects_node) + +@@ -855,7 +869,7 @@ def SetCIPD(gclient_dict, dep_name, package_name, new_version): + "The deps entry for %s:%s has no formatting information." % + (dep_name, package_name)) + +- if not isinstance(node, ast.Call) and not isinstance(node, ast.Str): ++ if not isinstance(node, ast.Call) and not _IsStringConstant(node): + raise ValueError( + "Unsupported dependency revision format. Please file a bug to the " + "Infra>SDK component in crbug.com") +@@ -880,15 +894,15 @@ def SetRevision(gclient_dict, dep_name, new_revision): + if isinstance(node, ast.BinOp): + node = node.right + +- if isinstance(node, ast.Str): ++ if _IsStringConstant(node): + token = _gclient_eval(tokens[node.lineno, node.col_offset][1]) +- if token != node.s: ++ if token != node.value: + raise ValueError( + 'Can\'t update value for %s. Multiline strings and implicitly ' + 'concatenated strings are not supported.\n' + 'Consider reformatting the DEPS file.' % dep_key) + +- if not isinstance(node, ast.Call) and not isinstance(node, ast.Str): ++ if not isinstance(node, ast.Call) and not _IsStringConstant(node): + raise ValueError( + "Unsupported dependency revision format. Please file a bug to the " + "Infra>SDK component in crbug.com") +@@ -897,15 +911,15 @@ def SetRevision(gclient_dict, dep_name, new_revision): + if var_name is not None: + SetVar(gclient_dict, var_name, new_revision) + else: +- if '@' in node.s: ++ if '@' in node.value: + # '@' is part of the last string, which we want to modify. + # Discard whatever was after the '@' and put the new revision in + # its place. +- new_revision = node.s.split('@')[0] + '@' + new_revision ++ new_revision = node.value.split('@')[0] + '@' + new_revision + elif '@' not in dep_dict[dep_key]: + # '@' is not part of the URL at all. This mean the dependency is + # unpinned and we should pin it. +- new_revision = node.s + '@' + new_revision ++ new_revision = node.value + '@' + new_revision + _UpdateAstString(tokens, node, new_revision) + dep_dict.SetNode(dep_key, new_revision, node) + +diff --git a/tests/gclient_eval_unittest.py b/tests/gclient_eval_unittest.py +index 03decdf30..3aed06156 100755 +--- a/tests/gclient_eval_unittest.py ++++ b/tests/gclient_eval_unittest.py +@@ -30,6 +30,11 @@ class GClientEvalTest(unittest.TestCase): + def test_str(self): + self.assertEqual('foo', gclient_eval._gclient_eval('"foo"')) + ++ def test_num(self): ++ self.assertEqual(42, gclient_eval._gclient_eval('42')) ++ self.assertEqual(1024.0, gclient_eval._gclient_eval('1024.0')) ++ self.assertEqual(42j, gclient_eval._gclient_eval('42j')) ++ + def test_tuple(self): + self.assertEqual(('a', 'b'), gclient_eval._gclient_eval('("a", "b")')) + +@@ -40,6 +45,7 @@ class GClientEvalTest(unittest.TestCase): + self.assertEqual({'a': 'b'}, gclient_eval._gclient_eval('{"a": "b"}')) + + def test_name_safe(self): ++ self.assertEqual(None, gclient_eval._gclient_eval('None')) + self.assertEqual(True, gclient_eval._gclient_eval('True')) + + def test_name_unsafe(self): +-- +2.52.0 +