feat: proper ruby parser for dependencies
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/git-clone/
|
||||
13
Makefile
Normal file
13
Makefile
Normal file
@@ -0,0 +1,13 @@
|
||||
help:
|
||||
@echo 'available commands: test, test-git, test-parser'
|
||||
|
||||
git-clone:
|
||||
git clone --depth 1 https://github.com/Homebrew/homebrew-core/ git-clone
|
||||
|
||||
test-git: git-clone
|
||||
python3 -c 'import test; test.testCoreFormulae()'
|
||||
|
||||
test-parser:
|
||||
python3 -c 'import test; test.testConfigVariations()'
|
||||
|
||||
test: test-git test-parser
|
||||
384
brew.py
384
brew.py
@@ -29,7 +29,7 @@ from argparse import (
|
||||
_MutuallyExclusiveGroup as ArgsXorGroup,
|
||||
)
|
||||
from typing import (
|
||||
Any, Callable, Iterable, Iterator, NamedTuple, Optional, Pattern, TypedDict, TypeVar
|
||||
Any, Callable, Iterable, Iterator, NamedTuple, Optional, TypedDict, TypeVar
|
||||
)
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ def cli_info(args: ArgParams) -> None:
|
||||
Log.info(' <not installed>')
|
||||
else:
|
||||
localDeps = Cellar.getDependencies(args.package, ver)
|
||||
Log.info(' ', ', '.join(localDeps) if localDeps else '<none>')
|
||||
Log.info(' ', ', '.join(sorted(localDeps)) or '<none>')
|
||||
|
||||
Log.info()
|
||||
Utils.ask('search online?') or exit(0)
|
||||
@@ -1324,21 +1324,10 @@ class Cellar:
|
||||
# Ruby file processing
|
||||
|
||||
@staticmethod
|
||||
def grepFormula(pkg: str, version: str, pattern: Pattern) \
|
||||
-> 'list[str]|None':
|
||||
''' Parse ruby file to extract information '''
|
||||
path = Cellar.rubyPath(pkg, version)
|
||||
if os.path.isfile(path):
|
||||
with open(path, 'r') as fp:
|
||||
return pattern.findall(fp.read())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def getDependencies(pkg: str, version: str) -> 'list[str]|None':
|
||||
def getDependencies(pkg: str, version: str) -> set[str]:
|
||||
''' Extract dependencies from ruby file '''
|
||||
assert version, 'version is required'
|
||||
rx = re.compile(r'depends_on\s*"([^"]*)"(?!\s*=>)') # w/o build deps
|
||||
return Cellar.grepFormula(pkg, version, rx)
|
||||
return RubyParser(Cellar.rubyPath(pkg, version)).parse().dependencies
|
||||
|
||||
@staticmethod
|
||||
def getHomepageUrl(pkg: str) -> 'str|None':
|
||||
@@ -1346,17 +1335,13 @@ class Cellar:
|
||||
info = Cellar.info(pkg)
|
||||
ver = info.verActive or ([None] + info.verAll)[-1]
|
||||
if ver:
|
||||
rx = re.compile(r'homepage\s*"([^"]*)"')
|
||||
url = Cellar.grepFormula(pkg, ver, rx)
|
||||
if url:
|
||||
return url[0]
|
||||
return RubyParser(Cellar.rubyPath(pkg, ver)).parseHomepageUrl()
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def isKegOnly(pkg: str, version: str) -> bool:
|
||||
''' Check if package is keg-only '''
|
||||
rx = re.compile(r'[\^\n]\s*keg_only\s*')
|
||||
return len(Cellar.grepFormula(pkg, version, rx) or []) > 0
|
||||
return RubyParser(Cellar.rubyPath(pkg, version)).parseKegOnly()
|
||||
|
||||
|
||||
# -----------------------------------
|
||||
@@ -1434,6 +1419,363 @@ class Fixer:
|
||||
os.utime(fname, (atime, mtime))
|
||||
|
||||
|
||||
# -----------------------------------
|
||||
# RubyParser
|
||||
# -----------------------------------
|
||||
|
||||
class RubyParser:
|
||||
PRINT_PARSE_ERRORS = True
|
||||
ASSERT_KNOWN_SYMBOLS = False
|
||||
IGNORE_RULES = False
|
||||
FAKE_INSTALLED = set() # type: set[str] # simulate Cellar.info().installed
|
||||
|
||||
IGNORED_TARGETS = set([':optional', ':build', ':test'])
|
||||
TARGET_SYMBOLS = IGNORED_TARGETS.union([':recommended'])
|
||||
# https://rubydoc.brew.sh/MacOSVersion.html#SYMBOLS-constant
|
||||
# MACOS_SYMBOLS = set([':' + x for x in Arch.ALL_OS])
|
||||
# https://rubydoc.brew.sh/RuboCop/Cask/Constants#ON_SYSTEM_METHODS-constant
|
||||
# https://rubydoc.brew.sh/Homebrew/SimulateSystem.html#arch_symbols-class_method
|
||||
# SYSTEM_SYMBOLS = set([':arm,', ':intel', ':arm64', ':x86_64'])
|
||||
# KNOWN_SYMBOLS = MACOS_SYMBOLS | SYSTEM_SYMBOLS | TARGET_SYMBOLS
|
||||
|
||||
def __init__(self, path: str) -> None:
|
||||
self.invalidArch = [] # type: list[str] # reasons why not supported
|
||||
self.path = path
|
||||
if not os.path.isfile(self.path):
|
||||
raise FileNotFoundError(path)
|
||||
|
||||
def readlines(self) -> Iterator[str]:
|
||||
with open(self.path, 'r') as fp:
|
||||
for line in fp.readlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
yield line
|
||||
|
||||
def parseHomepageUrl(self) -> 'str|None':
|
||||
''' Extract homepage url '''
|
||||
for line in self.readlines():
|
||||
if line.startswith('homepage '):
|
||||
return line.split('"')[1]
|
||||
return None
|
||||
|
||||
def parseKegOnly(self) -> bool:
|
||||
''' Check if package is keg-only '''
|
||||
for line in self.readlines():
|
||||
if line == 'keg_only' or line.startswith('keg_only '):
|
||||
return True
|
||||
return False
|
||||
|
||||
def parse(self) -> 'RubyParser':
|
||||
''' Extract depends_on rules (updates `.invalidArch`) '''
|
||||
END = r'\s*(?:#|$)' # \n or comment
|
||||
STR = r'"([^"]*)"' # "foo"
|
||||
ACT = r'([^\s:]+:)' # foo:
|
||||
SYM = r'(:[^\s:]+)' # :foo
|
||||
ARR = r'\[([^\]]*)\]' # [foo]
|
||||
TOK = fr'(?:{STR}|{SYM}|{ARR})' # "str" | :sym | [arr]
|
||||
TGT = fr'(?:\s*=>\s*{TOK})?' # OPTIONAL: => {TOK}
|
||||
# depends_on
|
||||
DEP = fr'(?:{STR}|{SYM}|{ACT}\s+{TOK})' # "str" | :sym | act: {TOK}
|
||||
IF = r'(?:\s+if\s+(.*))?' # OPTIONAL: if MacOS.version >= :catalina
|
||||
# uses_from_macos
|
||||
REQ = fr'(?:,\s+{ACT}\s+{SYM})?' # OPTIONAL: , act: :sym (with comma)
|
||||
|
||||
rx_grp = re.compile(fr'^on_([^\s]*)\s*(.*)\s+do{END}')
|
||||
rx_dep = re.compile(fr'^depends_on\s+{DEP}{TGT}{IF}{END}')
|
||||
rx_use = re.compile(fr'^uses_from_macos\s+{STR}{TGT}{REQ}{END}')
|
||||
|
||||
self.dependencies = set() # type: set[str]
|
||||
context = [True]
|
||||
prev_classes = set() # type: set[str]
|
||||
for line in self.readlines():
|
||||
if line.startswith('class '):
|
||||
prev_classes.add(line.split()[1])
|
||||
|
||||
if line.startswith('on_'):
|
||||
if match := rx_grp.match(line):
|
||||
flag = self._parse_block(*match.groups())
|
||||
context.append(flag)
|
||||
else:
|
||||
# ignore single outlier cvs.rb
|
||||
if not line.startswith('on_macos { patches'):
|
||||
self._err(line)
|
||||
|
||||
elif line == 'end' or line.startswith('end '):
|
||||
if len(context) > 1:
|
||||
context.pop()
|
||||
|
||||
elif not self.IGNORE_RULES and not all(context):
|
||||
continue
|
||||
|
||||
elif line.startswith('depends_on '):
|
||||
if match := rx_dep.match(line):
|
||||
if self._parse_depends(*match.groups()):
|
||||
self.dependencies.add(match.group(1))
|
||||
else:
|
||||
# glibc seems to be the only formula with weird defs
|
||||
# https://github.com/Homebrew/homebrew-core/blob/main/Formula/g/glibc%402.17.rb
|
||||
if line.split()[1] not in prev_classes:
|
||||
self._err(line)
|
||||
|
||||
elif line.startswith('uses_from_macos '):
|
||||
if match := rx_use.match(line):
|
||||
if not self._parse_uses(*match.groups()):
|
||||
self.dependencies.add(match.group(1))
|
||||
else:
|
||||
self._err(line)
|
||||
|
||||
return self
|
||||
|
||||
##################################################
|
||||
# Helper methods
|
||||
##################################################
|
||||
|
||||
def _err(self, *msg: Any) -> None:
|
||||
if self.PRINT_PARSE_ERRORS:
|
||||
Log.warn('ruby parse err //', *msg, '--', self.path)
|
||||
|
||||
def _unify_tok(self, string: str, sym: str, arr: str) -> list[str]:
|
||||
if string:
|
||||
return [string]
|
||||
if sym:
|
||||
return [sym]
|
||||
if arr:
|
||||
return [x.strip().strip('"') for x in arr.split(',')]
|
||||
return []
|
||||
|
||||
def _is_ignored_target(self, args: list[str]) -> bool:
|
||||
''' Returns `True` if target is :build or :test (unless debugging) '''
|
||||
if self.ASSERT_KNOWN_SYMBOLS:
|
||||
if unkown := set(args) - RubyParser.TARGET_SYMBOLS:
|
||||
self._err('unkown symbol', unkown)
|
||||
if self.IGNORE_RULES:
|
||||
return False
|
||||
for value in args:
|
||||
if value in self.IGNORED_TARGETS:
|
||||
return True
|
||||
return False # fallback to required
|
||||
|
||||
##################################################
|
||||
# on_xxx block
|
||||
##################################################
|
||||
|
||||
def _parse_block(self, block: str, param: str) -> bool:
|
||||
''' Returns `True` if on_BLOCK matches requirements '''
|
||||
# https://github.com/Homebrew/brew/blob/main/Library/Homebrew/ast_constants.rb#L32
|
||||
# on_macos, on_system, on_linux, on_arm, on_intel, "on_#{os_name}"
|
||||
if block == 'macos':
|
||||
if not param:
|
||||
return Arch.IS_MAC
|
||||
elif block == 'linux':
|
||||
if not param:
|
||||
return not Arch.IS_MAC
|
||||
elif block == 'arm':
|
||||
if not param:
|
||||
return Arch.IS_ARM
|
||||
elif block == 'intel':
|
||||
if not param:
|
||||
return not Arch.IS_ARM
|
||||
elif block == 'arch':
|
||||
return self._eval_on_arch(param)
|
||||
elif block == 'system':
|
||||
if param:
|
||||
return any(self._eval_on_system(x) for x in param.split(','))
|
||||
elif block in Arch.ALL_OS:
|
||||
if not Arch.IS_MAC:
|
||||
return False
|
||||
return self._eval_on_mac_version(block, param)
|
||||
self._err(f'unknown on_{block} with param "{param}"')
|
||||
return True # fallback to is-a-matching-block
|
||||
|
||||
def _eval_on_arch(self, param: str) -> bool:
|
||||
if param == ':arm':
|
||||
return Arch.IS_ARM
|
||||
if param in ':intel':
|
||||
return not Arch.IS_ARM
|
||||
self._err(f'unknown on_arch param "{param}"')
|
||||
return True # fallback to is-matching
|
||||
|
||||
def _eval_on_system(self, param: str) -> bool:
|
||||
''' Returns `True` if current machine matches requirements '''
|
||||
param = param.strip()
|
||||
if param == ':linux':
|
||||
return not Arch.IS_MAC
|
||||
|
||||
if param == ':macos':
|
||||
return Arch.IS_MAC
|
||||
|
||||
if param.startswith('macos: :'):
|
||||
if not Arch.IS_MAC:
|
||||
return False
|
||||
os_name = param.removeprefix('macos: :')
|
||||
if os_name.endswith('_or_older'):
|
||||
if ver := Arch.ALL_OS.get(os_name.removesuffix('_or_older')):
|
||||
return Arch.OS_VER <= ver
|
||||
elif os_name.endswith('_or_newer'):
|
||||
if ver := Arch.ALL_OS.get(os_name.removesuffix('_or_newer')):
|
||||
return Arch.OS_VER >= ver
|
||||
elif ver := Arch.ALL_OS.get(os_name):
|
||||
return Arch.OS_VER == ver
|
||||
|
||||
self._err(f'unknown on_system param "{param}"')
|
||||
return True # fallback to is-matching
|
||||
|
||||
def _eval_on_mac_version(self, macver: str, param: str) -> bool:
|
||||
''' Returns `True` if current machine matches requirements '''
|
||||
if not param:
|
||||
return Arch.OS_VER == Arch.ALL_OS[macver]
|
||||
if param == ':or_older':
|
||||
return Arch.OS_VER <= Arch.ALL_OS[macver]
|
||||
if param == ':or_newer':
|
||||
return Arch.OS_VER >= Arch.ALL_OS[macver]
|
||||
self._err(f'unknown on_{macver} param "{param}"')
|
||||
return True # fallback to is-matching
|
||||
|
||||
##################################################
|
||||
# uses_from_macos
|
||||
##################################################
|
||||
|
||||
def _parse_uses(
|
||||
self, dep: str, uStr: str, uSym: str, uArr: str, rAct: str, rSym: str,
|
||||
) -> bool:
|
||||
''' Returns `True` if requirement is fulfilled. '''
|
||||
# dep [=> :uSym|uArr]? [, rAct: :rSym]?
|
||||
if self._is_ignored_target(self._unify_tok(uStr, uSym, uArr)):
|
||||
return True # only a :build target
|
||||
|
||||
if not Arch.IS_MAC:
|
||||
return False # on linux, install
|
||||
|
||||
if not rAct:
|
||||
assert not rSym
|
||||
return True # no need to install, because it is a Mac
|
||||
|
||||
assert rSym
|
||||
if rAct == 'since:':
|
||||
if os_ver := Arch.ALL_OS.get(rSym.lstrip(':')):
|
||||
return Arch.OS_VER >= os_ver
|
||||
self._err('unknown uses_from_macos', rAct, rSym)
|
||||
return True # dont install, assuming it should be fine on any Mac
|
||||
|
||||
##################################################
|
||||
# depends_on
|
||||
##################################################
|
||||
|
||||
def _parse_depends(
|
||||
self, dep: str, sym: str, act: str,
|
||||
dStr: str, dSym: str, dArr: str,
|
||||
tStr: str, tSym: str, tArr: str, tIf: str,
|
||||
) -> bool:
|
||||
''' Returns `True` if dependency is required (needs install). '''
|
||||
# (dep|:sym|act: (dStr|:dSym|dArr))! [=> (tStr|:tSym|tArr)]? [if tIf]?
|
||||
if sym:
|
||||
self._validity_symbol(sym)
|
||||
return False # no dependency, only a system requirement
|
||||
|
||||
if act:
|
||||
dTok = self._unify_tok(dStr, dSym, dArr)
|
||||
param = dTok.pop(0)
|
||||
if not self._is_ignored_target(dTok):
|
||||
self._validity_action(act, param, dTok)
|
||||
return False # no dependency, only a system requirement
|
||||
|
||||
if self._is_ignored_target(self._unify_tok(tStr, tSym, tArr)):
|
||||
return False # only a :build target
|
||||
|
||||
if tIf and not self._eval_depends_if(tIf):
|
||||
return False # if-clause says "no need to install"
|
||||
return True # needs install
|
||||
|
||||
def _eval_depends_if(self, clause: str) -> bool:
|
||||
''' Returns `True` if if-clause evaluates to True '''
|
||||
if clause.startswith('MacOS.version '):
|
||||
if not Arch.IS_MAC:
|
||||
return False
|
||||
what, op, os_name = clause.split()
|
||||
if os_ver := Arch.ALL_OS.get(os_name.lstrip(':')):
|
||||
return Utils.cmpVersion(Arch.OS_VER, op, os_ver)
|
||||
|
||||
elif clause.startswith('Formula["') and \
|
||||
clause.endswith('"].any_version_installed?'):
|
||||
pkg = clause.split('"')[1]
|
||||
return Cellar.info(pkg).installed or pkg in self.FAKE_INSTALLED
|
||||
|
||||
elif clause.startswith('build.with? "'):
|
||||
pkg = clause.split('"')[1]
|
||||
# technically not correct, dependency could appear after this rule
|
||||
return pkg in self.dependencies
|
||||
|
||||
elif clause.startswith('build.without? "'):
|
||||
pkg = clause.split('"')[1]
|
||||
# technically not correct, dependency could appear after this rule
|
||||
return pkg not in self.dependencies
|
||||
|
||||
elif match := re.match(r'^(.+)\s+([<=>]+)\s+([0-9.]+)$', clause):
|
||||
what, op, ver = match.groups()
|
||||
ver = [int(x) for x in ver.split('.')]
|
||||
if what == 'DevelopmentTools.clang_build_version':
|
||||
return Utils.cmpVersion(Arch.getClangBuildVersion(), op, ver)
|
||||
if what.startswith('DevelopmentTools.gcc_version'):
|
||||
return Utils.cmpVersion(Arch.getGccVersion(), op, ver)
|
||||
|
||||
self._err('unhandled depends_on if-clause', clause)
|
||||
return True # in case of doubt, install
|
||||
|
||||
##################################################
|
||||
# Check system architecture
|
||||
##################################################
|
||||
|
||||
def _validArch(self, check: bool, desc: str) -> None:
|
||||
if not check:
|
||||
self.invalidArch.append(desc)
|
||||
|
||||
def _validity_symbol(self, sym: str) -> None:
|
||||
''' Check if symbol corresponds to current system architecture '''
|
||||
if sym == ':linux':
|
||||
self._validArch(not Arch.IS_MAC, 'Linux only')
|
||||
elif sym == ':macos':
|
||||
self._validArch(Arch.IS_MAC, 'MacOS only')
|
||||
elif sym == ':xcode':
|
||||
self._validArch(Arch.hasXcodeVer('1'), 'needs Xcode')
|
||||
else:
|
||||
self._err('unknown depends_on symbol', sym)
|
||||
|
||||
def _validity_action(self, act: str, param: str, flags: list[str]) -> None:
|
||||
''' Check if action is valid on current system architecture '''
|
||||
# https://github.com/Homebrew/brew/blob/main/Library/Homebrew/dependency_collector.rb#L161
|
||||
# arch:, macos:, maximum_macos:, xcode:
|
||||
# not supported (yet): linux:, codesign:
|
||||
if act == 'arch:':
|
||||
assert not flags
|
||||
if param == ':x86_64':
|
||||
self._validArch(not Arch.IS_ARM, 'no ARM support')
|
||||
elif param == ':arm64':
|
||||
self._validArch(Arch.IS_ARM, 'ARM only')
|
||||
else:
|
||||
self._err('unknown depends_on arch:', param)
|
||||
|
||||
elif act in ['macos:', 'maximum_macos:']:
|
||||
if os_ver := Arch.ALL_OS.get(param.lstrip(':')):
|
||||
op = '<=' if act == 'maximum_macos:' else '>='
|
||||
if Arch.IS_MAC:
|
||||
self._validArch(Utils.cmpVersion(Arch.OS_VER, op, os_ver),
|
||||
f'needs macOS {op} {os_ver}')
|
||||
else:
|
||||
self._validArch(False, f'needs macOS {op} {os_ver}')
|
||||
else:
|
||||
self._err('unknown depends_on', act, param)
|
||||
|
||||
elif act == 'xcode:':
|
||||
ver = param
|
||||
if ver.startswith(':'): # probably some ":build"
|
||||
self._validArch(Arch.hasXcodeVer('1'), 'needs Xcode')
|
||||
else:
|
||||
self._validArch(Arch.hasXcodeVer(ver), f'needs Xcode >= {ver}')
|
||||
|
||||
else:
|
||||
self._err('unknown depends_on action', act, param, flags)
|
||||
|
||||
|
||||
# -----------------------------------
|
||||
# Utils
|
||||
# -----------------------------------
|
||||
|
||||
245
test-formula.rb
Normal file
245
test-formula.rb
Normal file
@@ -0,0 +1,245 @@
|
||||
class Test < Formula
|
||||
homepage "https://example.org"
|
||||
keg_only :macos
|
||||
|
||||
depends_on xcode: "8.3"
|
||||
|
||||
depends_on :macos
|
||||
depends_on :linux
|
||||
|
||||
depends_on arch: :x86_64
|
||||
depends_on arch: :arm64
|
||||
|
||||
# test build target
|
||||
depends_on "__:build__:test__" => [:build, :test]
|
||||
depends_on "__:build__" => :build
|
||||
depends_on "__:test__" => :test
|
||||
depends_on "__:recommended__" => :recommended
|
||||
depends_on "__:optional__" => :optional
|
||||
|
||||
# test uses_from_macos
|
||||
uses_from_macos "__uses_from_macos__"
|
||||
uses_from_macos "__uses_from_macos__:build__" => :build
|
||||
uses_from_macos "__uses_from_macos__:build__since__" => :build, since: :catalina
|
||||
uses_from_macos "__uses_from_macos__since_catalina__", since: :catalina
|
||||
uses_from_macos "__uses_from_macos__since_sierra__", since: :sierra
|
||||
|
||||
# test if-clause
|
||||
depends_on "__if_clang_<=_1400__" if DevelopmentTools.clang_build_version <= 1400
|
||||
depends_on "__if_gcc_<_9__" if DevelopmentTools.gcc_version("/usr/bin/gcc") < 9
|
||||
depends_on "__if_macos_>=_catalina__" if MacOS.version >= :catalina
|
||||
depends_on "__if_any_zlib_installed__" if Formula["zlib"].any_version_installed?
|
||||
depends_on "__if_build.with_catalina__" if build.with? "__if_macos_>=_catalina__"
|
||||
depends_on "__if_build.without_catalina__" if build.without? "__if_macos_>=_catalina__"
|
||||
|
||||
|
||||
on_macos do
|
||||
depends_on "__on_macos__"
|
||||
end
|
||||
|
||||
on_linux do
|
||||
depends_on "__on_linux__"
|
||||
end
|
||||
|
||||
on_macos do
|
||||
on_linux do
|
||||
depends_on "__nested__on_macos__on_linux__"
|
||||
end
|
||||
end
|
||||
|
||||
on_linux do
|
||||
on_macos do
|
||||
depends_on "__nested__on_linux__on_macos__"
|
||||
end
|
||||
end
|
||||
|
||||
# https://rubydoc.brew.sh/OnSystem/MacOSAndLinux.html
|
||||
# https://rubydoc.brew.sh/Formula.html
|
||||
# https://rubydoc.brew.sh/Formula#uses_from_macos-class_method
|
||||
# https://rubydoc.brew.sh/Cask/DSL/DependsOn.html
|
||||
|
||||
#############################################################################
|
||||
# from https://rubydoc.brew.sh/Formula.html#on_system_blocks_exist%3F-class_method
|
||||
#
|
||||
on_monterey :or_older do
|
||||
depends_on "__on_monterey :or_older__"
|
||||
end
|
||||
on_system :linux, macos: :big_sur_or_newer do
|
||||
depends_on "__on_system :linux, macos: :big_sur_or_newer__"
|
||||
end
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
|
||||
#############################################################################
|
||||
# from https://rubydoc.brew.sh/OnSystem.html#ALL_OS_OPTIONS-constant
|
||||
#
|
||||
on_arch :arm do # comment
|
||||
depends_on "__on_arch :arm__"
|
||||
end
|
||||
on_arch :intel do
|
||||
depends_on "__on_arch :intel__"
|
||||
end
|
||||
on_system macos: :sierra_or_older do
|
||||
depends_on "__on_system macos: :sierra_or_older__"
|
||||
end
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
|
||||
#############################################################################
|
||||
# https://rubydoc.brew.sh/RuboCop/Cask/AST/Stanza.html#on_arch_conditional%3F-instance_method
|
||||
#
|
||||
on_arm do
|
||||
depends_on "__on_arm__"
|
||||
end
|
||||
on_intel do
|
||||
depends_on "__on_intel__"
|
||||
end
|
||||
#
|
||||
#
|
||||
on_yosemite do
|
||||
depends_on "__on_yosemite__"
|
||||
end
|
||||
on_el_capitan do
|
||||
depends_on "__on_el_capitan__"
|
||||
end
|
||||
on_sierra do
|
||||
depends_on "__on_sierra__"
|
||||
end
|
||||
on_high_sierra do
|
||||
depends_on "__on_high_sierra__"
|
||||
end
|
||||
on_mojave do
|
||||
depends_on "__on_mojave__"
|
||||
end
|
||||
on_catalina do
|
||||
depends_on "__on_catalina__"
|
||||
end
|
||||
on_big_sur do
|
||||
depends_on "__on_big_sur__"
|
||||
end
|
||||
on_monterey do
|
||||
depends_on "__on_monterey__"
|
||||
end
|
||||
on_ventura do
|
||||
depends_on "__on_ventura__"
|
||||
end
|
||||
on_sonoma do
|
||||
depends_on "__on_sonoma__"
|
||||
end
|
||||
on_sequoia do
|
||||
depends_on "__on_sequoia__"
|
||||
end
|
||||
on_tahoe do
|
||||
depends_on "__on_tahoe__"
|
||||
end
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
|
||||
#############################################################################
|
||||
# from glib formula
|
||||
#
|
||||
depends_on "python-setuptools" => :build # for gobject-introspection
|
||||
depends_on "python@3.13" => :build
|
||||
depends_on "pcre2"
|
||||
#
|
||||
uses_from_macos "flex" => :build # for gobject-introspection
|
||||
uses_from_macos "libffi", since: :catalina
|
||||
uses_from_macos "python"
|
||||
uses_from_macos "zlib"
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
|
||||
#############################################################################
|
||||
# from https://docs.brew.sh/Formula-Cookbook#specifying-macos-components-as-dependencies
|
||||
#
|
||||
# For example, to require the bzip2 formula on Linux while relying on built-in bzip2 on macOS:
|
||||
uses_from_macos "bzip2"
|
||||
# To require the perl formula only when building or testing on Linux:
|
||||
uses_from_macos "perl" => [:build, :test]
|
||||
# To require the curl formula on Linux and pre-macOS 12:
|
||||
uses_from_macos "curl", since: :monterey
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
|
||||
#############################################################################
|
||||
# from https://github.com/Homebrew/homebrew-core/blob/main/Formula/c/c-blosc2.rb
|
||||
#
|
||||
on_macos do
|
||||
depends_on "llvm" => :build if DevelopmentTools.clang_build_version <= 1400
|
||||
end
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
|
||||
#############################################################################
|
||||
# from https://docs.brew.sh/Formula-Cookbook#specifying-other-formulae-as-dependencies
|
||||
#
|
||||
depends_on "httpd" => [:build, :test]
|
||||
depends_on xcode: ["9.3", :build]
|
||||
depends_on arch: :x86_64
|
||||
depends_on "jpeg"
|
||||
depends_on macos: :high_sierra
|
||||
depends_on "pkg-config"
|
||||
depends_on "readline" => :recommended
|
||||
depends_on "gtk+" => :optional
|
||||
#
|
||||
option "with-foo", "Compile with foo bindings" # This overrides the generated description if you want to
|
||||
depends_on "foo" => :optional # Generated description would otherwise be "Build with foo support"
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
|
||||
#############################################################################
|
||||
# from https://docs.brew.sh/Formula-Cookbook#handling-different-system-configurations
|
||||
#
|
||||
on_linux do
|
||||
depends_on "gcc"
|
||||
end
|
||||
#
|
||||
on_mojave :or_newer do # comment
|
||||
depends_on "gettext" => :build
|
||||
end
|
||||
#
|
||||
on_system :linux, macos: :sierra_or_older do # comment
|
||||
depends_on "gettext" => :build # comment
|
||||
end
|
||||
#
|
||||
on_macos do # comment
|
||||
on_arm do
|
||||
depends_on "gettext" => :build
|
||||
end
|
||||
end
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
|
||||
#############################################################################
|
||||
# from https://rubydoc.brew.sh/Formula#depends_on-class_method
|
||||
#
|
||||
# :build means this dependency is only needed during build.
|
||||
depends_on "cmake" => :build
|
||||
# :test means this dependency is only needed during testing.
|
||||
depends_on "node" => :test
|
||||
# :recommended dependencies are built by default. But a --without-... option is generated to opt-out.
|
||||
depends_on "readline" => :recommended
|
||||
# :optional dependencies are NOT built by default unless the auto-generated --with-... option is passed.
|
||||
depends_on "glib" => :optional
|
||||
# If you need to specify that another formula has to be built with/out certain options (note, no -- needed before the option):
|
||||
depends_on "zeromq" => "with-pgm"
|
||||
depends_on "qt" => ["with-qtdbus", "developer"] # Multiple options.
|
||||
# Optional and enforce that "boost" is built using --with-c++11.
|
||||
depends_on "boost" => [:optional, "with-c++11"]
|
||||
# If a dependency is only needed in certain cases:
|
||||
depends_on "sqlite" if MacOS.version >= :catalina
|
||||
depends_on xcode: :build # If the formula really needs full Xcode to compile.
|
||||
depends_on macos: :mojave # Needs at least macOS Mojave (10.14) to run.
|
||||
# It is possible to only depend on something if build.with? or build.without? "another_formula":
|
||||
depends_on "postgresql" if build.without? "sqlite"
|
||||
#
|
||||
#############################################################################
|
||||
end
|
||||
219
test.py
Normal file
219
test.py
Normal file
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
from brew import Arch, RubyParser
|
||||
|
||||
RubyParser.PRINT_PARSE_ERRORS = True
|
||||
RubyParser.ASSERT_KNOWN_SYMBOLS = True
|
||||
RubyParser.IGNORE_RULES = True
|
||||
|
||||
Arch.OS_VER = '0'
|
||||
Arch.IS_MAC = True
|
||||
Arch.IS_ARM = True
|
||||
Arch._SOFTWARE_VERSIONS = {
|
||||
'xcode': [0],
|
||||
'gcc': [0],
|
||||
'clang': [0],
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
# testCoreFormulae()
|
||||
testRubyTestFile()
|
||||
# testConfigVariations()
|
||||
|
||||
|
||||
def testRubyTestFile() -> None:
|
||||
ruby = RubyParser('test-formula.rb').parse()
|
||||
print()
|
||||
print('deps:')
|
||||
for dep in sorted(ruby.dependencies):
|
||||
if dep.startswith('__'):
|
||||
print(' ', dep)
|
||||
print('invalid arch:')
|
||||
print(' ', ruby.invalidArch)
|
||||
|
||||
|
||||
def testCoreFormulae() -> None:
|
||||
if not os.path.isdir('git-clone'):
|
||||
print('run `make git-clone` first')
|
||||
return
|
||||
|
||||
RubyParser.PRINT_PARSE_ERRORS = True
|
||||
RubyParser.ASSERT_KNOWN_SYMBOLS = True
|
||||
RubyParser.IGNORE_RULES = True
|
||||
|
||||
for x in os.scandir('git-clone/Formula'):
|
||||
if x.is_dir():
|
||||
for file in os.scandir(x.path):
|
||||
RubyParser(file.path).parse()
|
||||
|
||||
|
||||
def testConfigVariations() -> None:
|
||||
RubyParser.PRINT_PARSE_ERRORS = False
|
||||
RubyParser.ASSERT_KNOWN_SYMBOLS = False
|
||||
RubyParser.IGNORE_RULES = False
|
||||
|
||||
for ver in Arch.ALL_OS.values():
|
||||
Arch.OS_VER = ver
|
||||
for ismac in [True, False]:
|
||||
Arch.IS_MAC = ismac
|
||||
for isarm in [True, False]:
|
||||
Arch.IS_ARM = isarm
|
||||
for xcode in [0, 9, 15]:
|
||||
Arch._SOFTWARE_VERSIONS['xcode'] = [xcode]
|
||||
for clang in [0, 1300, 1700]:
|
||||
Arch._SOFTWARE_VERSIONS['clang'] = [clang]
|
||||
for gcc in [0, 8, 14]:
|
||||
Arch._SOFTWARE_VERSIONS['gcc'] = [gcc]
|
||||
runSingleParseTest()
|
||||
|
||||
RubyParser.FAKE_INSTALLED.add('zlib')
|
||||
runSingleParseTest()
|
||||
RubyParser.FAKE_INSTALLED.clear()
|
||||
runSingleParseTest()
|
||||
print('ok')
|
||||
|
||||
|
||||
def runSingleParseTest() -> None:
|
||||
ruby = RubyParser('test-formula.rb').parse()
|
||||
assertInvalidArch(ruby)
|
||||
assertDependencies(ruby.dependencies)
|
||||
|
||||
|
||||
def assertInvalidArch(ruby: RubyParser) -> None:
|
||||
if Arch.IS_ARM:
|
||||
assert 'no ARM support' in ruby.invalidArch
|
||||
assert 'ARM only' not in ruby.invalidArch
|
||||
else:
|
||||
assert 'no ARM support' not in ruby.invalidArch
|
||||
assert 'ARM only' in ruby.invalidArch
|
||||
|
||||
if Arch._SOFTWARE_VERSIONS['xcode'] < [1]:
|
||||
assert 'needs Xcode >= 8.3' in ruby.invalidArch
|
||||
assert 'needs Xcode' in ruby.invalidArch
|
||||
elif Arch._SOFTWARE_VERSIONS['xcode'] < [8, 3]:
|
||||
assert 'needs Xcode >= 8.3' in ruby.invalidArch
|
||||
assert 'needs Xcode' not in ruby.invalidArch
|
||||
else:
|
||||
assert 'needs Xcode >= 8.3' not in ruby.invalidArch
|
||||
assert 'needs Xcode' not in ruby.invalidArch
|
||||
|
||||
if not Arch.IS_MAC:
|
||||
assert 'Linux only' not in ruby.invalidArch
|
||||
assert 'needs macOS >= 10.13' in ruby.invalidArch
|
||||
assert 'needs macOS >= 10.14' in ruby.invalidArch
|
||||
assert 'MacOS only' in ruby.invalidArch
|
||||
elif Arch.OS_VER < '10.13':
|
||||
assert 'Linux only' in ruby.invalidArch
|
||||
assert 'needs macOS >= 10.13' in ruby.invalidArch
|
||||
assert 'needs macOS >= 10.14' in ruby.invalidArch
|
||||
elif Arch.OS_VER < '10.14':
|
||||
assert 'Linux only' in ruby.invalidArch
|
||||
assert 'needs macOS >= 10.13' not in ruby.invalidArch
|
||||
assert 'needs macOS >= 10.14' in ruby.invalidArch
|
||||
else:
|
||||
assert 'Linux only' in ruby.invalidArch
|
||||
assert 'needs macOS >= 10.13' not in ruby.invalidArch
|
||||
assert 'needs macOS >= 10.14' not in ruby.invalidArch
|
||||
|
||||
|
||||
def assertDependencies(deps: set[str]) -> None:
|
||||
# test build target
|
||||
|
||||
assert '__:recommended__' in deps
|
||||
assert '__:build__:test__' not in deps
|
||||
assert '__:build__' not in deps
|
||||
assert '__:test__' not in deps
|
||||
assert '__:optional__' not in deps
|
||||
|
||||
# test nested ignore
|
||||
|
||||
assert '__nested__on_macos__on_linux__' not in deps
|
||||
assert '__nested__on_linux__on_macos__' not in deps
|
||||
|
||||
# test macos versions
|
||||
for os_name, os_ver in Arch.ALL_OS.items():
|
||||
if Arch.IS_MAC and Arch.OS_VER == os_ver:
|
||||
assert f'__on_{os_name}__' in deps, f'{os_name} in deps'
|
||||
else:
|
||||
assert f'__on_{os_name}__' not in deps, f'{os_name} not in deps'
|
||||
|
||||
if Arch.IS_MAC:
|
||||
assert '__on_macos__' in deps
|
||||
assert '__on_linux__' not in deps
|
||||
assert '__uses_from_macos__' not in deps
|
||||
else:
|
||||
assert '__on_linux__' in deps
|
||||
assert '__on_macos__' not in deps
|
||||
assert '__uses_from_macos__' in deps
|
||||
|
||||
if Arch.IS_ARM:
|
||||
assert '__on_arm__' in deps
|
||||
assert '__on_intel__' not in deps
|
||||
assert '__on_arch :arm__' in deps
|
||||
assert '__on_arch :intel__' not in deps
|
||||
else:
|
||||
assert '__on_arm__' not in deps
|
||||
assert '__on_intel__' in deps
|
||||
assert '__on_arch :arm__' not in deps
|
||||
assert '__on_arch :intel__' in deps
|
||||
|
||||
if Arch.IS_MAC and Arch.OS_VER <= '12':
|
||||
assert '__on_monterey :or_older__' in deps
|
||||
else:
|
||||
assert '__on_monterey :or_older__' not in deps
|
||||
|
||||
if Arch.OS_VER <= '10.12' and Arch.IS_MAC:
|
||||
assert '__on_system macos: :sierra_or_older__' in deps
|
||||
else:
|
||||
assert '__on_system macos: :sierra_or_older__' not in deps
|
||||
|
||||
if Arch.IS_MAC and Arch.OS_VER < '11':
|
||||
assert '__on_system :linux, macos: :big_sur_or_newer__' not in deps
|
||||
else:
|
||||
assert '__on_system :linux, macos: :big_sur_or_newer__' in deps
|
||||
|
||||
# test uses_from_macos
|
||||
|
||||
assert '__uses_from_macos__:build__' not in deps
|
||||
assert '__uses_from_macos__:build__since__' not in deps
|
||||
|
||||
if Arch.OS_VER >= '10.15' and Arch.IS_MAC:
|
||||
assert '__uses_from_macos__since_catalina__' not in deps
|
||||
assert '__uses_from_macos__since_sierra__' not in deps
|
||||
elif Arch.OS_VER >= '10.12' and Arch.IS_MAC:
|
||||
assert '__uses_from_macos__since_catalina__' in deps
|
||||
assert '__uses_from_macos__since_sierra__' not in deps
|
||||
else:
|
||||
assert '__uses_from_macos__since_catalina__' in deps
|
||||
assert '__uses_from_macos__since_sierra__' in deps
|
||||
|
||||
# test if-clause
|
||||
|
||||
if Arch.OS_VER >= '10.15' and Arch.IS_MAC:
|
||||
assert '__if_macos_>=_catalina__' in deps
|
||||
assert '__if_build.with_catalina__' in deps
|
||||
assert '__if_build.without_catalina__' not in deps
|
||||
else:
|
||||
assert '__if_build.with_catalina__' not in deps
|
||||
assert '__if_build.without_catalina__' in deps
|
||||
assert '__if_macos_>=_catalina__' not in deps
|
||||
|
||||
if Arch._SOFTWARE_VERSIONS['clang'] <= [1400]:
|
||||
assert '__if_clang_<=_1400__' in deps
|
||||
else:
|
||||
assert '__if_clang_<=_1400__' not in deps
|
||||
|
||||
if Arch._SOFTWARE_VERSIONS['gcc'] < [9]:
|
||||
assert '__if_gcc_<_9__' in deps
|
||||
else:
|
||||
assert '__if_gcc_<_9__' not in deps
|
||||
|
||||
if 'zlib' in RubyParser.FAKE_INSTALLED:
|
||||
assert '__if_any_zlib_installed__' in deps
|
||||
else:
|
||||
assert '__if_any_zlib_installed__' not in deps
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user