Import Upstream version 0.20201028
This commit is contained in:
commit
a3134ccad1
|
@ -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,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)}")
|
||||
|
|
@ -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
|
|
@ -0,0 +1,9 @@
|
|||
__all__ = [
|
||||
'java_defaults',
|
||||
]
|
||||
|
||||
import sys
|
||||
|
||||
def java_defaults(**args):
|
||||
print("Warning: java_defaults not yet implemented", file=sys.stderr)
|
||||
pass
|
|
@ -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()
|
|
@ -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)
|
|
@ -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)
|
|
@ -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 isn’t clear on this but it’s 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 [], [], []
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
Loading…
Reference in New Issue