summaryrefslogtreecommitdiff
path: root/util
diff options
context:
space:
mode:
authorMCApollo <34170230+MCApollo@users.noreply.github.com>2019-04-20 20:49:46 -0500
committerMCApollo <34170230+MCApollo@users.noreply.github.com>2019-04-23 20:18:47 -0500
commit12335518ab39608d58370c85ff9f5384ad2aa5f7 (patch)
tree352d81f2a2de3f1252af732080ec0fde38c13b4d /util
parenta2b26ad12d4fa12f0273645caf4be6d0b8b71e7c (diff)
Ported in the homebrew-marauder for a hacky update/import system.
TODO: Maybe add a license & fix up messy code.
Diffstat (limited to 'util')
-rw-r--r--util/.marauder/marauder/__init__.py4
-rw-r--r--util/.marauder/marauder/install.py200
-rw-r--r--util/.marauder/marauder/parser.py277
-rw-r--r--util/.marauder/marauder/version.py40
-rwxr-xr-xutil/brew-convert.py225
5 files changed, 746 insertions, 0 deletions
diff --git a/util/.marauder/marauder/__init__.py b/util/.marauder/marauder/__init__.py
new file mode 100644
index 000000000..379afe39b
--- /dev/null
+++ b/util/.marauder/marauder/__init__.py
@@ -0,0 +1,4 @@
+__version__ = '2.0'
+from .parser import parse
+from .version import version
+from .install import makesh \ No newline at end of file
diff --git a/util/.marauder/marauder/install.py b/util/.marauder/marauder/install.py
new file mode 100644
index 000000000..3c7b20405
--- /dev/null
+++ b/util/.marauder/marauder/install.py
@@ -0,0 +1,200 @@
+#!/usr/bin/env python3
+import re
+import csv
+import sys
+
+
+# FIXME 18 Errors - 18/02
+
+def _variable(word):
+
+ def ENV():
+ # Easier to parse & handle this way.
+ ENV.cc = '${PKG_TARG}-clang'
+ ENV.ld = '${PKG_TARG}-ld'
+ ENV.cxx = '${PKG_TARG}-clang++'
+ ENV.cflags = '${CFLAGS}'
+ ENV.ldflags = '${LDFLAGS}'
+ ENV.cxxflags = '${CXXFLAGS}'
+ ENV.cppflags = '${CPPFLAGS}'
+ ENV()
+
+ prefix = '${PKG_TAPF}'
+ man = '${PKG_TAPF}' + '/share/man'
+ pkgshare = '${PKG_TAPF}' + '/share'
+ lib = '${PKG_TAPF}' + '/lib'
+ share = '${PKG_TAPF}' + '/share'
+
+ sysconfig = '/etc'
+ bin = '${PKG_TAPF}' + 'bin'
+
+ # include = '$SDK' + '/usr/include'
+
+ b = ['ENV', 'prefix', 'man', 'pkgshare', 'lib', 'include']
+
+ w = ''.join(word)
+ try:
+ if not any([x for x in b if w.startswith(x)]):
+ return False # Prevent code execution
+ word = eval(w)
+ except NameError:
+ return False
+ except AttributeError:
+# print(f'WARNING: {w} needs to be defined.', file=sys.stderr)
+ # raise
+ return False
+
+ return word
+
+
+
+def _system():
+ """ Handles strings like: system "make", "install """
+
+ def _configure():
+ nonlocal l
+ banned = [#'--disable-debug',
+ '--prefix=',
+ '--man=',
+ '--mandir=',
+ '--localstatedir=',
+ '--sysconfdir=',
+ '--infodir=',
+ '--datadir'
+ '--disable-dependency-tracking',
+ '--disable-debug']
+ regex = re.compile(f"{'|'.join(banned)}*")
+ track = ''
+ for x in l:
+ if not any([a for a in x.split() if regex.search(a)]):
+ track += f' {x}'
+
+ l = track.strip()
+ try:
+ conf = re.search('(^.+/configure)', l).group(1)
+ except AttributeError:
+ # print("ERROR: configure - AttributeError - NoneType has no attribute 'group'", file=sys.stderr)
+ conf = l[0]
+ if conf == './configure':
+ l = l.replace(conf, f"pkg:configure")
+ else:
+ l = l.replace(conf, f"PKG_CONF={conf} pkg:configure")
+
+ def _make():
+ nonlocal l
+ l = ' '.join(l)
+ l = l.replace('install', 'DESTDIR=${PKG_DEST} install', 1)
+# if == 'make': # if the line is just `make`
+# l = l.replace('make', 'pkg:make') # Hand it to pkg:make for the args
+
+ def _cmake():
+ nonlocal l
+ l = ' '.join(l)
+ l = l.replace(r'*std_cmake_args', r'-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}')
+
+
+ l = re.sub(r'^system', '', g_line).strip()
+ l = list(csv.reader([l], quotechar='"', skipinitialspace=True))[0]
+
+ # Convert #{variable} to a bash variable.
+ variable = [k for k, p in enumerate(l) if re.findall(r'#{(?:[A-Za-z0-9.]+)}', p)]
+ if variable:
+ for p in variable:
+ n = re.findall(r'#{([A-Za-z0-9.]+)}', l[p])
+ k = _variable(n)
+ if not k:
+ continue # _variable() returned False for some reason.
+ l[p] = l[p].replace(f"#{{{''.join(n)}}}", f'{k}')
+
+ if 'configure' in l[0]:
+ _configure()
+ elif 'cmake' in l[0]:
+ _cmake()
+ elif 'make' in l[0]:
+ _make()
+ else:
+ l = ' '.join(l)
+
+ return l
+
+
+def _inreplace():
+ # TODO: inreplace |s|, let replace be a bash function, so remove any shell characters.
+ return -1
+
+def _clean(file):
+ """ Cleans up input for parsing """
+ file = iter(file)
+
+ def list():
+ # FIXME: 2 Formulas will error out here - ['inreplace %w[configure Makefile], for example.
+ nonlocal x
+ if re.search(r'%[A-Za-z]\[', x):
+ track = x
+ if x.endswith("]") or '].each' in x:
+ return track
+ else:
+ while True:
+ l = next(file)
+ track += f'{l}'
+ if re.search(r'^\]\.?', l):
+ break
+ track += ' '
+ return track
+ return False
+
+ def newlines():
+ nonlocal x
+ if not x.endswith(','):
+ return False
+ line = x
+ l = x
+ while True:
+ if not line.endswith(','):
+ break
+ line = next(file)
+ l += f' {line}'
+ return l
+
+ out = []
+ for x in file:
+ _list = list()
+ if _list:
+ out += [_list]
+ else:
+ _newline = newlines()
+ if _newline:
+ out += [_newline]
+ else:
+ out += [x]
+ return out
+
+
+def makesh(file=None):
+ try:
+ data = _clean(file)
+ except StopIteration:
+ # print(f'ERROR: parse - StopIteration - {file}', file=sys.stderr)
+ return None # Error
+ global g_line # Global line
+ n = []
+ for g_line in data:
+ if g_line.endswith('if build.head?'):
+ continue # Doesn't support head.
+ elif g_line.startswith("system"):
+ n += [_system()]
+ elif g_line.startswith("mkdir"):
+ try:
+ cd = re.findall(r'\"(.+?)\"', g_line)[0]
+ except IndexError:
+ # TODO - Fix index error
+ # print("ERROR: parse - IndexError - cd regex failed", file=sys.stderr)
+ n += [g_line]
+ continue
+ n += [re.sub('do$', f'&& cd {cd}', g_line)]
+ elif g_line.startswith("cd"):
+ n += [re.sub('do$', '', g_line)]
+ else:
+ n += [g_line]
+
+ return n
diff --git a/util/.marauder/marauder/parser.py b/util/.marauder/marauder/parser.py
new file mode 100644
index 000000000..1d5d2fdd1
--- /dev/null
+++ b/util/.marauder/marauder/parser.py
@@ -0,0 +1,277 @@
+import re
+import sys
+
+keywords = re.compile(r"^(desc|homepage|url|mirror|bottle|patch|def|resource|depends_on|conflicts_with|__END__)")
+
+regex_quoted = re.compile(r'["\'](.*?)["\']')
+regex_resource = re.compile(r'resource "(.*?)" do')
+
+def _reset():
+ global result
+ result = {
+ "name": None,
+ "description": None,
+ "url": None,
+ "mirror": None,
+ "homepage": None,
+ "depends": [],
+ "resource": [],
+ "conflicts": [],
+ "patches": [],
+ "install": []
+ }
+
+
+def _error(msg, err):
+ print("ERROR: {} - {}".format(msg, err), file=sys.stderr)
+
+
+def _name():
+ t = _clean(next(line))
+ if 'class' not in t:
+ return # Might be text at the top of file.
+ try:
+ t = t.split()[1]
+ except IndexError as err:
+ if "class" in t:
+ _error("_name()", err)
+ raise err
+ else:
+ pass
+ result['name'] = t
+
+
+def _patch():
+ url = None
+ strip = None
+ data = None
+
+ if x.endswith("__END__"):
+ data = []
+ while True:
+ try:
+ data.append(next(line))
+ except StopIteration:
+ break
+ d = {"url": url, "data": data}
+ result["patches"].append(d)
+ return
+ try:
+ t = re.match(r'(?:patch) :p?(\d|DATA)', x).groups(0)[0]
+ except AttributeError:
+ """ `patch do` would be the case here, assume strip=1 """
+ t = "1"
+ if "DATA" in t:
+ """ We'll get called later when __END__ appears """
+ # print("Expecting DATA at EOF.") # Debug line
+ return
+ else:
+ strip = t
+ while True:
+ a = _clean(next(line))
+ # print("----> {}".format(a)) # Debug
+ if a.startswith("url"):
+ try:
+ url = regex_quoted.findall(a)[0]
+ except IndexError as err:
+ _error("IndexError in _patch()", err)
+ return -1
+ if a.startswith("end"):
+ break
+ d = {"url": url, "strip": strip}
+ result["patches"].append(d)
+
+
+def _resource():
+ name = None
+ url = None
+ if not regex_resource.match(x):
+ return # TEMP, CLEAN UP
+ # Resource "XX" do
+ try:
+ name = regex_quoted.findall(x)[0]
+ except IndexError as err:
+ _error("IndexError in _resource() (name)", err)
+ return -1
+ while True:
+ try:
+ t = _clean(next(line))
+ except StopIteration as err:
+ _error("StopIteration in _resource()", err)
+ break
+ # print(t) # debug
+ if t.startswith("url"):
+ try:
+ url = regex_quoted.findall(t)[0]
+ except IndexError as err:
+ _error("IndexError in _resource() (url)", err)
+ return -1
+ if t.startswith("end"):
+ break
+ d = {"name": name, "url": url}
+ result["resource"].append(d)
+ return
+
+
+def _homepage():
+ try:
+ homepage = regex_quoted.findall(x)[0]
+ result['homepage'] = homepage
+ return
+ except IndexError as err:
+ _error("IndexError in _homepage()", err)
+ return -1
+
+
+def _url():
+ try:
+ url = regex_quoted.findall(x)[0]
+ if not result['url']:
+ result['url'] = url
+ return None
+ else:
+ return url
+ except IndexError as err:
+ _error("IndexError in _url()", err)
+ return None
+
+
+def _mirror():
+ mirror = _url()
+ if mirror:
+ result['mirror'] = mirror
+ else:
+ return -1
+
+
+def _bottle():
+ return -1
+
+
+def _description():
+ try:
+ description = regex_quoted.findall(_x)[0]
+ result['description'] = description
+ except IndexError as err:
+ _error("IndexError in _description()", err)
+ return -1
+ return
+
+
+def _clean(_line):
+ """ Strips new lines and comments"""
+ _line = _line.strip()
+ if "# " in _line:
+ _line = _line[:_line.index('#')]
+ return _line
+
+
+def _depends():
+ name = None
+ build = False # Build Dependency
+ try:
+ a = None
+ try:
+ a = re.findall(r'depends_on :(\S*)', x)[0]
+ except IndexError:
+ pass
+ if a:
+ name = a
+ else:
+ name = regex_quoted.findall(x)[0]
+ except IndexError as err:
+ _error("IndexError in _depends(): line {}".format(x), err)
+ return -1
+ if x.endswith(":build"):
+ build = True
+ d = {'depend': name, 'build-depend': build}
+ result["depends"].append(d)
+ return
+
+
+def _conflicts():
+ name = None
+ reason = None
+ try:
+ if ':because ' in x:
+ t = x.split(':because ')[1]
+ reason = regex_quoted.findall(t)[0]
+ name = regex_quoted.findall(x)[0]
+ except IndexError as err:
+ _error("IndexError in _conflicts()", err)
+ return -1
+ d = {'conflict': name, 'reason': reason}
+ result['conflicts'].append(d)
+ return
+
+def _install():
+ grammar = re.compile(r'( do|if )')
+ l = []
+ c = 0
+ while True:
+ a = _clean(next(line))
+ if grammar.match(a):
+ c += 1
+ elif a.startswith("end"):
+ if c <= 0:
+ break
+ else:
+ c -= 1
+ l.append(a)
+ result["install"] = l
+ return
+
+
+def _def():
+ if "install" in x:
+ _install()
+ return -1
+
+
+def _keywords(word):
+ if word.startswith("desc") and not result['description']:
+ _description()
+ elif word.startswith("homepage") and not result['homepage']:
+ _homepage()
+ elif word.startswith("url") and not result['url']:
+ _url()
+ elif word.startswith("mirror") and not result['mirror']:
+ _mirror()
+ elif word.startswith("bottle"):
+ _bottle()
+ elif word.startswith("patch") or word.startswith("__END__"):
+ _patch()
+ elif word.startswith("resource"):
+ _resource()
+ elif word.startswith("depends_on"):
+ _depends()
+ elif word.startswith("conflicts_with"):
+ _conflicts()
+ elif word.startswith("def"):
+ _def()
+ else:
+ return -1
+
+
+def _parse(file):
+ """ Reads the data """
+ global line, x, _x
+ # a = 0 # debug
+ with open(file) as line:
+ line = iter(line)
+ while not result["name"]:
+ _name()
+ for x in line:
+ _x = x # Temp workaround for "#" in descriptions
+ x = _clean(x)
+ # a += 1 # debug
+ # print("{} {}".format(a, x)) # Debug
+ if keywords.match(x):
+ # print("keywords match ({}) found! {}".format(keywords.search(x).group(1), x)) # DEBUG
+ _keywords(keywords.search(x).group(1))
+
+
+def parse(file):
+ _reset()
+ _parse(file)
+ return result
diff --git a/util/.marauder/marauder/version.py b/util/.marauder/marauder/version.py
new file mode 100644
index 000000000..6ad09d9fe
--- /dev/null
+++ b/util/.marauder/marauder/version.py
@@ -0,0 +1,40 @@
+import os
+from io import BytesIO
+import re
+from urllib.request import urlopen
+import sys
+
+default = re.compile(r'(?:([0-9]+(?:[.-_][0-9]+)+))') # Version()
+
+""" Takes URL string and guesses version """
+def version(url):
+ filename = os.path.basename(url)
+ version = default.findall(filename)
+ if not version:
+ x = os.path.basename(os.path.dirname(url))
+ version = default.findall(x) # Go up one directory and try that.
+ if version:
+ return version[0]
+ version = re.findall(r'\S*?(:?[0-9-]+)', filename) # v40 or program-83 for example.
+ if version:
+ return version[0]
+ if url.endswith(".git"):
+ return None # FIXME: this is too slow.
+ # print(f"Finding version for {url}", file=sys.stderr)
+ latest_release = os.popen(
+ f"git ls-remote --tags {url} 2>/dev/null | sort -Vk2 | awk 'END{{ print $2 }}' | grep -oE '[0-9]+(\\.[0-9]+)+'"
+ ).read().strip()
+ latest_commit = os.popen(
+ f"git ls-remote --tags {url} 2>/dev/null | sort -Vk2 | awk 'END{{ print $1 }}'"
+ ).read().strip()
+ if latest_release and latest_commit:
+ version = f"{latest_release}-git-{latest_commit[:8]}"
+ elif latest_commit:
+ version = f"1.0-git-{latest_commit[:8]}"
+ else:
+ return None
+ return version
+
+ if not version: # If still no result.
+ return None
+ return version[0]
diff --git a/util/brew-convert.py b/util/brew-convert.py
new file mode 100755
index 000000000..f027ad54b
--- /dev/null
+++ b/util/brew-convert.py
@@ -0,0 +1,225 @@
+#!/usr/bin/env python3
+import sys
+import os
+import json
+import glob
+import zipfile
+import tempfile
+import argparse
+from urllib.request import urlopen
+from io import BytesIO
+
+sys.path.append(os.path.split(os.path.abspath(__file__))[0] + '/.marauder')
+import marauder as m
+
+# Global variables:
+# Swapping for linux-brew might work.
+url = 'https://github.com/Homebrew/homebrew-core/archive/master.zip'
+# Use this string as the default version
+_version = "1.0-unknown"
+# Pick the hidden dot-file that holds json information.
+_dotfile = "beer"
+
+
+def download():
+ """ Downloads and extracts the url """
+ print(f"Downloading {url}", file=sys.stderr)
+
+ global extract
+ dl = urlopen(url)
+ z = zipfile.ZipFile(BytesIO(dl.read()))
+ extract = os.path.commonprefix(z.namelist()) + "Formula"
+
+ for file in z.namelist():
+ if file.startswith(extract):
+ z.extract(file)
+
+
+def convert(create=False):
+ """ General purpose, converts the formulas """
+ global extract
+
+ def creator(data):
+ """ Creates/write out the formula to the build-system """
+ nonlocal file
+
+ def skip():
+ """ Returns true if formula has any unwanted dependencies """
+ nonlocal data
+ s = ['python@2',
+ 'python',
+ 'go',
+ 'mono',
+ 'ruby',
+ 'x11',
+ 'xcode',
+ 'java',
+ 'rust',
+ 'osxfuse',
+ 'gtk+3',
+ 'zsh',
+ 'perl',
+ 'node',
+ 'cabal-install',
+ 'ghc',
+ 'ant']
+
+ for d in data['depends']:
+ if any(d['depend'] in depend for depend in s):
+ return True
+ return False
+
+ if skip():
+ return None
+ elif data['url'].endswith('.git') or 'svn.' in os.path.dirname(data['url']):
+ return None # example.com/foo.git; svn.example.com/trunk
+
+ data['version'] = m.version(data['url']) or _version # <--- or default
+ data['install'] = [line for line in data['install'] if line != ''] # Strips lines
+ data['file'] = os.path.basename(file) # foobar.json | The original formula
+
+ # print(f"{data['name']}\t{data['version']}\t{data['url']}", file=sys.stderr)
+
+ # Create directories
+ n = data['name'].lower() # Name
+ try:
+ os.makedirs(f"{n}/_metadata")
+ except FileExistsError as e:
+ print(f"Directory {data['name']} already exists! Skipping... ({e}) {data['file']}", file=sys.stderr)
+ return -1
+ with open(f"{n}/_metadata/name", 'w') as f_name, \
+ open(f"{n}/_metadata/description", 'w') as f_description, \
+ open(f"{n}/_metadata/version", 'w') as f_version, \
+ open(f"{n}/_metadata/homepage", 'w') as f_homepage, \
+ open(f"{n}/.{_dotfile}", 'w') as f_dotfile, \
+ open(f"{n}/.make.sh-auto", 'w') as f_make, \
+ os.fdopen(os.open(f"{n}/download.sh", os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o755), 'w') as f_download:
+ # _metadata
+ print(f"{data['name']}", file=f_name) # Use print because it appends a newline.
+ print(f"{data['description']}", file=f_description)
+ print(f"{data['version']}", file=f_version)
+ print(f"{data['homepage']}", file=f_homepage)
+ # JSON information file for whatever
+ json.dump(data, f_dotfile, indent=4)
+ # Create a script to download the file.
+ f_download.write(f"wget {data['url']}")
+ if data['mirror']:
+ f_download.write(f" || wget {data['mirror']}")
+ if data['patches']:
+ with os.fdopen(os.open(f"{n}/patches.sh", os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o755), 'w') as f_patches:
+ for p in data['patches']:
+ if not p['url']: # There's data in this entry.
+ try:
+ assert p['data'] # But it might be a parsing error.
+
+ print("echo 'Creating brew-patch.diff'", file=f_patches)
+ print("cat << EOF >> brew-patch.diff", file=f_patches)
+ for l in p['data']:
+ f_patches.write(l)
+ print("EOF", file=f_patches)
+ except KeyError:
+ pass
+ else: # not p['url']
+ print(f"wget {p['url']}", file=f_patches)
+ # // if data['patches']
+ if data['install']:
+ try:
+ print("pkg:setup", file=f_make)
+ for l in m.makesh(data['install']):
+ print(f"{l}", file=f_make)
+ except TypeError: # FIXME
+ os.remove(f"{n}/.make.sh-auto")
+ pass
+
+ # TODO:
+ # -> Parse `def install`
+
+ def updater(dotfile, data, file):
+ """ Updates a formula """
+ if dotfile['url'] == data['url']:
+ # print(f"{dotfile['name']} is up to date.", file=sys.stderr)
+ return 0
+ # Version comes from the urls, so check the urls instead.
+ else:
+ print(f"{dotfile['name']} has to be updated.", file=sys.stdout)
+
+ data['version'] = m.version(data['url']) or _version
+ data['install'] = [line for line in data['install'] if line != '']
+ data['file'] = dotfile['file']
+
+ n = os.path.dirname(file)
+ with open(f"{n}/_metadata/version", 'w') as f_version, \
+ open(f"{n}/.{_dotfile}", 'w') as f_dotfile, \
+ os.fdopen(os.open(f"{n}/download.sh", os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o755), 'w') as f_download:
+
+ print(f"{data['version']}", file=f_version)
+ json.dump(data, f_dotfile, indent=4)
+ f_download.write(f"wget {data['url']}")
+ if data['mirror']:
+ f_download.write(f" || wget {data['mirror']}")
+ try: # Just try to remove the old tarball.
+ os.remove(f"{n}/" + os.path.basename(dotfile['url']))
+ except OSError:
+ pass
+ if data['patches']:
+ with os.fdopen(os.open(f"{n}/patches.sh", os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o755), 'w') as f_patches:
+ for p in data['patches']:
+ if not p['url']:
+ try:
+ assert p['data']
+ print("echo 'Creating brew-patch.diff'", file=f_patches)
+ print("cat << EOF >> brew-patch.diff", file=f_patches)
+ for l in p['data']:
+ f_patches.write(l)
+ print("EOF", file=f_patches)
+ except KeyError:
+ pass
+ else:
+ print(f"wget {p['url']}", file=f_patches)
+
+
+ with tempfile.TemporaryDirectory() as t:
+ # `pushd` tmpdir
+ cwd = os.getcwd()
+ os.chdir(t)
+ # Grab master and extract
+ download()
+ assert extract
+ extract = os.path.realpath(extract)
+ # `popd`
+ os.chdir(cwd)
+
+ if create:
+ for file in sorted(glob.glob(extract + '/*')):
+ creator(m.parse(file))
+ else:
+ for file in sorted(glob.glob(f"*/.{_dotfile}")):
+ f = json.load(open(file))
+ rb = f"{extract}/" + f['file']
+ updater(f, m.parse(rb), file)
+
+
+def parse_arg(argv):
+ parser = argparse.ArgumentParser(prog='Brew-Converter', description='Converts homebrew formulas to `pwd`. Be sure to cd before using this tool.')
+ group = parser.add_mutually_exclusive_group()
+ # Args
+ group.add_argument('--all', help='Create and parse all brew formulas',
+ action='store_true', default=False, dest='all')
+ group.add_argument('--upgrade', help='Upgrade all formulas present',
+ action='store_true', default=False, dest='upgrade')
+ results = parser.parse_args()
+ # Checking
+ if len(argv) <= 0:
+ parser.print_help()
+ sys.exit(1)
+ elif results.all:
+ convert(create=True)
+ elif results.upgrade:
+ convert()
+ else:
+ print(f"Something went wrong: {results}")
+ sys.exit(3)
+
+
+if __name__ == '__main__':
+ parse_arg(sys.argv[1:])