12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved. 22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be 32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# found in the LICENSE file. 42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Autocompletion config for YouCompleteMe in Chromium. 62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# USAGE: 82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 92a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 1. Install YCM [https://github.com/Valloric/YouCompleteMe] 102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# (Googlers should check out [go/ycm]) 112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 2. Point to this config file in your .vimrc: 132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# let g:ycm_global_ycm_extra_conf = 142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# '<chrome_depot>/src/tools/vim/chromium.ycm_extra_conf.py' 152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 3. Profit 172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Usage notes: 202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# * You must use ninja & clang to build Chromium. 222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# * You must have run gyp_chromium and built Chromium recently. 242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Hacking notes: 272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# * The purpose of this script is to construct an accurate enough command line 292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# for YCM to pass to clang so it can build and extract the symbols. 302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# * Right now, we only pull the -I and -D flags. That seems to be sufficient 322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# for everything I've used it for. 332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# * That whole ninja & clang thing? We could support other configs if someone 352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# were willing to write the correct commands and a parser. 362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# * This has only been tested on gPrecise. 382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import os 412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import subprocess 422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Flags from YCM's default config. 452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)flags = [ 462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)'-DUSE_CLANG_COMPLETER', 472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)'-std=c++11', 482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)'-x', 492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)'c++', 502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)] 512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 53c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)def PathExists(*args): 54c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) return os.path.exists(os.path.join(*args)) 55c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 56c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def FindChromeSrcFromFilename(filename): 582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Searches for the root of the Chromium checkout. 592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Simply checks parent directories until it finds .gclient and src/. 612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Args: 632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) filename: (String) Path to source file being edited. 642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Returns: 662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) (String) Path of 'src/', or None if unable to find. 672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """ 682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) curdir = os.path.normpath(os.path.dirname(filename)) 6990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) while not (PathExists(curdir, 'src') and PathExists(curdir, 'src', 'DEPS') 70c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) and (PathExists(curdir, '.gclient') 71c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) or PathExists(curdir, 'src', '.git'))): 722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) nextdir = os.path.normpath(os.path.join(curdir, '..')) 732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if nextdir == curdir: 742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return None 752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) curdir = nextdir 762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return os.path.join(curdir, 'src') 772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 79b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)# Largely copied from ninja-build.vim (guess_configuration) 80b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles)def GetNinjaOutputDirectory(chrome_root): 81b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) """Returns either <chrome_root>/out/Release or <chrome_root>/out/Debug. 82b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) 83b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) The configuration chosen is the one most recently generated/built.""" 84b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) root = os.path.join(chrome_root, 'out') 85b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) debug_path = os.path.join(root, 'Debug') 86b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) release_path = os.path.join(root, 'Release') 87b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) 88b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) def is_release_15s_newer(test_path): 89b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) try: 90b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) debug_mtime = os.path.getmtime(os.path.join(debug_path, test_path)) 91b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) except os.error: 92b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) debug_mtime = 0 93b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) try: 94b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) rel_mtime = os.path.getmtime(os.path.join(release_path, test_path)) 95b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) except os.error: 96b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) rel_mtime = 0 97b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) return rel_mtime - debug_mtime >= 15 98b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) 99b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) if is_release_15s_newer('build.ninja') or is_release_15s_newer('protoc'): 100b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) return release_path 101b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) return debug_path 102b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) 103b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) 1042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def GetClangCommandFromNinjaForFilename(chrome_root, filename): 1052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Returns the command line to build |filename|. 1062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Asks ninja how it would build the source file. If the specified file is a 1082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) header, tries to find its companion source file first. 1092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Args: 1112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) chrome_root: (String) Path to src/. 1122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) filename: (String) Path to source file being edited. 1132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Returns: 1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) (List of Strings) Command line arguments for clang. 1162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """ 1172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if not chrome_root: 1182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return [] 1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Generally, everyone benefits from including Chromium's src/, because all of 1212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Chromium's includes are relative to that. 1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) chrome_flags = ['-I' + os.path.join(chrome_root)] 1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Header files can't be built. Instead, try to match a header file to its 1252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # corresponding source file. 1262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if filename.endswith('.h'): 1272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) alternates = ['.cc', '.cpp'] 1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for alt_extension in alternates: 1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) alt_name = filename[:-2] + alt_extension 1302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if os.path.exists(alt_name): 1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) filename = alt_name 1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) break 1332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 1342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # If this is a standalone .h file with no source, the best we can do is 1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # try to use the default flags. 1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return chrome_flags 1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Ninja needs the path to the source file from the output build directory. 1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Cut off the common part and /. 1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) subdir_filename = filename[len(chrome_root)+1:] 1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) rel_filename = os.path.join('..', '..', subdir_filename) 1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 143b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) out_dir = GetNinjaOutputDirectory(chrome_root) 144b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) 1452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Ask ninja how it would build our source file. 146b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) p = subprocess.Popen(['ninja', '-v', '-C', out_dir, '-t', 1472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 'commands', rel_filename + '^'], 1482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) stdout=subprocess.PIPE) 1492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) stdout, stderr = p.communicate() 1502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if p.returncode: 1512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return chrome_flags 1522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Ninja might execute several commands to build something. We want the last 1542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # clang command. 1552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) clang_line = None 1562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for line in reversed(stdout.split('\n')): 1572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if 'clang' in line: 1582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) clang_line = line 1592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) break 1602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 1612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return chrome_flags 1622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Parse out the -I and -D flags. These seem to be the only ones that are 1642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # important for YCM's purposes. 1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for flag in clang_line.split(' '): 1662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if flag.startswith('-I'): 1672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Relative paths need to be resolved, because they're relative to the 1682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # output dir, not the source. 1692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if flag[2] == '/': 1702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) chrome_flags.append(flag) 1712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 172b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) abs_path = os.path.normpath(os.path.join(out_dir, flag[2:])) 1732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) chrome_flags.append('-I' + abs_path) 1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) elif flag.startswith('-') and flag[1] in 'DWFfmO': 1757dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch if flag == '-Wno-deprecated-register' or flag == '-Wno-header-guard': 1767dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch # These flags causes libclang (3.3) to crash. Remove it until things 1777dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch # are fixed. 1787dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch continue 1792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) chrome_flags.append(flag) 1802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return chrome_flags 1822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def FlagsForFile(filename): 1852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """This is the main entry point for YCM. Its interface is fixed. 1862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Args: 1882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) filename: (String) Path to source file being edited. 1892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) Returns: 1912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) (Dictionary) 1922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 'flags': (List of Strings) Command line flags. 1932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 'do_cache': (Boolean) True if the result should be cached. 1942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """ 1952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) chrome_root = FindChromeSrcFromFilename(filename) 1962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) chrome_flags = GetClangCommandFromNinjaForFilename(chrome_root, 1972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) filename) 1982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) final_flags = flags + chrome_flags 1992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return { 2012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 'flags': final_flags, 2022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 'do_cache': True 2032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 204