aboutsummaryrefslogtreecommitdiffstats
path: root/meta-oe/classes/socorro-syms.bbclass
blob: 766a9e62723e2038bf10b7a14b2c8dd39d2d52d4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# Inherit this class when you want to allow Mozilla Socorro to link Breakpad's
# stack trace information to the correct source code revision.
# This class creates a new version of the symbol file (.sym) created by
# Breakpad. The absolute file paths in the symbol file will be replaced by VCS,
# branch, file and revision of the source file. That information facilitates the
# lookup of a particular source code line in the stack trace.
#
# Use example:
#
# BREAKPAD_BIN = "YourBinary"
# inherit socorro-syms
#

# We depend on Breakpad creating the original symbol file.
inherit breakpad

PACKAGE_PREPROCESS_FUNCS += "symbol_file_preprocess"
PACKAGES =+ "${PN}-socorro-syms"
FILES_${PN}-socorro-syms = "/usr/share/socorro-syms"


python symbol_file_preprocess() {

    package_dir = d.getVar("PKGD", True)
    breakpad_bin = d.getVar("BREAKPAD_BIN", True)
    if not breakpad_bin:
        package_name = d.getVar("PN", True)
        bb.error("Package %s depends on Breakpad via socorro-syms. See "
            "breakpad.bbclass for instructions on setting up the Breakpad "
            "configuration." % package_name)
        raise ValueError("BREAKPAD_BIN not defined in %s." % package_name)

    sym_file_name = breakpad_bin + ".sym"

    breakpad_syms_dir = os.path.join(
        package_dir, "usr", "share", "breakpad-syms")
    socorro_syms_dir = os.path.join(
        package_dir, "usr", "share", "socorro-syms")
    if not os.path.exists(socorro_syms_dir):
        os.makedirs(socorro_syms_dir)

    breakpad_sym_file_path = os.path.join(breakpad_syms_dir, sym_file_name)
    socorro_sym_file_path = os.path.join(socorro_syms_dir, sym_file_name)

    # In the symbol file, all source files are referenced like the following.
    # FILE 123 /path/to/some/File.cpp
    # Go through all references and replace the file paths with repository
    # paths.
    with open(breakpad_sym_file_path, 'r') as breakpad_sym_file, \
            open(socorro_sym_file_path, 'w') as socorro_sym_file:

        for line in breakpad_sym_file:
            if line.startswith("FILE "):
                socorro_sym_file.write(socorro_file_reference(line))
            else:
                socorro_sym_file.write(line)

    return
}


def socorro_file_reference(line):

    # The 3rd position is the file path. See example above.
    source_file_path = line.split()[2]
    source_file_repo_path = repository_path(os.path.normpath(source_file_path))

    # If the file could be found in any repository then replace it with the
    # repository's path.
    if source_file_repo_path:
        return line.replace(source_file_path, source_file_repo_path)

    return line


def repository_path(source_file_path):

    if not os.path.isfile(source_file_path):
        return None

    # Check which VCS is used and use that to extract repository information.
    (output, error) = bb.process.run("git status",
        cwd=os.path.dirname(source_file_path))
    if not error:
        return git_repository_path(source_file_path)

    # Here we can add support for other VCSs like hg, svn, cvs, etc.

    # The source file isn't under any VCS so we leave it be.
    return None


def run_command(command, directory):

    (output, error) = bb.process.run(command, cwd=directory)
    if error:
        raise bb.process.ExecutionError(command, error)

    return output.rstrip()


def git_repository_path(source_file_path):

    import re

    # We need to extract the following.
    # (1): VCS URL, (2): branch, (3): repo root directory name, (4): repo file,
    # (5): revision.

    source_file_dir = os.path.dirname(source_file_path)

    # (1) Get the VCS URL and extract the server part, i.e. change the URL from
    # gitolite@git.someserver.com:SomeRepo.git to just git.someserver.com.
    source_long_url = run_command(
        "git config --get remote.origin.url", source_file_dir)

    # The URL could be a local download directory. If so, get the URL again
    # using the local directory's config file.
    if os.path.isdir(source_long_url):
        git_config_file = os.path.join(source_long_url, "config")
        source_long_url = run_command(
            "git config --file %s --get remote.origin.url" % git_config_file,
            source_file_dir)

    # The URL can have several formats. A full list can be found using
    # git help clone. Extract the server part with a regex.
    url_match = re.search(".*(://|@)([^:/]*).*", source_long_url)
    source_server = url_match.group(2)

    # (2) Get the branch for this file. If it's detached and just shows HEAD
    # then set it to master and hope we can get the correct revision from there.
    source_branch = run_command(
        "git rev-parse --abbrev-ref HEAD", source_file_dir)
    if source_branch == "HEAD":
        source_branch = "master"

    # (3) Since the repo root directory name can be changed without affecting
    # git, we need to extract the name from something more reliable.
    # The git URL has a repo name that we could use. We just need to strip off
    # everything around it - from gitolite@git.someserver.com:SomeRepo.git/ to
    # SomeRepo.
    source_repo_dir = re.sub("/$", "", source_long_url)
    source_repo_dir = re.sub("\.git$", "", source_repo_dir)
    source_repo_dir = re.sub(".*[:/]", "", source_repo_dir)

    # (4) We know the file but want to remove all of the build system dependent
    # path up to and including the repository's root directory, e.g. remove
    # /home/someuser/dev/repo/projectx/
    source_toplevel = run_command(
        "git rev-parse --show-toplevel", source_file_dir)
    source_toplevel = source_toplevel + os.path.sep
    source_file = source_file_path.replace(source_toplevel, "")

    # (5) Get the source revision this file is part of.
    source_revision = run_command("git rev-parse HEAD", source_file_dir)

    # Assemble the repository path according to the Socorro format.
    socorro_reference = "git:%s/%s:%s/%s:%s" % \
        (source_server, source_branch,
        source_repo_dir, source_file,
        source_revision)

    return socorro_reference