Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
eclipse.py462 linesDownload Raw Back to generator
1# Copyright (c) 2012 Google Inc. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""GYP backend that generates Eclipse CDT settings files.
6
7This backend DOES NOT generate Eclipse CDT projects. Instead, it generates XML
8files that can be imported into an Eclipse CDT project. The XML file contains a
9list of include paths and symbols (i.e. defines).
10
11Because a full .cproject definition is not created by this generator, it's not
12possible to properly define the include dirs and symbols for each file
13individually.  Instead, one set of includes/symbols is generated for the entire
14project.  This works fairly well (and is a vast improvement in general), but may
15still result in a few indexer issues here and there.
16
17This generator has no automated tests, so expect it to be broken.
18"""
19
20import os.path
21import shlex
22import subprocess
23import xml.etree.ElementTree as ET
24from xml.sax.saxutils import escape
25
26import gyp
27import gyp.common
28import gyp.msvs_emulation
29
30generator_wants_static_library_dependencies_adjusted = False
31
32generator_default_variables = {}
33
34for dirname in ["INTERMEDIATE_DIR", "PRODUCT_DIR", "LIB_DIR", "SHARED_LIB_DIR"]:
35    # Some gyp steps fail if these are empty(!), so we convert them to variables
36    generator_default_variables[dirname] = "$" + dirname
37
38for unused in [
39    "RULE_INPUT_PATH",
40    "RULE_INPUT_ROOT",
41    "RULE_INPUT_NAME",
42    "RULE_INPUT_DIRNAME",
43    "RULE_INPUT_EXT",
44    "EXECUTABLE_PREFIX",
45    "EXECUTABLE_SUFFIX",
46    "STATIC_LIB_PREFIX",
47    "STATIC_LIB_SUFFIX",
48    "SHARED_LIB_PREFIX",
49    "SHARED_LIB_SUFFIX",
50    "CONFIGURATION_NAME",
51]:
52    generator_default_variables[unused] = ""
53
54# Include dirs will occasionally use the SHARED_INTERMEDIATE_DIR variable as
55# part of the path when dealing with generated headers.  This value will be
56# replaced dynamically for each configuration.
57generator_default_variables["SHARED_INTERMEDIATE_DIR"] = "$SHARED_INTERMEDIATE_DIR"
58
59
60def CalculateVariables(default_variables, params):
61    generator_flags = params.get("generator_flags", {})
62    for key, val in generator_flags.items():
63        default_variables.setdefault(key, val)
64    flavor = gyp.common.GetFlavor(params)
65    default_variables.setdefault("OS", flavor)
66    if flavor == "win":
67        gyp.msvs_emulation.CalculateCommonVariables(default_variables, params)
68
69
70def CalculateGeneratorInputInfo(params):
71    """Calculate the generator specific info that gets fed to input (called by
72    gyp)."""
73    generator_flags = params.get("generator_flags", {})
74    if generator_flags.get("adjust_static_libraries", False):
75        global generator_wants_static_library_dependencies_adjusted
76        generator_wants_static_library_dependencies_adjusted = True
77
78
79def GetAllIncludeDirectories(
80    target_list,
81    target_dicts,
82    shared_intermediate_dirs,
83    config_name,
84    params,
85    compiler_path,
86):
87    """Calculate the set of include directories to be used.
88
89    Returns:
90      A list including all the include_dir's specified for every target followed
91      by any include directories that were added as cflag compiler options.
92    """
93
94    gyp_includes_set = set()
95    compiler_includes_list = []
96
97    # Find compiler's default include dirs.
98    if compiler_path:
99        command = shlex.split(compiler_path)
100        command.extend(["-E", "-xc++", "-v", "-"])
101        proc = subprocess.Popen(
102            args=command,
103            stdin=subprocess.PIPE,
104            stdout=subprocess.PIPE,
105            stderr=subprocess.PIPE,
106        )
107        output = proc.communicate()[1].decode("utf-8")
108        # Extract the list of include dirs from the output, which has this format:
109        #   ...
110        #   #include "..." search starts here:
111        #   #include <...> search starts here:
112        #    /usr/include/c++/4.6
113        #    /usr/local/include
114        #   End of search list.
115        #   ...
116        in_include_list = False
117        for line in output.splitlines():
118            if line.startswith("#include"):
119                in_include_list = True
120                continue
121            if line.startswith("End of search list."):
122                break
123            if in_include_list:
124                include_dir = line.strip()
125                if include_dir not in compiler_includes_list:
126                    compiler_includes_list.append(include_dir)
127
128    flavor = gyp.common.GetFlavor(params)
129    if flavor == "win":
130        generator_flags = params.get("generator_flags", {})
131    for target_name in target_list:
132        target = target_dicts[target_name]
133        if config_name in target["configurations"]:
134            config = target["configurations"][config_name]
135
136            # Look for any include dirs that were explicitly added via cflags. This
137            # may be done in gyp files to force certain includes to come at the end.
138            # TODO(jgreenwald): Change the gyp files to not abuse cflags for this, and
139            # remove this.
140            if flavor == "win":
141                msvs_settings = gyp.msvs_emulation.MsvsSettings(target, generator_flags)
142                cflags = msvs_settings.GetCflags(config_name)
143            else:
144                cflags = config["cflags"]
145            for cflag in cflags:
146                if cflag.startswith("-I"):
147                    include_dir = cflag[2:]
148                    if include_dir not in compiler_includes_list:
149                        compiler_includes_list.append(include_dir)
150
151            # Find standard gyp include dirs.
152            if "include_dirs" in config:
153                include_dirs = config["include_dirs"]
154                for shared_intermediate_dir in shared_intermediate_dirs:
155                    for include_dir in include_dirs:
156                        include_dir = include_dir.replace(
157                            "$SHARED_INTERMEDIATE_DIR", shared_intermediate_dir
158                        )
159                        if not os.path.isabs(include_dir):
160                            base_dir = os.path.dirname(target_name)
161
162                            include_dir = base_dir + "/" + include_dir
163                            include_dir = os.path.abspath(include_dir)
164
165                        gyp_includes_set.add(include_dir)
166
167    # Generate a list that has all the include dirs.
168    all_includes_list = list(gyp_includes_set)
169    all_includes_list.sort()
170    for compiler_include in compiler_includes_list:
171        if compiler_include not in gyp_includes_set:
172            all_includes_list.append(compiler_include)
173
174    # All done.
175    return all_includes_list
176
177
178def GetCompilerPath(target_list, data, options):
179    """Determine a command that can be used to invoke the compiler.
180
181    Returns:
182      If this is a gyp project that has explicit make settings, try to determine
183      the compiler from that.  Otherwise, see if a compiler was specified via the
184      CC_target environment variable.
185    """
186    # First, see if the compiler is configured in make's settings.
187    build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0])
188    make_global_settings_dict = data[build_file].get("make_global_settings", {})
189    for key, value in make_global_settings_dict:
190        if key in ["CC", "CXX"]:
191            return os.path.join(options.toplevel_dir, value)
192
193    # Check to see if the compiler was specified as an environment variable.
194    for key in ["CC_target", "CC", "CXX"]:
195        compiler = os.environ.get(key)
196        if compiler:
197            return compiler
198
199    return "gcc"
200
201
202def GetAllDefines(target_list, target_dicts, data, config_name, params, compiler_path):
203    """Calculate the defines for a project.
204
205    Returns:
206      A dict that includes explicit defines declared in gyp files along with all
207      of the default defines that the compiler uses.
208    """
209
210    # Get defines declared in the gyp files.
211    all_defines = {}
212    flavor = gyp.common.GetFlavor(params)
213    if flavor == "win":
214        generator_flags = params.get("generator_flags", {})
215    for target_name in target_list:
216        target = target_dicts[target_name]
217
218        if flavor == "win":
219            msvs_settings = gyp.msvs_emulation.MsvsSettings(target, generator_flags)
220            extra_defines = msvs_settings.GetComputedDefines(config_name)
221        else:
222            extra_defines = []
223        if config_name in target["configurations"]:
224            config = target["configurations"][config_name]
225            target_defines = config["defines"]
226        else:
227            target_defines = []
228        for define in target_defines + extra_defines:
229            split_define = define.split("=", 1)
230            if len(split_define) == 1:
231                split_define.append("1")
232            if split_define[0].strip() in all_defines:
233                # Already defined
234                continue
235            all_defines[split_define[0].strip()] = split_define[1].strip()
236    # Get default compiler defines (if possible).
237    if flavor == "win":
238        return all_defines  # Default defines already processed in the loop above.
239    if compiler_path:
240        command = shlex.split(compiler_path)
241        command.extend(["-E", "-dM", "-"])
242        cpp_proc = subprocess.Popen(
243            args=command, cwd=".", stdin=subprocess.PIPE, stdout=subprocess.PIPE
244        )
245        cpp_output = cpp_proc.communicate()[0].decode("utf-8")
246        cpp_lines = cpp_output.split("\n")
247        for cpp_line in cpp_lines:
248            if not cpp_line.strip():
249                continue
250            cpp_line_parts = cpp_line.split(" ", 2)
251            key = cpp_line_parts[1]
252            val = cpp_line_parts[2] if len(cpp_line_parts) >= 3 else "1"
253            all_defines[key] = val
254
255    return all_defines
256
257
258def WriteIncludePaths(out, eclipse_langs, include_dirs):
259    """Write the includes section of a CDT settings export file."""
260
261    out.write(
262        '  <section name="org.eclipse.cdt.internal.ui.wizards.'
263        'settingswizards.IncludePaths">\n'
264    )
265    out.write('    <language name="holder for library settings"></language>\n')
266    for lang in eclipse_langs:
267        out.write('    <language name="%s">\n' % lang)
268        for include_dir in include_dirs:
269            out.write(
270                '      <includepath workspace_path="false">%s</includepath>\n'
271                % include_dir
272            )
273        out.write("    </language>\n")
274    out.write("  </section>\n")
275
276
277def WriteMacros(out, eclipse_langs, defines):
278    """Write the macros section of a CDT settings export file."""
279
280    out.write(
281        '  <section name="org.eclipse.cdt.internal.ui.wizards.'
282        'settingswizards.Macros">\n'
283    )
284    out.write('    <language name="holder for library settings"></language>\n')
285    for lang in eclipse_langs:
286        out.write('    <language name="%s">\n' % lang)
287        for key in sorted(defines):
288            out.write(
289                "      <macro><name>%s</name><value>%s</value></macro>\n"
290                % (escape(key), escape(defines[key]))
291            )
292        out.write("    </language>\n")
293    out.write("  </section>\n")
294
295
296def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name):
297    options = params["options"]
298    generator_flags = params.get("generator_flags", {})
299
300    # build_dir: relative path from source root to our output files.
301    # e.g. "out/Debug"
302    build_dir = os.path.join(generator_flags.get("output_dir", "out"), config_name)
303
304    toplevel_build = os.path.join(options.toplevel_dir, build_dir)
305    # Ninja uses out/Debug/gen while make uses out/Debug/obj/gen as the
306    # SHARED_INTERMEDIATE_DIR. Include both possible locations.
307    shared_intermediate_dirs = [
308        os.path.join(toplevel_build, "obj", "gen"),
309        os.path.join(toplevel_build, "gen"),
310    ]
311
312    GenerateCdtSettingsFile(
313        target_list,
314        target_dicts,
315        data,
316        params,
317        config_name,
318        os.path.join(toplevel_build, "eclipse-cdt-settings.xml"),
319        options,
320        shared_intermediate_dirs,
321    )
322    GenerateClasspathFile(
323        target_list,
324        target_dicts,
325        options.toplevel_dir,
326        toplevel_build,
327        os.path.join(toplevel_build, "eclipse-classpath.xml"),
328    )
329
330
331def GenerateCdtSettingsFile(
332    target_list,
333    target_dicts,
334    data,
335    params,
336    config_name,
337    out_name,
338    options,
339    shared_intermediate_dirs,
340):
341    gyp.common.EnsureDirExists(out_name)
342    with open(out_name, "w") as out:
343        out.write('<?xml version="1.0" encoding="UTF-8"?>\n')
344        out.write("<cdtprojectproperties>\n")
345
346        eclipse_langs = [
347            "C++ Source File",
348            "C Source File",
349            "Assembly Source File",
350            "GNU C++",
351            "GNU C",
352            "Assembly",
353        ]
354        compiler_path = GetCompilerPath(target_list, data, options)
355        include_dirs = GetAllIncludeDirectories(
356            target_list,
357            target_dicts,
358            shared_intermediate_dirs,
359            config_name,
360            params,
361            compiler_path,
362        )
363        WriteIncludePaths(out, eclipse_langs, include_dirs)
364        defines = GetAllDefines(
365            target_list, target_dicts, data, config_name, params, compiler_path
366        )
367        WriteMacros(out, eclipse_langs, defines)
368
369        out.write("</cdtprojectproperties>\n")
370
371
372def GenerateClasspathFile(
373    target_list, target_dicts, toplevel_dir, toplevel_build, out_name
374):
375    """Generates a classpath file suitable for symbol navigation and code
376    completion of Java code (such as in Android projects) by finding all
377    .java and .jar files used as action inputs."""
378    gyp.common.EnsureDirExists(out_name)
379    result = ET.Element("classpath")
380
381    def AddElements(kind, paths):
382        # First, we need to normalize the paths so they are all relative to the
383        # toplevel dir.
384        rel_paths = set()
385        for path in paths:
386            if os.path.isabs(path):
387                rel_paths.add(os.path.relpath(path, toplevel_dir))
388            else:
389                rel_paths.add(path)
390
391        for path in sorted(rel_paths):
392            entry_element = ET.SubElement(result, "classpathentry")
393            entry_element.set("kind", kind)
394            entry_element.set("path", path)
395
396    AddElements("lib", GetJavaJars(target_list, target_dicts, toplevel_dir))
397    AddElements("src", GetJavaSourceDirs(target_list, target_dicts, toplevel_dir))
398    # Include the standard JRE container and a dummy out folder
399    AddElements("con", ["org.eclipse.jdt.launching.JRE_CONTAINER"])
400    # Include a dummy out folder so that Eclipse doesn't use the default /bin
401    # folder in the root of the project.
402    AddElements("output", [os.path.join(toplevel_build, ".eclipse-java-build")])
403
404    ET.ElementTree(result).write(out_name)
405
406
407def GetJavaJars(target_list, target_dicts, toplevel_dir):
408    """Generates a sequence of all .jars used as inputs."""
409    for target_name in target_list:
410        target = target_dicts[target_name]
411        for action in target.get("actions", []):
412            for input_ in action["inputs"]:
413                if os.path.splitext(input_)[1] == ".jar" and not input_.startswith("$"):
414                    if os.path.isabs(input_):
415                        yield input_
416                    else:
417                        yield os.path.join(os.path.dirname(target_name), input_)
418
419
420def GetJavaSourceDirs(target_list, target_dicts, toplevel_dir):
421    """Generates a sequence of all likely java package root directories."""
422    for target_name in target_list:
423        target = target_dicts[target_name]
424        for action in target.get("actions", []):
425            for input_ in action["inputs"]:
426                if os.path.splitext(input_)[1] == ".java" and not input_.startswith(
427                    "$"
428                ):
429                    dir_ = os.path.dirname(
430                        os.path.join(os.path.dirname(target_name), input_)
431                    )
432                    # If there is a parent 'src' or 'java' folder, navigate up to it -
433                    # these are canonical package root names in Chromium.  This will
434                    # break if 'src' or 'java' exists in the package structure. This
435                    # could be further improved by inspecting the java file for the
436                    # package name if this proves to be too fragile in practice.
437                    parent_search = dir_
438                    while os.path.basename(parent_search) not in ["src", "java"]:
439                        parent_search, _ = os.path.split(parent_search)
440                        if not parent_search or parent_search == toplevel_dir:
441                            # Didn't find a known root, just return the original path
442                            yield dir_
443                            break
444                    else:
445                        yield parent_search
446
447
448def GenerateOutput(target_list, target_dicts, data, params):
449    """Generate an XML settings file that can be imported into a CDT project."""
450
451    if params["options"].generator_output:
452        raise NotImplementedError("--generator_output not implemented for eclipse")
453
454    if user_config := params.get("generator_flags", {}).get("config", None):
455        GenerateOutputForConfig(target_list, target_dicts, data, params, user_config)
456    else:
457        config_names = target_dicts[target_list[0]]["configurations"]
458        for config_name in config_names:
459            GenerateOutputForConfig(
460                target_list, target_dicts, data, params, config_name
461            )
462 
codekingpro/portable-devtools · Team Ai