#!/usr/bin/env python3 # Usage: go mod graph | ./gomodtorbm import errno import os import os.path import re import sys STAGING_DIR = "staging" MAIN_VERSION = "v3.1.41" # Projects that already exist. TERMINALS = { "golang.org/x/crypto", "golang.org/x/net", "golang.org/x/sys", "golang.org/x/text", } def strip_version_suffix(import_path): return re.sub(r'/+v[0-9]+$', "", import_path) def parse_spec(spec): print("Parsing "+ spec) try: import_path, version = spec.split("@") except ValueError: import_path = spec version = MAIN_VERSION return strip_version_suffix(import_path), Version(version) class Version: def __init__(self, version): self.version = version def tuple(self): m = re.match(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)-(?:0\.)?([0-9]+)-([0-9a-fA-F]+)(\+incompatible)?$', self.version) if m: g = m.groups() return int(g[0]), int(g[1]), int(g[2]), g[3], g[4] m = re.match(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)-(rc\.[0-9\.]+)-([0-9a-fA-F]+)$', self.version) if m: g = m.groups() return int(g[0]), int(g[1]), int(g[2]), g[3], g[4] m = re.match(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)-(rc\.[0-9]+)$', self.version) if m: g = m.groups() return int(g[0]), int(g[1]), int(g[2]), g[3], "" m = re.match(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)-([0-9]{4}\.[0-9]+\.[0-9]+)$', self.version) if m: g = m.groups() return int(g[0]), int(g[1]), int(g[2]), g[3], "" m = re.match(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)(\+incompatible)?$', self.version) if m: g = m.groups() return int(g[0]), int(g[1]), int(g[2]), "", "" assert False, self.version def __str__(self): major, minor, micro, _, hash = self.tuple() if hash: if major != 0 or minor != 0 or micro != 0: return "{} # v{}.{}.{}".format(hash, major, minor, micro) else: return "{}".format(hash) else: return "v{}.{}.{}".format(major, minor, micro) # https://github.com/golang/proposal/blob/master/design/24301-versioned-go.md#update-timing--high-fidelity-builds # https://github.com/golang/proposal/blob/master/design/24301-versioned-go.md#compatibility def __lt__(self, other): return self.tuple() < other.tuple() PROJECT_MAP = {} # Create a unique Project for each unique import path, and set version to the # maximum seen. def lookup_project(spec): import_path, version = parse_spec(spec) try: project = PROJECT_MAP[import_path] except KeyError: project = Project(spec) PROJECT_MAP[import_path] = project print("Project version = ", project.version, "version = ", version) if project.version < version: project.version = version return project class Project: def __init__(self, spec): self.import_path, self.version = parse_spec(spec) self.deps = set() def add_dep(self, dep): self.deps.add(dep) # Cheesy heuristics to choose an rbm package name given an import path. def project_name(self): m = re.match(r'^gopkg\.in/([\w_-]+)\.v[0-9]+$', self.import_path) if m: return "go" + m.group(1) m = re.match(r'^github\.com/(?:golang|hpcloud)/([\w_-]+)$', self.import_path) if m: return "go" + m.group(1) try: return { "golang.org/x/crypto": "goxcrypto", "golang.org/x/mod": "goxmod", "golang.org/x/net": "goxnet", "golang.org/x/sys": "goxsys", "golang.org/x/sync": "goxsync", "golang.org/x/term": "goxterm", "golang.org/x/text": "goxtext", "golang.org/x/tools": "goxtools", "golang.org/x/xerrors": "goxxerrors", "github.com/pkg/errors": "goerrors", "google.golang.org/protobuf": "protobuf", }[self.import_path] except KeyError: pass if self.import_path.startswith("github.com/pion"): return "-".join(self.import_path.split("/")[1:]) elif self.import_path.startswith("github.com/"): return "-".join(self.import_path.split("/")[2:]) assert False, self.import_path def output(self, f): print("""\ # vim: filetype=yaml sw=2 version: '[% c("abbrev") %]' git_url: https://{import_path} git_hash: {version} filename: '[% project %]-[% c("version") %]-[% c("var/osname") %]-[% c("var/build_id") %].tar.gz' build: '[% c("projects/go/var/build_go_lib") %]' var: container: use_container: 1 go_lib: {import_path}\ """.format(import_path=self.import_path, version=self.version), file=f) if self.deps: print(" go_lib_deps:", file=f) for dep in sorted(self.deps, key=lambda x: x.project_name()): print(" - {project_name}".format(project_name=dep.project_name()), file=f) print("""\ input_files: - project: container-image - name: go project: go\ """, file=f) for dep in sorted(self.deps, key=lambda x: x.project_name()): print("""\ - name: {project_name} project: {project_name}\ """.format(project_name=dep.project_name()), file=f) def makedirs(path): try: os.makedirs(path) except OSError as e: if e.errno != errno.EEXIST: raise for line in sys.stdin: mod_spec, req_spec = line.strip().split() mod_project = lookup_project(mod_spec) req_project = lookup_project(req_spec) mod_project.add_dep(req_project) for project in PROJECT_MAP.values(): if project.import_path in TERMINALS: continue project_path = os.path.join(STAGING_DIR, "projects", project.project_name()) makedirs(project_path) with open(os.path.join(project_path, "config"), "w") as f: print(project_path) project.output(f)