feat: proper ruby parser for dependencies

This commit is contained in:
relikd
2025-08-27 17:54:49 +02:00
parent 376a3892d4
commit 2c51910ee2
5 changed files with 841 additions and 21 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/git-clone/

13
Makefile Normal file
View 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
View File

@@ -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
View 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
View 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()