Import Upstream version 0.20201028

This commit is contained in:
Lu zhiping 2022-06-10 11:49:00 +08:00
commit a3134ccad1
12 changed files with 892 additions and 0 deletions

35
README.md Normal file
View File

@ -0,0 +1,35 @@
Mini-Soong
==========
Mini-Soong is a minimalist and incomplete reimplementation of Soong, the
Android build system. It is intended to simplify the packaging of parts
of Android for Debian. It is not intended to be a complete replacement
for Soong.
What is Soong?
==============
The Soong build system was introduced in Android 7.0 to replace Make
which at Android's scale became slow, error prone, unscalable, and
difficult to test. It leverages the Kati GNU Make clone tool and Ninja
build system component to speed up builds of Android.
To learn more about Soong, see <https://source.android.com/setup/build>.
What does Mini-Soong support?
=============================
At the moment, Mini-Soong accepts almost any Soong Blueprint file,
but only supports a minimal set of features Soong provides. The only
feature of the Blueprint format not supported is the addition of maps.
Feature-wise, only flat Soong files for projects in C, C++ and assembler
work. No recursive builds, other programming languages, YACC support,
rule generation or any of the advanced features.
Mini-Soong generates a Makefile with three targets: `clean`, `build`,
`install`. The `install` target only installs binaries and shared
libraries. Static libraries and headers are *not* installed. `DESTDIR`,
`prefix` and `libdir` are taken into account. When ran on a Debian system
with `dpkg-dev` installed, the system build flags are automatically
picked up.

0
mini_soong/__init__.py Normal file
View File

View File

@ -0,0 +1,31 @@
import importlib
import pkgutil
builders = []
methods = {}
for _, name, _ in pkgutil.walk_packages(__path__):
mod = importlib.import_module(f"{__name__}.{name}")
builders.append(mod)
for fn in mod.__dict__.get('__all__', []):
methods[fn] = mod.__dict__[fn]
def flag_defaults():
print(".PHONY: build install\n")
print(".DEFAULT_GOAL := build\n")
print("DESTDIR ?=")
print("prefix ?= /usr")
print("libdir ?= ${prefix}/lib\n")
for builder in builders:
if 'flag_defaults' in builder.__dict__:
builder.flag_defaults()
def extra_targets():
install_targets = []
for builder in builders:
if 'extra_targets' in builder.__dict__:
install_targets += builder.extra_targets()
print(f"install: {' '.join(install_targets)}")

346
mini_soong/builders/cc.py Normal file
View File

@ -0,0 +1,346 @@
# Builders for C and C++
#
# Copyright 2020 Andrej Shadura
#
# SPDX-License-Identifier: MIT
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
__all__ = [
'cc_defaults',
'cc_binary',
'cc_binary_host',
'cc_library',
'cc_library_shared',
'cc_library_host_shared',
'cc_library_static',
'cc_library_host_static',
'cc_test',
'cc_test_host',
'cc_benchmark',
'cc_benchmark_host',
]
from ..utils import mergedefaults, print_vars, match_libs
from ..model import defaults, all_targets, all_modules, current_recipe
from functools import lru_cache
from pathlib import Path
import os
import sh
dpkg_architecture = sh.Command('dpkg-architecture')
targets_binary = list()
targets_shlib = list()
flag_blacklist = ['-Werror', '-U_FORTIFY_SOURCE', '-m32', '-m64', '-Wno-#pragma-messages']
@lru_cache(maxsize=None)
def detect_arch():
if 'DEB_HOST_ARCH' in os.environ:
return os.environ['DEB_HOST_ARCH']
else:
return dpkg_architecture('-q', 'DEB_HOST_ARCH').rstrip()
def map_arch():
arch = detect_arch()
if arch == 'amd64':
return 'x86_64'
elif arch == 'i386':
return 'x86'
elif arch == 'arm64':
return 'arm64'
elif arch.startswith('arm'):
return 'arm'
elif arch.startswith('mips64'):
return 'mips64'
elif arch.startswith('mips'):
return 'mips'
return arch
def multilib():
if map_arch().endswith('64'):
return 'lib64'
else:
return 'lib32'
@lru_cache(maxsize=None)
def detect_multiarch():
if 'DEB_HOST_MULTIARCH' in os.environ:
return os.environ['DEB_HOST_MULTIARCH']
else:
return dpkg_architecture('-q', 'DEB_HOST_MULTIARCH').rstrip()
def collect_defaults(args):
global defaults
def_cxxflags = []
def_cflags = []
def_ldflags = []
def_ldlibs = []
def_srcs = []
for default in args.get('defaults', []):
if default in defaults:
def_cxxflags += [f"$({default}_CXXFLAGS)"]
def_cflags += [f"$({default}_CFLAGS)"]
def_ldflags += [f"$({default}_LDFLAGS)"]
def_ldlibs += [f"$({default}_LDLIBS)"]
def_srcs += [f"$({default}_SRCS)"]
arch_specific = args.get('arch', {}).get(map_arch(), {})
mergedefaults(args, arch_specific)
target_specific = args.get('target', {}).get('linux_glibc', {})
mergedefaults(args, target_specific)
multilib_specific = args.get('multilib', {}).get(multilib(), {})
mergedefaults(args, multilib_specific)
return def_cxxflags, def_cflags, def_ldflags, def_ldlibs, def_srcs
def filter_flags(flags):
return [flag for flag in flags if flag not in flag_blacklist]
def relative_to(path: Path, fname: str) -> Path:
if fname.startswith('/'):
return Path(fname)
else:
return path / fname
def rel(fname: str) -> Path:
return relative_to(current_recipe['dir'], fname)
def cc_defaults(**args):
def_cxxflags, def_cflags, def_ldflags, def_ldlibs, def_srcs = collect_defaults(args)
defaults[args['name']] = {k: v for k, v in args.items() if k != 'name'}
local_include_dirs = args.get('local_include_dirs', [])
cxxflags = filter_flags(def_cxxflags + args.get('cppflags', []) + [f"-I{inc}" for inc in local_include_dirs])
cflags = filter_flags(def_cflags + args.get('cflags', []) + [f"-I{rel(inc)}" for inc in local_include_dirs])
ldflags = filter_flags(def_ldflags + args.get('ldflags', []))
ldlibs = def_ldlibs + [f"-l{lib[3:]}" for lib in args.get('shared_libs', [])]
srcs = [(f"{rel(path)}" if not path.startswith('$') else path) for path in def_srcs + args.get('srcs', [])]
print_vars(args['name'], locals(), ['cxxflags', 'cflags', 'ldflags', 'ldlibs', 'srcs'])
print()
def cc_compile_link(args, binary: bool = True, shared: bool = False, variables: bool = True):
print(f"# link {args['name']} {'shared' if shared else 'static'} library")
def_cxxflags, def_cflags, def_ldflags, def_ldlibs, def_srcs = collect_defaults(args)
local_include_dirs = args.get('local_include_dirs', [])
if not binary:
export_include_dirs = args.get('export_include_dirs', [])
export_system_include_dirs = args.get('export_system_include_dirs', [])
includes = [f"-I{rel(inc)}" for inc in export_include_dirs]
system_includes = [f"-isystem {rel(inc)}" for inc in export_system_include_dirs]
cxxflags = filter_flags(def_cxxflags + args.get('cppflags', []) + [f"-I{rel(inc)}" for inc in local_include_dirs])
cflags = filter_flags(def_cflags + args.get('cflags', []) + [f"-I{rel(inc)}" for inc in local_include_dirs])
if not binary:
cxxflags += [f"$({args['name']}_INCLUDES)", f"$({args['name']}_SYSTEM_INCLUDES)"]
cflags += [f"$({args['name']}_INCLUDES)", f"$({args['name']}_SYSTEM_INCLUDES)"]
ldflags = filter_flags(def_ldflags + args.get('ldflags', []))
shared_libs = args.get('shared_libs', [])
if not binary:
if shared and 'shared' in args:
mergedefaults(args, args['shared'])
elif not shared and 'static' in args:
mergedefaults(args, args['static'])
shared_libs += [lib for lib in args.get('static_libs', []) if lib not in all_modules]
shared_libs += [lib for lib in args.get('whole_static_libs', []) if lib not in all_modules]
ldlibs = def_ldlibs + [f"-l{lib[3:]}" for lib in shared_libs]
shlibdeps = []
multiarch_libdir = Path(f"/usr/lib/{detect_multiarch()}")
multiarch_android_libdir = multiarch_libdir / "android"
external_android_shlibs = [lib for lib in shared_libs if (lib not in all_modules) and (multiarch_android_libdir / f"{lib}.so").exists()]
if external_android_shlibs:
ldflags += [f"-L{multiarch_android_libdir}"]
external_shlibs = external_android_shlibs + [lib for lib in shared_libs if (lib not in all_modules) and (multiarch_libdir / f"{lib}.so").exists()]
for lib in shared_libs:
cxxflags += [f"$({lib}_INCLUDES)", f"$({lib}_SYSTEM_INCLUDES)"]
cflags += [f"$({lib}_INCLUDES)", f"$({lib}_SYSTEM_INCLUDES)"]
if lib not in external_shlibs:
shlibdeps += [f"{lib}.so"]
if any(lib for lib in shared_libs if lib in all_modules):
ldflags += ["-L."]
static_libs = [f"{lib}.a" for lib in args.get('static_libs', []) if lib in all_modules]
if 'whole_static_libs' in args:
static_libs += ["-Wl,--whole-archive"]
static_libs += [f"{lib}.a" for lib in args.get('whole_static_libs', []) if lib in all_modules]
static_libs += ["-Wl,--no-whole-archive"]
srcs = [(f"{rel(path)}" if not path.startswith('$') else path) for path in def_srcs + args.get('srcs', [])]
if variables:
print(f"{args['name']}_DIR = {current_recipe['dir']}")
if shared:
matches = {lib: (major, ver) for lib, major, ver in match_libs([args['name']])}
if args['name'] in matches:
somajor, soversion = matches[args['name']]
print(f"{args['name']}_soversion ?= {soversion}")
print(f"{args['name']}_somajor = $(basename $(basename $({args['name']}_soversion)))")
else:
print(f"{args['name']}_soversion ?= 0.0.0")
print(f"{args['name']}_somajor = $(basename $(basename $({args['name']}_soversion)))")
print_vars(args['name'], locals(), ['cxxflags', 'cflags', 'ldflags', 'ldlibs', 'srcs'] + ['includes', 'system_includes'] if not binary else [])
print()
if not binary:
suffix = f".so.$({args['name']}_soversion)" if shared else ".a"
soname = f"{args['name']}{suffix}"
target = soname
shared_flag = f"-shared -Wl,-soname,{args['name']}.so.$({args['name']}_somajor)" if shared else ""
else:
target = args['name']
shared_flag = ""
if shared:
print(f"{args['name']}.so: {target}")
print(f"{target}: $({args['name']}_SRCS) {' '.join(shlibdeps)}")
print("\t" + ' '.join([
f"$(CC) $({args['name']}_SRCS) -o $@" if shared or binary else f"$(CC) $({args['name']}_SRCS) -c",
' '.join(static_libs),
f"$(CPPFLAGS)",
f"$(CFLAGS) $({args['name']}_CFLAGS)",
f"$(CXXFLAGS) $({args['name']}_CXXFLAGS)" if have_cxx(srcs) else "",
f"$(LDFLAGS) $({args['name']}_LDFLAGS) {shared_flag}" if shared or binary else "",
"-lstdc++" if have_cxx(srcs) else "",
f"$(LDLIBS) $({args['name']}_LDLIBS)" if shared or binary else "",
]))
if shared:
print(f"\tln -sf $@ $(@:.$({args['name']}_soversion)=.$({args['name']}_somajor))")
print(f"\tln -sf $(@:.$({args['name']}_soversion)=.$({args['name']}_somajor)) $(@:.$({args['name']}_soversion)=)")
if not shared and not binary:
print(f"\tar rcs {soname} $(patsubst %,%.o,$(notdir $(basename $({args['name']}_SRCS))))")
print(f"\trm $(patsubst %,%.o,$(notdir $(basename $({args['name']}_SRCS))))")
all_targets.append(target)
print()
print(f"clean-{target}:")
print(f"\trm -f {target}")
if shared:
print(f"\trm -f {args['name']}.so {args['name']}.so.$({args['name']}_somajor)")
print()
print(f"install-{target}: {target} install-{args['name']}-headers")
print(f"\tinstall -m644 -D -t $(DESTDIR)$(libdir) $<")
print(f"\tln -s $< $(DESTDIR)$(libdir)/$(<:.$({args['name']}_soversion)=.$({args['name']}_somajor))")
print(f"\tln -s $(<:.$({args['name']}_soversion)=.$({args['name']}_somajor)) $(DESTDIR)$(libdir)/$(<:.$({args['name']}_soversion)=)")
targets_shlib.append(target)
elif binary:
print()
print(f"install-{target}: {target}")
print(f"\tinstall -m755 -D -t $(DESTDIR)$(prefix)/bin $<")
targets_binary.append(target)
if shared and (export_include_dirs or export_system_include_dirs):
print()
print(f"install-{args['name']}-headers:")
print(f"\tmkdir -p $(DESTDIR)$(prefix)/include/{args['name']}")
for inc_dir in export_include_dirs + export_system_include_dirs:
print(f"\tcp -R -t $(DESTDIR)$(prefix)/include/{args['name']} {inc_dir}/*")
print()
def cc_binary(**args):
cc_compile_link(args, binary = True, shared = False)
def cc_binary_host(**args):
cc_compile_link(args, binary = True, shared = False)
def cc_library(**args):
cc_compile_link(args, binary = False, shared = True)
cc_compile_link(args, binary = False, shared = False, variables = False)
def cc_library_shared(**args):
cc_compile_link(args, binary = False, shared = True)
def cc_library_host_shared(**args):
cc_compile_link(args, binary = False, shared = True)
def cc_library_static(**args):
cc_compile_link(args, binary = False, shared = False)
def cc_library_host_static(**args):
cc_compile_link(args, binary = False, shared = False)
def cc_test(**args):
pass
def cc_test_host(**args):
pass
def cc_benchmark(**args):
pass
def cc_benchmark_host(**args):
pass
def have_cxx(files):
return any(is_cxx(f) for f in files)
def is_cxx(filename):
return (filename.endswith('.cc') or
filename.endswith('.cxx') or
filename.endswith('.cpp') or
filename.endswith('.CPP') or
filename.endswith('.c++') or
filename.endswith('.cp') or
filename.endswith('.C'))
def flag_defaults():
print("DPKG_EXPORT_BUILDFLAGS = 1")
print("-include /usr/share/dpkg/buildflags.mk\n")
print("CXXFLAGS += " + ' '.join([
"-D__STDC_FORMAT_MACROS",
"-D__STDC_CONSTANT_MACROS",
"-std=c++11",
]))
print("CFLAGS += " + ' '.join([
"-D_FILE_OFFSET_BITS=64",
"-D_LARGEFILE_SOURCE=1",
"-Wa,--noexecstack",
"-fPIC",
"-fcommon",
]))
print("LDFLAGS += " + ' '.join([
"-Wl,-z,noexecstack",
"-Wl,--no-undefined-version",
"-Wl,--as-needed",
]))
print("LDLIBS += " + ' '.join([
f'-l{lib}' for lib in [
"c",
"dl",
"gcc",
#"gcc_s",
"m",
#"ncurses",
"pthread",
#"resolv",
"rt",
"util",
]
]))
print()
def extra_targets():
targets = []
if targets_binary:
print(f"install-binaries: install-{' install-'.join(targets_binary)}")
targets.append('install-binaries')
if targets_shlib:
print(f"install-shlibs: install-{' install-'.join(targets_shlib)}")
targets.append('install-shlibs')
return targets

View File

@ -0,0 +1,9 @@
__all__ = [
'java_defaults',
]
import sys
def java_defaults(**args):
print("Warning: java_defaults not yet implemented", file=sys.stderr)
pass

73
mini_soong/main.py Normal file
View File

@ -0,0 +1,73 @@
# Soong to Makefile translator
#
# Copyright 2020 Andrej Shadura
#
# SPDX-License-Identifier: MIT
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from .parser import soong
from .model import defaults, all_targets, all_modules, current_recipe, Module
from .utils import mergedefaults, print_vars
from .builders import flag_defaults, extra_targets
import argparse
import os
from pathlib import Path
def run():
import sys
global all_modules, current_recipe
parser = argparse.ArgumentParser(description='Convert Soong recipes to a Makefile')
parser.add_argument('bp', metavar='RECIPES', type=str, default='**/Android.bp', nargs='?', help='pattern used to find recipes')
parser.add_argument('poutput', metavar='OUTPUT', type=str, nargs='?', help='where to write the Makefile (default: stdout)')
parser.add_argument('--output', '-o', metavar='OUTPUT', type=str, help='where to write the Makefile (default: stdout)')
args = parser.parse_args()
bp = args.bp
output = args.poutput or args.output
if output:
sys.stdout = open(output, 'w')
flag_defaults()
all_modules.clear()
# todo: allow chdir
recipes = sorted([path for path in Path('.').glob(bp) if not path.parts[0].startswith('.')])
for recipe in recipes:
parsed = soong.parseString(recipe.read_text())
all_modules += [r.arguments['name'] for r in parsed if isinstance(r, Module) and 'name' in r.arguments]
current_recipe.update({
'name': str(recipe),
'dir': recipe.parents[0],
'parsed': parsed
})
for r in parsed:
r.run()
print(f"build: {' '.join(all_targets)}")
if all_targets:
clean_targets = [f"clean-{target}" for target in all_targets]
print(f"clean: {' '.join(clean_targets)}")
else:
print(f"clean:")
extra_targets()

65
mini_soong/model.py Normal file
View File

@ -0,0 +1,65 @@
#!/usr/bin/python3
# Soong parsed model
#
# Copyright 2020 Andrej Shadura
#
# SPDX-License-Identifier: MIT
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from dataclasses import dataclass
all_modules = list()
all_targets = list()
defaults = dict()
current_recipe = dict()
@dataclass
class Assignment:
# NOT currently used
variable: str
value: object
def __init__(self, tokens):
self.variable = tokens[0][0]
self.value = tokens[0][1]
def run(self):
global variables
variables[self.variable] = self.value
@dataclass
class Module:
name: str
arguments: dict
def __init__(self, tokens):
self.name = tokens[0][0]
self.arguments = tokens[0][1].asDict()
def run(self):
from . import builders
if self.name in builders.methods:
builders.methods[self.name](**self.arguments)
else:
import sys
print(f"WARNING: {self.name} not yet supported", file=sys.stderr)

99
mini_soong/parser.py Normal file
View File

@ -0,0 +1,99 @@
#!/usr/bin/python3
# Soong parser
#
# Based on an implementation of a simple JSON parser shipped with pyparsing
#
# Copyright 2006, 2007, 2016 Paul McGuire
# Copyright 2020 Andrej Shadura
#
# SPDX-License-Identifier: MIT
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import pyparsing as pp
from pyparsing import pyparsing_common as ppc
from typing import Mapping, Sequence
from .model import Assignment, Module
def make_keyword(kwd_str, kwd_value):
return pp.Keyword(kwd_str).setParseAction(pp.replaceWith(kwd_value))
TRUE = make_keyword("true", True)
FALSE = make_keyword("false", False)
NULL = make_keyword("null", None)
LBRACK, RBRACK, LBRACE, RBRACE, COLON, EQUALS, COMMA, PLUS = map(pp.Suppress, "[]{}:=,+")
quotedString = pp.quotedString().setParseAction(pp.removeQuotes)
number = ppc.integer()
def add_things(tokens):
return [tokens[0] + tokens[1]]
numberExp = (number + PLUS + number).setParseAction(add_things)
bracedMap = pp.Forward().setName("dictionary")
singleValue = pp.Forward()
listElements = pp.delimitedList(quotedString) + pp.Optional(COMMA)
stringList = pp.Group(LBRACK + pp.Optional(listElements, []) + RBRACK)
variables = dict()
def set_variable(tokens):
global variables
variable, value = tokens[0]
variables[variable] = value
return []
def resolve_var(tokens):
global variables
variable, = tokens[0]
return [variables[variable]]
def add_vars(tokens):
global variables
left, right = tokens[0]
return [variables[left] + variables[right]]
variableReference = pp.Group(ppc.identifier).setParseAction(resolve_var)
variableExp = pp.Group(ppc.identifier + PLUS + ppc.identifier).setParseAction(add_vars)
stringListExp = (stringList + PLUS + stringList).setParseAction(add_things)
singleValue << (
quotedString | numberExp | number | pp.Group(bracedMap) | TRUE | FALSE | NULL | variableExp | variableReference | stringListExp | stringList
)
singleValue.setName("value")
memberDef = pp.Group((quotedString | ppc.identifier) + COLON + singleValue)
mapMembers = pp.delimitedList(memberDef) + pp.Optional(COMMA)
mapMembers.setName("dictionary members")
bracedMap << pp.Dict(LBRACE + pp.Optional(mapMembers) + RBRACE)
comment = pp.cppStyleComment
soongAssignment = pp.Group(ppc.identifier + EQUALS + singleValue)
soongAssignment.setParseAction(set_variable)
soongModule = pp.Group(ppc.identifier + pp.Group(bracedMap)).setParseAction(Module)
soongStatement = soongAssignment | soongModule
soong = soongStatement[...]
soong.ignore(comment)

94
mini_soong/utils.py Normal file
View File

@ -0,0 +1,94 @@
from typing import Mapping, Sequence
def mergedefaults(a, b):
for k, v in b.items():
if k in a:
if isinstance(v, Mapping):
new = v.copy()
new.update(a[k])
a[k] = new
elif isinstance(v, Sequence):
a[k] = v + a[k]
else:
a[k] = v
return a
def add_dicts(a, b):
# currently unused
new = {}
for k, v in b.items():
if k in a:
# the docco isnt clear on this but its better to overwrite strings
if isinstance(v, str):
new[k] = b[k]
elif isinstance(v, Mapping):
new[k] = add_dicts(a[k], b[k])
elif isinstance(v, Sequence):
new[k] = a[k] + b[k]
else:
new[k] = v
return new
def print_vars(target, kv, names):
for name in names:
if name in kv:
print(f"{target}_{name.upper():<8} = {' '.join(kv[name])}")
import re
from functools import lru_cache
from pathlib import Path
from debian.deb822 import Deb822
from debian.changelog import Changelog
from debian.debian_support import Version
@lru_cache(maxsize=None)
def deb_version():
changelog = Path('debian/changelog')
if changelog.exists():
try:
with changelog.open() as f:
ch = Changelog(f, max_blocks=1)
return ch.version
except:
pass
return None
def library_pkgs():
_, _, lib_pkgs = parse_control()
return lib_pkgs
def mangle_lib(lib: str) -> str:
# add a dash if there is a number before .so
lib = re.sub(r'([0-9])\.so$', r'\1-', lib)
# drop .so
lib = re.sub(r'\.so$', '', lib)
return lib.replace('_', '-').lower()
def match_libs(libs):
pkg_ver = deb_version()
if pkg_ver:
pkg_ver = '.'.join(pkg_ver.upstream_version.split('.')[:3])
lib_pkgs = sorted(library_pkgs())
mangled_libs = [(lib, mangle_lib(lib)) for lib in sorted(libs)]
for lib, mangled_lib in mangled_libs:
for pkg in lib_pkgs:
if pkg.startswith(mangled_lib):
rest = pkg.replace(mangled_lib, '', 1)
if rest.isdecimal():
yield (lib, rest, pkg_ver if pkg_ver.startswith(rest) else rest)
break
@lru_cache(maxsize=None)
def parse_control():
control = Path('debian/control')
if control.exists():
try:
with control.open() as f:
bin_pkgs = [p['Package'] for p in Deb822.iter_paragraphs(f) if 'Package' in p]
dev_pkgs = [p for p in bin_pkgs if p.startswith('lib') and p.endswith('-dev')]
lib_pkgs = [p for p in bin_pkgs if p.startswith('lib') and not p.endswith('-dev')]
return bin_pkgs, dev_pkgs, lib_pkgs
except:
pass
return [], [], []

31
setup.cfg Normal file
View File

@ -0,0 +1,31 @@
[metadata]
name = mini-soong
version = 0
url = https://salsa.debian.org/android-tools-team/mini-soong
author = Andrej Shadura
author-email = andrewsh@debian.org
description = minimalist Soong build system reimplementation
long_description = file: README.md
long_description_content_type = text/markdown
license = MIT
classifier =
Development Status :: 4 - Beta
License :: OSI Approved :: MIT
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.8
[options]
python_requires = >= 3.8
packages = find:
install_requires =
python-debian
pyparsing
sh
[options.data_files]
#share/perl5/Debian/Debhelper/Buildsystem = soong.pm
[options.entry_points]
console_scripts =
mini-soong = mini_soong.main:run

9
setup.py Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/python3
import sys
from setuptools import setup, find_packages
if sys.version_info < (3, 8, 0):
sys.exit("Python 3.8.0 is the minimum required version")
setup()

100
soong.pm Normal file
View File

@ -0,0 +1,100 @@
# A debhelper build system class for handling simple soong-based projects.
# It uses mini-soong, a Python re-implementation of a minimal Soong subset.
#
# Copyright: © 2008 Joey Hess
# © 2008-2009 Modestas Vainius
# © 2020 Andrej Shadura
#
# SPDX-License-Identifier: GPL-2+
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version.
package Debian::Debhelper::Buildsystem::soong;
use strict;
use Debian::Debhelper::Dh_Lib qw(compat escape_shell clean_jobserver_makeflags gain_root_cmd dpkg_architecture_value);
use base 'Debian::Debhelper::Buildsystem::makefile';
sub DESCRIPTION {
"mini-Soong"
}
sub clean {
my $this=shift;
if (-e $this->get_buildpath("soong.mk")) {
$this->SUPER::clean(@_);
}
$this->doit_in_builddir('rm', '-f', 'soong.mk');
}
sub configure {
my $this=shift;
$this->mkdir_builddir();
my $builddir = $this->get_builddir();
my @opts;
if (-e $this->get_buildpath("Android.bp")) {
push @opts, "-o";
push @opts, "soong.mk";
}
$this->doit_in_builddir("mini-soong", @opts, @_);
}
sub do_make {
my $this=shift;
# Avoid possible warnings about unavailable jobserver,
# and force make to start a new jobserver.
clean_jobserver_makeflags();
my @opts;
push @opts, "-f";
push @opts, "soong.mk";
my $prefix = "/usr";
push @opts, "prefix=${prefix}";
push @opts, "mandir=${prefix}/share/man";
push @opts, "infodir=${prefix}/share/info";
push @opts, "sysconfdir=/etc";
my $multiarch=dpkg_architecture_value("DEB_HOST_MULTIARCH");
if (defined $multiarch) {
push @opts, "libdir=${prefix}/lib/$multiarch";
push @opts, "libexecdir=${prefix}/lib/$multiarch" if compat(11);
}
else {
push @opts, "libexecdir=${prefix}/lib" if compat(11);
}
my @root_cmd;
if (exists($this->{_run_make_as_root}) and $this->{_run_make_as_root}) {
@root_cmd = gain_root_cmd();
}
$this->doit_in_builddir(@root_cmd, $this->{makecmd}, @opts, @_);
}
sub exists_make_target {
my $this=shift;
return $this->SUPER::exists_make_target(@_, "-f", "soong.mk");
}
sub check_auto_buildable {
my $this=shift;
my ($step)=@_;
return 0 unless -e $this->get_sourcepath("Android.bp");
return 1;
}
sub new {
my $class=shift;
my $this=$class->SUPER::new(@_);
$this->{makecmd} = "make";
return $this;
}
1