Skip to content

Instantly share code, notes, and snippets.

@MikuAuahDark
Last active October 11, 2023 04:29
Show Gist options
  • Save MikuAuahDark/d9c099f5714e09a765496471c2827a55 to your computer and use it in GitHub Desktop.
Save MikuAuahDark/d9c099f5714e09a765496471c2827a55 to your computer and use it in GitHub Desktop.
Python Script to Help Embedding Source Indexing Data to PDB

This is a simple Python script to help embedding source indexing information to PDBs using HTTP source server provider.

If you're looking to source-index your PDB and you're using GitHub, then this is perfect. If you need more information on how it works, take a look at Baldur Karlsson's writings for GitHub source indexing and Bruce Dawson's blogpost about source indexing in general.

To use this script, you need to specify a source-URL mapping which can be specified with --source. For example, if your project is hosted at https://github.com/love2d/love and you're building for commit 4b825dc642cb6eb9a060e54bf8d69288fbee4904, then the URL to access the source file can be re-constructed as https://raw.githubusercontent.com/love2d/love/4b825dc642cb6eb9a060e54bf8d69288fbee4904. Now say you cloned the repository to D:\love2d, then you specify the --source like this:

--source D:\love2d https://raw.githubusercontent.com/love2d/love/4b825dc642cb6eb9a060e54bf8d69288fbee4904

Note that --source accepts 2 space-delimited string (--source <path> <url>) and the order matters. If your project span across multiple projects (i.e. submodules), pass additional --source as needed. Note that at least --source must be specified.

This script assume srctool.exe and pdbstr.exe are in your %PATH%. If this is not the case, specify the location of those executables with --srctool and --pdbstr respectively.

Once you pass the source-URL mapping, you can pass PDB(s) to embed.

*.pdb path\pdb1.pdb path2\pdb2.pdb
# Python script to help embedding Source Index in PDB files.
# See end of file for license.
import argparse
import glob
import os
import subprocess
def find_best_same_path(paths: list[str]):
i = 1
result = ""
while True:
result = paths[0][:i]
if len(result) < i:
return result
for path in paths:
if path[:i] != result:
return result[:-1]
i = i + 1
class SourceIndexMapper:
def __init__(self, path: str, url: str, /):
# Ensure path and url has no trailing slash
if path[-1] in ("\\", "/"):
self.path = path[:-1]
else:
self.path = path
if url[-1] == "/":
self._url = url[:-1]
else:
self._url = url
# Check if the specified path is acceptable
def accepts(self, path: str):
return path.startswith(os.path.abspath(self.path) + os.sep)
# Transform from absolute path to URL
def transform(self, file: str):
# The substring gives path that starts with "/"
return self._url + file.replace("\\", "/")[len(self.path) :]
@property
def url(self):
return self._url
class SrcsrvWriter:
def __init__(self, output: str) -> None:
self._output = output
self.files = [] # type: list[tuple[str, str]]
def add(self, localfile: str, urlfile: str):
self.files.append((localfile, urlfile))
return self
def write(self):
if not self.files:
return False
file = open(self._output, "w", encoding="UTF-8", newline="")
base_http = find_best_same_path([x[1] for x in self.files])
len_base_http = len(base_http)
file.write("SRCSRV: ini ------------------------------------------------\r\n")
file.write("VERSION=2\r\nVERCTRL=http\r\n")
file.write("SRCSRV: variables ------------------------------------------\r\n")
file.write(f"HTTP_ALIAS={base_http}\r\n")
file.write("HTTP_EXTRACT_TARGET=%HTTP_ALIAS%%var2%\r\nSRCSRVTRG=%HTTP_EXTRACT_TARGET%\r\n")
file.write("SRCSRV: source files ---------------------------------------\r\n")
for f in self.files:
file.write(f[0])
file.write("*")
file.write(f[1][len_base_http:])
file.write("\r\n")
file.write("SRCSRV: end ------------------------------------------------\r\n")
file.close()
return True
@property
def output(self):
return self._output
source_mappers = [] # type: list[SourceIndexMapper]
def get_source_list(srctool: str, pdb: str):
result = subprocess.run([srctool, "-r", pdb], capture_output=True, encoding="UTF-8")
if result.returncode == 4294967295:
raise RuntimeError("srctool.exe returns -1")
return result.stdout.split("\n", result.returncode)[:-1]
def embed_pdb(pdbstr: str, pdb: str, srcsrv: str):
subprocess.run([pdbstr, "-w", f"-p:{pdb}", "-s:srcsrv", f"-i:{srcsrv}"], check=True)
def main():
global source_mappers
parser = argparse.ArgumentParser()
parser.add_argument("--srctool", help="path to srctool.exe.", default="srctool.exe")
parser.add_argument("--pdbstr", help="path to pdbstr.exe.", default="pdbstr.exe")
parser.add_argument("--dry", help="don't embed, only write the _srcsrv.txt file.", action="store_true")
parser.add_argument(
"--source",
help="path-URL tuple to match and substitute in this order.",
metavar=("PATH", "URL"),
action="append",
nargs=2,
)
parser.add_argument("pdb", help="PDB files to map and source index.", nargs="+")
args = parser.parse_args()
if not args.source or len(args.source) == 0:
parser.error("at least one --source is required")
for mapping in args.source:
source_mappers.append(SourceIndexMapper(*mapping))
for pdb_list in args.pdb:
for pdb in glob.glob(pdb_list):
files = get_source_list(args.srctool, pdb)
srcsrv = SrcsrvWriter(os.path.basename(pdb) + "_srcsrv.txt")
for file in files:
# Find transform
for mapper in source_mappers:
if mapper.accepts(file):
srcsrv.add(file, mapper.transform(file))
break
if srcsrv.write() and (not args.dry):
embed_pdb(args.pdbstr, pdb, srcsrv.output)
if __name__ == "__main__":
main()
# ------------------------------------------------------------------------------
# This software is available under 2 licenses -- choose whichever you prefer.
# ------------------------------------------------------------------------------
# ALTERNATIVE A - MIT License
#
# Copyright (c) 2023 Miku AuahDark
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# ------------------------------------------------------------------------------
# ALTERNATIVE B - Public Domain (www.unlicense.org)
#
# This is free and unencumbered software released into the public domain.
# Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
# software, either in source code form or as a compiled binary, for any purpose,
# commercial or non-commercial, and by any means.
#
# In jurisdictions that recognize copyright laws, the author or authors of this
# software dedicate any and all copyright interest in the software to the public
# domain. We make this dedication for the benefit of the public at large and to
# the detriment of our heirs and successors. We intend this dedication to be an
# overt act of relinquishment in perpetuity of all present and future rights to
# this software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# ------------------------------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment