| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | import os, sys, re, datetime, time, getopt
|
| | from urllib.parse import urlparse
|
| | import xml.sax
|
| | import xml.sax.handler
|
| | import xml.sax.xmlreader
|
| |
|
| | try:
|
| | from StringIO import StringIO
|
| | except ImportError:
|
| | from io import StringIO
|
| |
|
| |
|
| |
|
| | class SvnHandler(xml.sax.handler.ContentHandler):
|
| | def __init__(self):
|
| | super().__init__()
|
| | self.inUrl = 0
|
| | self.inDate = 0
|
| | self.mapping = {}
|
| |
|
| | def startElement(self, name, attributes):
|
| | if name == "entry":
|
| | self.buffer = ""
|
| | self.mapping["Rev"] = attributes["revision"]
|
| | elif name == "url":
|
| | self.inUrl = 1
|
| | elif name == "date":
|
| | self.inDate = 1
|
| |
|
| | def characters(self, data):
|
| | if self.inUrl:
|
| | self.buffer += data
|
| | elif self.inDate:
|
| | self.buffer += data
|
| |
|
| | def endElement(self, name):
|
| | if name == "url":
|
| | self.inUrl = 0
|
| | self.mapping["Url"] = self.buffer
|
| | self.buffer = ""
|
| | elif name == "date":
|
| | self.inDate = 0
|
| | self.mapping["Date"] = self.buffer
|
| | self.buffer = ""
|
| |
|
| |
|
| | class VersionControl:
|
| | def __init__(self):
|
| | self.rev = ""
|
| | self.date = ""
|
| | self.url = ""
|
| |
|
| | def extractInfo(self, srcdir, bindir):
|
| | return False
|
| |
|
| | def printInfo(self):
|
| | print("")
|
| |
|
| | def writeVersion(self, lines):
|
| | content = []
|
| | for line in lines:
|
| | line = line.replace("$WCREV$", self.rev)
|
| | line = line.replace("$WCDATE$", self.date)
|
| | line = line.replace("$WCURL$", self.url)
|
| | content.append(line)
|
| | return content
|
| |
|
| |
|
| | class UnknownControl(VersionControl):
|
| | def extractInfo(self, srcdir, bindir):
|
| |
|
| | if os.path.exists(bindir + "/src/Build/Version.h.out"):
|
| | return False
|
| | self.rev = "Unknown"
|
| | self.date = "Unknown"
|
| | self.url = "Unknown"
|
| | return True
|
| |
|
| | def printInfo(self):
|
| | print("Unknown version control")
|
| |
|
| |
|
| | class DebianChangelog(VersionControl):
|
| | def extractInfo(self, srcdir, bindir):
|
| |
|
| | if os.path.exists(bindir + "/src/Build/Version.h.out"):
|
| | return False
|
| | try:
|
| | f = open(srcdir + "/debian/changelog")
|
| | except Exception:
|
| | return False
|
| | c = f.readline()
|
| | f.close()
|
| | r = re.search("bzr(\\d+)", c)
|
| | if r is not None:
|
| | self.rev = r.groups()[0] + " (Launchpad)"
|
| |
|
| | t = time.localtime()
|
| | self.date = ("%d/%02d/%02d %02d:%02d:%02d") % (
|
| | t.tm_year,
|
| | t.tm_mon,
|
| | t.tm_mday,
|
| | t.tm_hour,
|
| | t.tm_min,
|
| | t.tm_sec,
|
| | )
|
| | self.url = "https://code.launchpad.net/~vcs-imports/freecad/trunk"
|
| | return True
|
| |
|
| | def printInfo(self):
|
| | print("debian/changelog")
|
| |
|
| |
|
| | class BazaarControl(VersionControl):
|
| | def extractInfo(self, srcdir, bindir):
|
| | info = os.popen("bzr log -l 1 %s" % (srcdir)).read()
|
| | if len(info) == 0:
|
| | return False
|
| | lines = info.split("\n")
|
| | for i in lines:
|
| | r = re.match("^revno: (\\d+)$", i)
|
| | if r is not None:
|
| | self.rev = r.groups()[0]
|
| | continue
|
| | r = re.match("^timestamp: (\\w+ \\d+-\\d+-\\d+ \\d+:\\d+:\\d+)", i)
|
| | if r is not None:
|
| | self.date = r.groups()[0]
|
| | continue
|
| | return True
|
| |
|
| | def printInfo(self):
|
| | print("bazaar")
|
| |
|
| |
|
| | class DebianGitHub(VersionControl):
|
| |
|
| |
|
| |
|
| | def extractInfo(self, srcdir, bindir):
|
| | try:
|
| | f = open(srcdir + "/debian/git-build-recipe.manifest")
|
| | except Exception:
|
| | return False
|
| |
|
| |
|
| | recipe = f.readline()
|
| | commit = f.readline()
|
| | f.close()
|
| |
|
| | base_url = "https://api.github.com"
|
| | owner = "FreeCAD"
|
| | repo = "FreeCAD"
|
| | sha = commit[commit.rfind(":") + 1 : -1]
|
| | self.hash = sha
|
| |
|
| | try:
|
| | import requests
|
| |
|
| | request_url = "{}/repos/{}/{}/commits?per_page=1&sha={}".format(
|
| | base_url, owner, repo, sha
|
| | )
|
| | commit_req = requests.get(request_url)
|
| | if not commit_req.ok:
|
| | return False
|
| |
|
| | commit_date = commit_req.headers.get("last-modified")
|
| |
|
| | except Exception:
|
| |
|
| | commit_date = recipe[recipe.rfind("~") + 1 : -1]
|
| |
|
| | try:
|
| |
|
| | t = time.strptime(commit_date, "%a, %d %b %Y %H:%M:%S GMT")
|
| | commit_date = ("%d/%02d/%02d %02d:%02d:%02d") % (
|
| | t.tm_year,
|
| | t.tm_mon,
|
| | t.tm_mday,
|
| | t.tm_hour,
|
| | t.tm_min,
|
| | t.tm_sec,
|
| | )
|
| | except Exception:
|
| | t = time.strptime(commit_date, "%Y%m%d%H%M")
|
| | commit_date = ("%d/%02d/%02d %02d:%02d:%02d") % (
|
| | t.tm_year,
|
| | t.tm_mon,
|
| | t.tm_mday,
|
| | t.tm_hour,
|
| | t.tm_min,
|
| | t.tm_sec,
|
| | )
|
| |
|
| | self.date = commit_date
|
| | self.branch = "unknown"
|
| |
|
| | try:
|
| |
|
| |
|
| | branch_url = "https://github.com/{}/{}/branch_commits/{}".format(owner, repo, sha)
|
| | branch_req = requests.get(branch_url)
|
| | if branch_req.ok:
|
| | html = branch_req.text
|
| | pattern = '<li class="branch"><a href='
|
| | start = html.find(pattern) + len(pattern)
|
| | end = html.find("\n", start)
|
| | link = html[start:end]
|
| | start = link.find(">") + 1
|
| | end = link.find("<", start)
|
| | self.branch = link[start:end]
|
| |
|
| | link = commit_req.headers.get("link")
|
| | beg = link.rfind("&page=") + 6
|
| | end = link.rfind(">")
|
| | self.rev = link[beg:end] + " (GitHub)"
|
| | except Exception:
|
| | pass
|
| |
|
| | self.url = "git://github.com/{}/{}.git {}".format(owner, repo, self.branch)
|
| | return True
|
| |
|
| | def writeVersion(self, lines):
|
| | content = VersionControl.writeVersion(self, lines)
|
| | content.append("// Git relevant stuff\n")
|
| | content.append('#define FCRepositoryHash "%s"\n' % (self.hash))
|
| | content.append('#define FCRepositoryBranch "%s"\n' % (self.branch))
|
| | return content
|
| |
|
| | def printInfo(self):
|
| | print("Debian/GitHub")
|
| |
|
| |
|
| | class GitControl(VersionControl):
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | def getremotes(self):
|
| | """return a mapping of remotes and their fetch urls"""
|
| | rr = os.popen("git remote -v")
|
| | rrstr = rr.read().strip()
|
| | if rr.close() is None:
|
| | self.remotes = dict(
|
| | l[:-8].split("\t") for l in rrstr.splitlines() if l.endswith(" (fetch)")
|
| | )
|
| | self.branchlst = (
|
| | os.popen("git show -s --pretty=%d HEAD").read().strip(" ()\n").split(", ")
|
| | )
|
| |
|
| | def geturl(self):
|
| | urls = []
|
| | for ref in self.branchlst:
|
| | if "/" in ref:
|
| | remote, branch = ref.split("/", 1)
|
| | if remote in self.remotes:
|
| | url = self.remotes[remote]
|
| |
|
| | match = re.match(r"git@github\.com:(\S+?)/(\S+\.git)", url) or re.match(
|
| | r"https://github\.com/(\S+)/(\S+\.git)", url
|
| | )
|
| | if match is not None:
|
| | url = "git://github.com/%s/%s" % match.groups()
|
| | match = re.match(r"ssh://\S+?@(\S+)", url)
|
| | if match is not None:
|
| | url = "git://%s" % match.group(1)
|
| | parsed_url = urlparse(url)
|
| | entryscore = (
|
| | url == "git://github.com/FreeCAD/FreeCAD.git",
|
| | parsed_url.netloc == "github.com",
|
| | branch == self.branch,
|
| | branch == "main",
|
| | "@" not in url,
|
| | )
|
| |
|
| | if branch == self.branch:
|
| | url = "%s %s" % (url, branch)
|
| | urls.append((entryscore, url))
|
| | if len(urls) > 0:
|
| | self.url = sorted(urls)[-1][1]
|
| | else:
|
| | self.url = "Unknown"
|
| |
|
| | def revisionNumber(self, srcdir, origin=None):
|
| | """sets the revision number
|
| | for main and release branches all commits are counted
|
| | for other branches. The version number is split in to two parts:
|
| | The first number reflects the number of commits in common with the
|
| | blessed main repository. The second part (separated by " +") reflects
|
| | the number of commits that are different from the main repository"""
|
| | referencecommit = "7d8e53aaab17961d85c5009de34f69f2af084e8b"
|
| | referencerevision = 14555
|
| |
|
| | result = None
|
| | null_device = "nul" if os.name == "nt" else "/dev/null"
|
| | countallfh = os.popen(f"git rev-list --count {referencecommit}..HEAD 2>{null_device}")
|
| | countallstr = countallfh.read().strip()
|
| | if countallfh.close() is not None:
|
| | date_object = datetime.datetime.strptime(self.date, "%Y/%m/%d %H:%M:%S")
|
| | formatted_date = date_object.strftime("%Y%m%d")
|
| | self.rev = f"{formatted_date} (Git shallow)"
|
| | return
|
| | else:
|
| | countall = int(countallstr)
|
| |
|
| | if (
|
| | origin is not None
|
| | and self.branch.lower() != "main"
|
| | and "release" not in self.branch.lower()
|
| | ):
|
| | mbfh = os.popen("git merge-base %s/main HEAD" % origin)
|
| | mergebase = mbfh.read().strip()
|
| | if mbfh.close() is None:
|
| | try:
|
| | countmergebase = int(
|
| | os.popen("git rev-list --count %s..%s" % (referencecommit, mergebase))
|
| | .read()
|
| | .strip()
|
| | )
|
| | if countall > countmergebase:
|
| | result = "%04d +%d (Git)" % (
|
| | countmergebase + referencerevision,
|
| | countall - countmergebase,
|
| | )
|
| | except ValueError:
|
| | pass
|
| | self.rev = result or ("%04d (Git)" % (countall + referencerevision))
|
| |
|
| | def namebranchbyparents(self):
|
| | """name multiple branches in case that the last commit was a merge
|
| | a merge is identified by having two or more parents
|
| | if the describe does not return a ref name (the hash is added)
|
| | if one parent is the main and the second one has no ref name, one branch was
|
| | merged."""
|
| | parents = os.popen("git log -n1 --pretty=%P").read().strip().split(" ")
|
| | if len(parents) >= 2:
|
| | parentrefs = []
|
| | names = []
|
| | hasnames = 0
|
| | for p in parents:
|
| | refs = os.popen("git show -s --pretty=%%d %s" % p).read().strip(" ()\n").split(", ")
|
| | if refs[0] != "":
|
| | parentrefs.append(refs)
|
| | names.append(refs[-1])
|
| | hasnames += 1
|
| | else:
|
| | parentrefs.append(p)
|
| | names.append(p[:7])
|
| | if hasnames >= 2:
|
| | self.branch = ",".join(names)
|
| |
|
| | def extractInfo(self, srcdir, bindir):
|
| | self.hash = os.popen("git log -1 --pretty=format:%H").read().strip()
|
| | if self.hash == "":
|
| | return False
|
| |
|
| | import time
|
| |
|
| | info = os.popen("git log -1 --date=raw --pretty=format:%cd").read()
|
| |
|
| |
|
| | self.date = time.strftime(
|
| | "%Y/%m/%d %H:%M:%S", time.gmtime(float(info.strip().split(" ", 1)[0]))
|
| | )
|
| | for self.branch in os.popen("git branch --no-color").read().split("\n"):
|
| | if re.match(r"\*", self.branch) is not None:
|
| | break
|
| | self.branch = self.branch[2:]
|
| | self.getremotes()
|
| |
|
| | self.geturl()
|
| | origin = None
|
| | for fetchurl in (
|
| | "git@github.com:FreeCAD/FreeCAD.git",
|
| | "https://github.com/FreeCAD/FreeCAD.git",
|
| | ):
|
| | for key, url in self.remotes.items():
|
| | if fetchurl in url:
|
| | origin = key
|
| | break
|
| | if origin is not None:
|
| | break
|
| |
|
| | self.revisionNumber(srcdir, origin)
|
| | if self.branch.lower() != "main" and "release" not in self.branch.lower():
|
| | self.namebranchbyparents()
|
| | if self.branch == "(no branch)":
|
| | if len(self.branchlst) >= 2:
|
| | self.branch = self.branchlst[1]
|
| | else:
|
| | self.branch = "(%s)" % os.popen("git describe --all --dirty").read().strip()
|
| |
|
| |
|
| | if self.url == "Unknown":
|
| | for i in info:
|
| | r = re.match("origin\\W+(\\S+)", i)
|
| | if r is not None:
|
| | self.url = r.groups()[0]
|
| | break
|
| | return True
|
| |
|
| | def printInfo(self):
|
| | print("git")
|
| |
|
| | def writeVersion(self, lines):
|
| | content = VersionControl.writeVersion(self, lines)
|
| | content.append("// Git relevant stuff\n")
|
| | content.append('#define FCRepositoryHash "%s"\n' % (self.hash))
|
| | content.append('#define FCRepositoryBranch "%s"\n' % (self.branch))
|
| | return content
|
| |
|
| |
|
| | class MercurialControl(VersionControl):
|
| | def extractInfo(self, srcdir, bindir):
|
| | return False
|
| |
|
| | def printInfo(self):
|
| | print("mercurial")
|
| |
|
| |
|
| | class Subversion(VersionControl):
|
| | def extractInfo(self, srcdir, bindir):
|
| | parser = xml.sax.make_parser()
|
| | handler = SvnHandler()
|
| | parser.setContentHandler(handler)
|
| |
|
| |
|
| | Ver = os.popen("svnversion %s -n" % (srcdir)).read()
|
| | Info = os.popen("svn info %s --xml" % (srcdir)).read()
|
| | try:
|
| | inpsrc = xml.sax.InputSource()
|
| | strio = StringIO.StringIO(Info)
|
| | inpsrc.setByteStream(strio)
|
| | parser.parse(inpsrc)
|
| | except Exception:
|
| | return False
|
| |
|
| |
|
| | self.url = handler.mapping["Url"]
|
| | self.rev = handler.mapping["Rev"]
|
| | self.date = handler.mapping["Date"]
|
| | self.date = self.date[:19]
|
| |
|
| | self.date = self.date.replace("T", " ")
|
| | self.date = self.date.replace("-", "/")
|
| |
|
| |
|
| | m = time.strptime(self.date, "%Y/%m/%d %H:%M:%S")
|
| |
|
| | l = (
|
| | m.tm_year,
|
| | m.tm_mon,
|
| | m.tm_mday,
|
| | m.tm_hour,
|
| | m.tm_min,
|
| | m.tm_sec,
|
| | m.tm_wday,
|
| | m.tm_yday,
|
| | 0,
|
| | )
|
| |
|
| | t = time.mktime(l) - time.timezone
|
| | self.date = time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(t))
|
| |
|
| |
|
| | self.time = time.strftime("%Y/%m/%d %H:%M:%S")
|
| |
|
| | self.mods = "Src not modified"
|
| | self.mixed = "Src not mixed"
|
| | self.range = self.rev
|
| |
|
| |
|
| | r = re.search("M$", Ver)
|
| | if r is not None:
|
| | self.mods = "Src modified"
|
| |
|
| |
|
| | r = re.match("^\\d+\\:\\d+", Ver)
|
| | if r is not None:
|
| | self.mixed = "Src mixed"
|
| | self.range = Ver[: r.end()]
|
| | return True
|
| |
|
| | def printInfo(self):
|
| | print("subversion")
|
| |
|
| |
|
| | def main():
|
| |
|
| |
|
| |
|
| | srcdir = "."
|
| | bindir = "."
|
| | try:
|
| | opts, args = getopt.getopt(sys.argv[1:], "sb:", ["srcdir=", "bindir="])
|
| | except getopt.GetoptError:
|
| | pass
|
| |
|
| | for o, a in opts:
|
| | if o in ("-s", "--srcdir"):
|
| | srcdir = a
|
| | if o in ("-b", "--bindir"):
|
| | bindir = a
|
| |
|
| | vcs = [
|
| | GitControl(),
|
| | DebianGitHub(),
|
| | BazaarControl(),
|
| | Subversion(),
|
| | MercurialControl(),
|
| | DebianChangelog(),
|
| | UnknownControl(),
|
| | ]
|
| | for i in vcs:
|
| | if i.extractInfo(srcdir, bindir):
|
| |
|
| | inp = open("%s/src/Build/Version.h.in" % (bindir))
|
| | lines = inp.readlines()
|
| | inp.close()
|
| | lines = i.writeVersion(lines)
|
| | out = open("%s/src/Build/Version.h.out" % (bindir), "w")
|
| | out.writelines(lines)
|
| | out.write("\n")
|
| | out.close()
|
| | i.printInfo()
|
| | sys.stdout.write("%s/src/Build/Version.h.out written\n" % (bindir))
|
| | break
|
| |
|
| |
|
| | if __name__ == "__main__":
|
| | main()
|
| |
|