///|
/// Native argument adapter declared by a platform package.
pub struct AdapterSpec {
type_name : String
ark_type : String
ark_import : String
acquire : String
release : String
}
///|
/// Adapter bound to the package alias used in `moonohos.json`.
pub struct AdapterDef {
pkg_alias : String
type_name : String
pkg : String
ark_type : String
ark_import : String
acquire : String
release : String
}
///|
/// Controlled description of a MoonBit package that ships C stubs.
pub struct NativeDescriptor {
pkg : String
sources : Array[String]
include_dirs : Array[String]
defines : Array[String]
libraries : Array[String]
abis : Array[String]
adapters : Array[AdapterSpec]
}
///|
pub fn native_descriptor_file() -> String {
"moonohos.native.json"
}
///|
/// HarmonyOS NDK libraries that platform packages may link.
pub fn system_library_allowlist() -> Array[String] {
["libhilog_ndk.z.so", "librawfile.z.so", "libdeviceinfo_ndk.z.so"]
}
///|
pub fn abi_triple(abi : String) -> String {
if abi == "arm64-v8a" {
"aarch64-linux-ohos"
} else {
"x86_64-linux-ohos"
}
}
///|
fn quoted_strings(text : String) -> Array[String] {
let result : Array[String] = []
let mut current = ""
let mut quoted = false
let mut escaped = false
for ch in text {
if !quoted {
if ch == '"' {
quoted = true
current = ""
}
} else if escaped {
current += ch.to_string()
escaped = false
} else if ch == '\\' {
escaped = true
} else if ch == '"' {
quoted = false
result.push(current)
} else {
current += ch.to_string()
}
}
result
}
///|
/// Extracts the `native-stub` list from `moon.pkg` or `moon.pkg.json`.
pub fn native_stubs(package_file : String) -> Array[String] {
let marker = "\"native-stub\""
let parts = package_file.split(marker).to_array()
if parts.length() < 2 {
return []
}
let rest = parts[1].to_owned()
let open = rest.split("[").to_array()
if open.length() < 2 {
return []
}
let list = open[1].split("]").head().unwrap_or("").to_owned()
quoted_strings(list)
}
///|
/// Build hooks and compiler flags that MoonOHOS cannot reproduce for OHOS.
pub fn forbidden_build_hook(package_file : String) -> String? {
let compact = package_file
.replace_all(old=" ", new="")
.replace_all(old="\n", new="")
.replace_all(old="\r", new="")
.replace_all(old="\t", new="")
for
(needle, reason) in [
("pre-build", "pre-build hooks"),
("dev_build(", "pre-build hooks"),
("rule(", "custom build rules"),
("cc-flags", "C compiler flags"),
("cc-link-flags", "C linker flags"),
("stub-cc", "stub compiler overrides"),
("\"cc\":", "C compiler overrides"),
("native_stub", "legacy native stub fields"),
] {
if compact.contains(needle) {
return Some(reason)
}
}
None
}
///|
fn relative_source(path : String, context : String) -> Unit raise ConfigError {
if path == "" ||
path.has_prefix("/") ||
path.contains(":") ||
path.contains("\\") ||
path.split("/").any(p => p == ".." || p == "." || p == "") {
raise Invalid(context + ": path must stay inside the package: " + path)
}
}
///|
fn string_list(
json : Json,
key : String,
context : String,
) -> Array[String] raise ConfigError {
match json {
Object(fields) =>
match fields.get(key) {
None => []
Some(Array(values)) =>
values.map(v => {
match v {
String(text) => text
_ => raise Invalid(context + ": " + key + " must contain strings")
}
})
_ => raise Invalid(context + ": " + key + " must be an array")
}
_ => raise Invalid(context + ": descriptor must be an object")
}
}
///|
fn native_symbol(name : String) -> Bool {
name.has_prefix("moonohos_") && identifier(name) && name.length() <= 96
}
///|
fn valid_define(text : String) -> Bool {
let parts = text.split("=").map(s => s.to_owned()).to_array()
if parts.length() > 2 || parts[0] == "" {
return false
}
for index, ch in parts[0] {
if !(ch >= 'A' && ch <= 'Z') &&
ch != '_' &&
!(index > 0 && ch >= '0' && ch <= '9') {
return false
}
}
parts[0] != "MOONOHOS_BRIDGE" &&
parts[0] != "MOONOHOS_TARGET_OHOS" &&
(
parts.length() == 1 ||
identifier(parts[1]) ||
parts[1].iter().all(c => c >= '0' && c <= '9')
)
}
///|
/// Accepts exactly `import { name } from '@kit.Kit';`.
fn valid_ark_import(text : String) -> Bool {
guard text.has_prefix("import { ") && text.has_suffix("';") else {
return false
}
let parts = text[9:text.length() - 2]
.to_owned()
.split(" } from '@kit.")
.to_array()
parts.length() == 2 &&
identifier(parts[0].to_owned()) &&
identifier(parts[1].to_owned())
}
///|
/// Parses and validates a descriptor against the package's declared stubs.
pub fn parse_native_descriptor(
text : String,
pkg : String,
stubs : Array[String],
) -> NativeDescriptor raise ConfigError {
let context = native_descriptor_file() + " for " + pkg
let json = @json.parse(text) catch {
_ => raise Invalid(context + ": invalid JSON")
}
match json {
Object(fields) => {
for key, _ in fields {
if ![
"schema", "package", "sources", "includeDirs", "defines", "systemLibraries",
"abis", "adapters",
].contains(key) {
raise Invalid(context + ": unknown field " + key)
}
}
match fields.get("schema") {
Some(String("moonohos-native-v1")) => ()
_ => raise Invalid(context + ": schema must be moonohos-native-v1")
}
match fields.get("package") {
Some(String(name)) if name == pkg => ()
_ => raise Invalid(context + ": package field must be " + pkg)
}
}
_ => raise Invalid(context + ": descriptor must be an object")
}
let sources = string_list(json, "sources", context)
for source in sources {
relative_source(source, context)
if !source.has_suffix(".c") {
raise Invalid(context + ": only C sources are supported: " + source)
}
}
let declared = sources.copy()
declared.sort()
let expected = stubs.copy()
expected.sort()
if declared != expected {
raise Invalid(
context +
": sources must equal moon.pkg native-stub [" +
stubs.join(", ") +
"]",
)
}
let include_dirs = string_list(json, "includeDirs", context)
for dir in include_dirs {
relative_source(dir, context)
}
let defines = string_list(json, "defines", context)
for definition in defines {
if !valid_define(definition) {
raise Invalid(context + ": invalid define " + definition)
}
}
let libraries = string_list(json, "systemLibraries", context)
for library in libraries {
if !system_library_allowlist().contains(library) {
raise Invalid(
context +
": system library " +
library +
" is not in the allowlist " +
system_library_allowlist().join(", "),
)
}
}
let abis = string_list(json, "abis", context)
parse_abis(abis.join(",")) |> ignore
let adapters : Array[AdapterSpec] = []
let entries = match json {
Object(fields) =>
match fields.get("adapters") {
None => []
Some(Array(entries)) => entries
_ => raise Invalid(context + ": adapters must be an array")
}
_ => []
}
for entry in entries {
let adapter : AdapterSpec = match entry {
{
"type": String(type_name),
"arkType": String(ark_type),
"arkImport": String(ark_import),
"acquire": String(acquire),
"release": String(release),
..
} => { type_name, ark_type, ark_import, acquire, release, }
_ => raise Invalid(context + ": malformed adapter")
}
let ark_parts = adapter.ark_type.split(".").to_array()
if !identifier(adapter.type_name) ||
!native_symbol(adapter.acquire) ||
!native_symbol(adapter.release) ||
ark_parts.length() > 2 ||
!ark_parts.iter().all(p => identifier(p.to_owned())) ||
!valid_ark_import(adapter.ark_import) {
raise Invalid(context + ": invalid adapter " + adapter.type_name)
}
adapters.push(adapter)
}
{ pkg, sources, include_dirs, defines, libraries, abis, adapters, }
}
///|
pub fn NativeDescriptor::to_json(self : NativeDescriptor) -> Json {
{
"package": self.pkg,
"sources": self.sources.to_json(),
"includeDirs": self.include_dirs.to_json(),
"defines": self.defines.to_json(),
"systemLibraries": self.libraries.to_json(),
"abis": self.abis.to_json(),
"adapters": self.adapters
.map(a => {
"type": a.type_name,
"arkType": a.ark_type,
"acquire": a.acquire,
"release": a.release,
})
.to_json(),
}
}
///|
/// Package directories linked by `link-core`, in the compiler's order.
pub fn linked_packages(link_core : Array[String]) -> Array[(String, String)] {
let result : Array[(String, String)] = []
let mut index = 0
while index + 1 < link_core.length() {
if link_core[index] == "-pkg-sources" {
let value = link_core[index + 1]
// Windows directories contain ':' after the drive letter; split once.
let parts = value.split(":").to_array()
if parts.length() >= 2 {
let pkg = parts[0].to_owned()
let dir = value[pkg.length() + 1:].to_owned()
result.push((pkg, dir))
}
index += 2
} else {
index += 1
}
}
result
}
///|
/// Binds `@alias.Type` parameters to adapters using the target's mbti imports.
pub fn resolve_adapters(
project : Project,
descriptors : Array[NativeDescriptor],
imports : Map[String, String],
) -> Array[AdapterDef] raise ConfigError {
let result : Array[AdapterDef] = []
for function in project.functions {
for param in function.params {
guard param.kind is External(pkg_alias, type_name) else { continue }
if result
.iter()
.any(a => a.pkg_alias == pkg_alias && a.type_name == type_name) {
continue
}
if pkg_alias == "core" || pkg_alias.has_prefix("moonohos") {
raise Invalid("adapter: reserved package alias @" + pkg_alias)
}
let pkg = imports
.get(pkg_alias)
.unwrap_or_else(() => {
raise Invalid(
"adapter: @" +
pkg_alias +
" is not imported by " +
project.package_name,
)
})
let descriptor = descriptors
.search_by(d => d.pkg == pkg)
.map(i => descriptors[i])
.unwrap_or_else(() => {
raise Invalid(
"adapter: " + pkg + " has no " + native_descriptor_file(),
)
})
let spec = descriptor.adapters
.search_by(a => a.type_name == type_name)
.map(i => descriptor.adapters[i])
.unwrap_or_else(() => {
raise Invalid(
"adapter: " + pkg + " does not declare an adapter for " + type_name,
)
})
result.push({
pkg_alias,
type_name,
pkg,
ark_type: spec.ark_type,
ark_import: spec.ark_import,
acquire: spec.acquire,
release: spec.release,
})
}
}
result
}
///|
/// Additional CMake inputs contributed by native dependencies.
pub(all) struct NativeBuild {
sources : Array[String]
include_dirs : Array[String]
defines : Array[String]
libraries : Array[String]
}
///|
pub fn empty_native_build() -> NativeBuild {
{ sources: [], include_dirs: [], defines: [], libraries: [], }
}