diff --git a/arkoala/framework/native/scripts/configure.mjs b/arkoala/framework/native/scripts/configure.mjs new file mode 100644 index 0000000000000000000000000000000000000000..44d21b6dbcea0926148953f9b8a2c9deea6b45e5 --- /dev/null +++ b/arkoala/framework/native/scripts/configure.mjs @@ -0,0 +1,480 @@ +/* + * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from "fs"; +import { exit, platform } from "process"; +import chalk from "chalk"; +import path from "path"; +import minimist from "minimist" + +import { findMeson, CrossFile, requireEnv, relativeToSourceRoot } from "./utils.mjs" + +import { createRequire } from 'node:module'; +import { ohConf } from "../../../ohos-sdk/scripts/ohconf.mjs" +import { OH_SDK_COMPONENT, ohSdkInfo } from "../../../ohos-sdk/scripts/oh-sdk-utils.mjs" +import url from "url" +const require = createRequire(import.meta.url); + +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)) + +const OHCONF = ohConf(path.join(__dirname, "../../../../arkoala/arkui-common/.ohconf.json")) + +let cliOptions = minimist(process.argv.slice(2)) + +let targets = cliOptions._; +let help = cliOptions.h ||cliOptions.help || false; +let verbose = cliOptions.v ||cliOptions.verbose || false; +let reconfigure = cliOptions.reconfigure || false; +let wipe = cliOptions.wipe || false; +let clean = cliOptions.clean; +let nodeDir = cliOptions["node-dir"] || process.env.NODE_DIR || ''; +let nodeBuildType = cliOptions["node-buildtype"] || process.env.NODE_BUILDTYPE || "release"; +let dryRun = !!(cliOptions.n || cliOptions["dry-run"]); +let vmKind = cliOptions.vm_kind || undefined +let root = cliOptions.root || path.join(__dirname, "..") + +let arkBuildType = process.env.ARK_BUILDTYPE || "Release"; +let arkRuntimeOptions = process.env.ARK_RUNTIME_OPTIONS || "jit:aot"; + +if (targets.length === 0 || help) { + usage(); + exit(1); +} + +const meson = findMeson(); + +if (nodeDir && !path.isAbsolute(nodeDir)) { + console.log("NODE_DIR must be an absolute path") + exit(1); +} + +nodeDir && console.log(`NODE_DIR: ${chalk.green(nodeDir)}`); +console.log(`NODE_BUILDTYPE: ${chalk.green(nodeBuildType)}`); + +targets.forEach(target => configure(target)); + +function getNdkRoot() { + if (process.env["ANDROID_NDK_ROOT"]) return process.env["ANDROID_NDK_ROOT"] + let dir = path.join(requireEnv("ANDROID_SDK_ROOT"), 'ndk') + let ndks = fs.readdirSync(dir).sort() + if (ndks.length < 1) throw new Error("Unexpected NDK dir layout") + return path.join(dir, ndks[ndks.length - 1]) +} + +function getJdkRoot() { + if (process.env["JAVA_HOME"]) return process.env["JAVA_HOME"] + throw new Error("Cannot find JAVA_HOME") +} + +function getArkRuntime() { + if (process.env["ARK_RUNTIME"]) return process.env["ARK_RUNTIME"] + return "ets" +} + +function getPandaOhosSdkDir() { + return path.join(path.dirname(require.resolve("@panda/sdk")), 'ohos_arm64') +} + +export function configure(target) { + let buildDirName = `build-${target}`; + + if (fs.existsSync(buildDirName)) { + if (clean) { + !dryRun && fs.rmSync(buildDirName, { recursive: true, force: true }); + reconfigure = wipe = false; + } else if (!reconfigure && !wipe) { + console.log(chalk.yellow(`target ${target} already configured`) + + "\n restart with --clean, --wipe or --reconfigure to configure again"); + return; + } + } else { + reconfigure = false; + wipe = false; + } + + // TODO add architecture + let destDir = target; + let binDir = target == "wasm" ? destDir : void 0; // Wasm output can be an executable only + let libDir = target == "wasm" ? void 0 : destDir; + + const vsenv = process.platform === "win32" && target === "node-host" + + const doConfigure = (isAndroid, isJvm, isOhos, ...crossFiles) => { + verbose && console.log(`Configuring target ${chalk.bold(target)}\n`); + try { + const isNode = !isJvm && !target.startsWith("jsc-"); + meson.configure({ + builddir: "build-" + target, + prefix: path.resolve("../.runtime-prebuilt"), + wipe, + reconfigure, + verbose, + binDir, + libDir, + crossFiles, + dryRun, + vsenv, + options: { + "ndk_dir": isAndroid ? getNdkRoot() : null, + "jdk_dir": isJvm ? getJdkRoot() : null, + "oh_sk_log_to_file": isOhos && !!process.env.KOALAUI_OHOS_LOG_TO_FILE ? "true" : null, + "node:node_dir": isNode ? (nodeDir && relativeToSourceRoot(nodeDir)) : null, + "node:build_type": nodeBuildType, + "vm_kind": vmKind || undefined + } + }); + verbose && console.log(); + console.log(`Target ${chalk.bold(target)}: ${chalk.green("SUCCESS")}`); + } catch (err) { + console.log(err); + console.log(`Target ${chalk.bold(target)}: ${chalk.red("FAIL")}`); + } + } + + switch (target) { + case "wasm": + doConfigure(false, false, false, createWasmCrossFile()); + break; + case "node-host": + doConfigure(false, false, false); + break; + case "jsc-ios-arm64": + case "jsc-ios-x64": + doConfigure(false, false, false, createIosCrossFile(target)); + break; + case "node-macos-arm64": + case "node-macos-x64": + doConfigure(false, false, false, createMacosCrossFile(target)); + break; + case "node-windows-clang-x64": + doConfigure(false, false, false, createWindowsClangCrossFile("x64")); + break; + case "node-android-x64": + case "node-android-arm64": + doConfigure(true, false, false, createAndroidCrossFile(target.substring(5))); + break; + case "arkjs-ohos-arm32": + case "ark-ohos-arm64": + case "arkjs-ohos-arm64": + doConfigure(false, false, true, createOhosCrossFile(target)); + break; + case "jni-host": + doConfigure(false, true); + break; + default: + console.log(chalk.yellow("unsupported target '" + target + "'")); + } +} + +function createWasmCrossFile() { + let cf = CrossFile("wasm"); + let emcc = "emcc", empp = "em++", emar = "emar"; + if (platform == "win32") { + if (process.env.EMSDK) { + let emsdk = process.env.EMSDK; + console.log("EMSDK: " + chalk.green(emsdk)); + let prefix = path.join(emsdk, "upstream", "emscripten"); + emcc = path.join(prefix, emcc + ".bat"); + empp = path.join(prefix, empp + ".bat"); + emar = path.join(prefix, emar + ".bat"); + } else { + console.log("EMSDK: " + chalk.red("not found") + "\r\n" + + chalk.gray(" set environment variable EMSDK=")); + exit(1); + } + } + cf.section("binaries", { + c: emcc, + cpp: empp, + ar: emar, + }) + .section("host_machine", { + system: 'emscripten', + cpu_family: 'wasm32', + cpu: 'wasm32', + endian: 'little', + }) + .close(); + + return cf.path; +} + +function createWindowsClangCrossFile(arch) { + let cf = CrossFile("windows-clang-" + arch); + let cpu = arch === "x64" ? "x86_64" : "x86"; // TODO check + let xwinHome = requireEnv("WINCRT_HOME") + console.log(`Using CRT from ${chalk.bold(xwinHome)}`) + + let link_args = [ + `-L${xwinHome}/crt/lib/${cpu}`, + `-L${xwinHome}/sdk/lib/um/${cpu}`, + `-L${xwinHome}/sdk/lib/ucrt/${cpu}`, + ] + + let include_dirs = [ + `/I${xwinHome}/crt/include`, + `/I${xwinHome}/sdk/include/ucrt`, + `/I${xwinHome}/sdk/include/um`, + `/I${xwinHome}/sdk/include/shared`, + ] + + let llvmBin = (() => { + let llvmHome = process.env.LLVM_HOME; + if (!llvmHome) { + if (process.platform === "win32") { + llvmHome = path.join(process.env.ProgramFiles, "LLVM") + } else { + llvmHome = "/usr/lib/llvm-14" // Ubuntu + } + if (fs.existsSync(llvmHome)) { + console.log(`Using LLVM from ${chalk.bold(llvmHome)}`) + } else { + console.log(`Using LLVM from ${chalk.bold("PATH")}`) + llvmHome = null + } + } + if (llvmHome) { + return bin => path.join(llvmHome, 'bin', bin) + } else { + return bin => bin // search in PATH + } + })() + + cf.section("binaries", { + // Ninja must have /I flags for feature detection, so adding them here + c: [llvmBin("clang-cl"), ...include_dirs], + cpp: [llvmBin("clang-cl"), ...include_dirs], + ar: llvmBin("llvm-lib"), + }) + .section("built-in options", { + c_args: ['-Wno-unused-local-typedef'], + cpp_args: ['-Wno-unused-local-typedef'], + + c_link_args: [...link_args], + cpp_link_args: [...link_args], + }) + .section("host_machine", { + system: "windows", + cpu_family: cpu, + cpu: cpu, + endian: "little", + }) + .close(); + + return cf.path; +} + +function createAndroidCrossFile(target) { + let cf = CrossFile(target); + let ndkRoot = getNdkRoot() + let compilersPath = "" + let suffix = "" + if (platform == 'win32') { + compilersPath = path.join(ndkRoot, "toolchains", "llvm", "prebuilt", "windows-x86_64", "bin"); + suffix = ".cmd"; + } else + compilersPath = path.join(ndkRoot, "toolchains", "llvm", "prebuilt", platform + "-x86_64", "bin"); + compilersPath = path.resolve(compilersPath); + + let cpu = 'unknown' + if (target == 'android-arm64') + cpu = 'aarch64' + else if (target == 'android-x64') + cpu = 'x86_64' + else + throw "Unknown target " + target; + + cf.section("binaries", { + c: path.join(compilersPath, cpu + "-linux-android28-clang" + suffix), + cpp: path.join(compilersPath, cpu + "-linux-android28-clang++" + suffix), + ar: path.join(compilersPath, "llvm-ar"), + }) + .section("host_machine", { + system: 'android', + cpu_family: cpu, + cpu: cpu, + endian: 'little', + }) + .close(); + + return cf.path; +} + +function createOhosCrossFile(target) { + let cf = CrossFile(target, root); + let sdkNativePath = ohSdkInfo(OHCONF.sdkPath(), OH_SDK_COMPONENT.native).path + let compilersPath = "" + let suffix = "" + if (platform == 'win32') { + suffix = ".exe"; + } + compilersPath = path.join(sdkNativePath, "llvm", "bin"); + + let cflags = [ + '--sysroot=' + path.resolve(path.join(sdkNativePath, 'sysroot')) + ] + let cpu = 'unknown' + if (target == 'ark-ohos-arm64' || target == 'arkjs-ohos-arm64') { + cpu = 'aarch64' + cflags = [ + ...cflags, + '--target=aarch64-linux-ohos' + ] + } else if (target == 'arkjs-ohos-arm32') { + cpu = 'arm' + cflags = [ + ...cflags, + '--target=arm-linux-ohos' + ] + } else { + throw "Unknown target " + target; + } + + cf.section("binaries", { + c: path.join(compilersPath, "clang" + suffix), + c_ld: path.join(compilersPath, "ld.lld" + suffix), + cpp: path.join(compilersPath, "clang++" + suffix), + cpp_ld: path.join(compilersPath, "ld.lld" + suffix), + ar: path.join(compilersPath, "llvm-ar" + suffix), + }) + .section("built-in options", { + c_args: cflags, + c_link_args: cflags, + cpp_args: cflags, + cpp_link_args: cflags + }) + .section("host_machine", { + system: 'ohos', + cpu_family: cpu, + cpu: cpu, + endian: 'little', + }) + .close(); + + return cf.path; +} + +function createIosCrossFile(target) { + let cf = CrossFile(target); + + let cflags = [ + ] + let cpu = 'unknown' + if (target == 'jsc-ios-arm64') { + cpu = 'aarch64' + cflags = [ + ...cflags, + '-arch', 'arm64', + "-isysroot", "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk" + ] + } else if (target == 'jsc-ios-x64') { + cpu = 'x86_64' + cflags = [ + ...cflags, + '-arch', 'x86_64', + "-isysroot", '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk' + ] + } else { + throw "Unknown target " + target; + } + + cf.section("binaries", { + c: 'clang', + cpp: 'clang++', + objcpp: 'clang++', + ar: 'ar', + strip: 'strip' + }) + .section("built-in options", { + c_args: cflags, + c_link_args: cflags, + cpp_args: cflags, + cpp_link_args: cflags, + objcpp_args: cflags, + objcpp_link_args: cflags, + }) + .section("host_machine", { + system: 'ios', + cpu_family: cpu, + cpu: cpu, + endian: 'little', + }) + .close(); + + return cf.path; +} + +function createMacosCrossFile(target) { + let cf = CrossFile(target); + + let cflags = [ + ] + let cpu = 'unknown' + if (target == 'node-macos-arm64') { + cpu = 'aarch64' + cflags = [ + ...cflags, + '-arch', 'arm64', + ] + } else if (target == 'node-macos-x64') { + cpu = 'x86_64' + cflags = [ + ...cflags, + '-arch', 'x86_64', + ] + } else { + throw "Unknown target " + target; + } + + cf.section("binaries", { + c: 'clang', + cpp: 'clang++', + objcpp: 'clang++', + ar: 'ar', + strip: 'strip', + }) + .section("built-in options", { + c_args: cflags, + c_link_args: cflags, + cpp_args: cflags, + cpp_link_args: cflags, + objcpp_args: cflags, + objcpp_link_args: cflags, + }) + .section("host_machine", { + system: 'darwin', + cpu_family: cpu, + cpu: cpu, + endian: 'little', + }) + .close(); + + return cf.path; +} + +function usage() { + console.log(`USAGE: node configure.mjs [OPTION]... TARGET [TARGET]... + +TARGET = wasm | node-android-x64 | node-android-arm64 | jni-host | node-host | ark-ohos-arm64 | ark-ohos-arm32 | jsc-ios-arm64 | jsc-ios-x64 | node-macos-x64 | node-macos-arm64 compilation target + +OPTIONS: + -h, --help show this help and exit + -v, --verbose show Meson output + -n, --dry-run do not emit files and do not perform meson configuring + --wipe wipe build directory and reconfigure + --reconfigure reconfigure build directory (use if options were changed) + --clean remove build directory before configuring +`) +} diff --git a/arkoala/framework/native/scripts/utils.mjs b/arkoala/framework/native/scripts/utils.mjs new file mode 100644 index 0000000000000000000000000000000000000000..20d01aaf90951a78253d0c6ceaafeae96f6e66c4 --- /dev/null +++ b/arkoala/framework/native/scripts/utils.mjs @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from "fs" +import { exit } from "process" +import { spawnSync } from "child_process" +import chalk from "chalk" +import path from "path" +import url from "url" + +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); + +class Version { + constructor(version) { + let [major, minor, patch] = version.split(/\./).map(x => +x); + this.major = major; + this.minor = minor; + this.patch = patch; + } + + valueOf() { + return this.major * 1000 + this.minor * 1000 + this.patch; + } + + toString() { + return `${this.major}.${this.minor}.${this.patch}`; + } +} + +export function toIniValue(value) { + if (typeof value == "string") { + value = value.replace("'", "\\'"); + return `'${value}'`; + } else if (Array.isArray(value)) { + let res = '' + for (let i in value) { + res += toIniValue(value[i]) + (i < value.length - 1 ? ', ' : '') + } + return '[' + res + ']'; + } else { + return value.toString(); + } +} + + +export function CrossFile(target, root = path.join(__dirname, '..')) { + let filename = target + '.ini'; + let filepath = path.join(root, filename); + + let lines = [] + return { + section(name, values) { + lines.push(`\n[${name}]\n`); + if (values) { + for (let prop in values) { + if (values.hasOwnProperty(prop)) { + this.property(prop, values[prop]); + } + } + } + return this; + }, + property(name, value) { + value = toIniValue(value); + lines.push(`${name} = ${value}\n`); + return this; + }, + close() { + fs.writeFileSync(filepath, lines.join('')); + }, + get path() { + return filepath; + } + } +} + +class Meson { + #version; + + constructor(version) { + this.#version = new Version(version); + } + + get version() { + return this.#version; + } + + configure(options) { + let builddir = options.builddir; + let crossFiles = options.crossFiles || []; + let defs = options.options || {}; + let dryRun = options.dryRun || false; + + let args = ["setup", builddir]; + if (options.prefix) { + args.push("--prefix", options.prefix); + } + if (options.binDir) { + args.push("--bindir", options.binDir); + } + if (options.libDir) { + args.push("--libdir", options.libDir); + } + for (const file of crossFiles) { + args.push("--cross-file", file); + } + for (const def in defs) { + let value = defs[def]; + if (defs.hasOwnProperty(def) && value != null) { + args.push(`-D${def}=${value}`); + } + } + if (options.wipe) { + args.push("--wipe"); + } + if (options.reconfigure) { + args.push("--reconfigure"); + } + if (options.vsenv) { + // Force MSVC instead of clang + args.push("--vsenv"); + } + if (options.vm_kind) { + args.push(`-Dvm_kind=${options.vm_kind}`); + } + if (dryRun) { + console.log(`> meson ${args.join(' ')}`); + } else { + let stdio = options.verbose ? ['inherit', 'inherit', 'inherit'] : void 0; + let env = process.env + let meson = spawnSync("meson", args, { encoding: "utf8", stdio, env }); + if (meson.status != 0) { + throw new Error("failed to configure"); + } + } + } +} + +export function findMeson() { + try { + let version = spawnSync("meson", ["-v"], { encoding: "utf8" }).output.join('').trim(); + console.log("Meson: " + chalk.green(version)); + return new Meson(version); + } catch (err) { + console.log("Meson: " + chalk.red("NOT FOUND")); + exit(1); + } +} + +export function findNinja() { + try { + let version = spawnSync("ninja", ["--version"], { encoding: "utf8" }).output.join('').trim(); + console.log("Ninja: " + chalk.green(version)); + return { version: new Version(version) }; + } catch (err) { + console.log("Ninja: " + chalk.red("NOT FOUND")); + exit(1); + } +} + +export function findPython() { + try { + let version = spawnSync("python3", ["--version"], { encoding: "utf8" }).output.join('').trim(); + version = version.replace(/^Python\s*/i, ""); + console.log("Python: " + chalk.green(version)); + return { version: new Version(version) }; + } catch (err) { + console.log("Python: " + chalk.red("NOT FOUND")); + exit(1); + } +} + +export function requireEnv(name, description) { + let value = process.env[name]; + if (value) { + console.log(`${name}: ${chalk.green(value)}`); + return value; + } else { + console.log(name + ": " + chalk.red("not found") + "\r\n" + + chalk.gray(` set environment variable ${name}=<${description}>`)); + exit(1); + } +} + +export function relativeToSourceRoot(abspath) { + let sourceRoot = path.resolve(__dirname, ".."); + return path.relative(sourceRoot, abspath); +} + +export function sourceRoot() { + return path.resolve(__dirname, ".."); +} + +class CMake { + #version; + + constructor(version) { + this.#version = new Version(version); + } + + get version() { + return this.#version; + } + + configure(options) { + let builddir = options.builddir; + let srcdir = options.srcdir + let defs = options.defs; + let dryRun = options.dryRun || false; + + let args = ['-GNinja', `-S${srcdir}`, `-B${builddir}`]; + for (const def in defs) { + let value = defs[def]; + if (defs.hasOwnProperty(def) && value != null) { + args.push(`-D${def}=${value}`); + } + } + console.log(`> cmake ${args.join(' ')}`); + if (dryRun) { + console.log(`> cmake ${args.join(' ')}`); + } else { + let stdio = options.verbose ? ['inherit', 'inherit', 'inherit'] : void 0; + let cmake = spawnSync("cmake", args, { + encoding: "utf8", + stdio + }); + if (cmake.status != 0) { + throw new Error("failed to configure"); + } + } + } +} + +export function findCmake() { + try { + let version = spawnSync("cmake", ["--version"], { encoding: "utf8" }).output.join('').trim(); + console.log("CMake: " + chalk.green(version)); + return new CMake(version); + } catch (err) { + console.log("CMake: " + chalk.red("NOT FOUND")); + exit(1); + } +} diff --git a/arkoala/framework/package.json b/arkoala/framework/package.json index 69ac9ba1cee1da36f832283c8f57adb9830cf415..883f380a740046183660ee3f356249b23a58ac5c 100644 --- a/arkoala/framework/package.json +++ b/arkoala/framework/package.json @@ -32,9 +32,9 @@ "compile:native-jni-host": "npm run configure:native-jni-host && cd native && meson compile -C build-jni-host && meson install -C build-jni-host", "configure:native-arkjs-host": "cd native && meson setup -D vm_kind=arkjs build-arkjs-host", "compile:native-arkjs-host": "npm run configure:native-arkjs-host && cd native && meson compile -C build-arkjs-host && meson install -C build-arkjs-host", - "configure:native-arkjs-ohos-arm64": "npm run --prefix ../../../ohos-sdk download && cd native && node ../../../../skoala-bridges/scripts/configure.mjs arkjs-ohos-arm64 --reconfigure --vm_kind=arkjs", + "configure:native-arkjs-ohos-arm64": "npm run --prefix ../ohos-sdk download && cd native && node scripts/configure.mjs arkjs-ohos-arm64 --reconfigure --vm_kind=arkjs", "compile:native-arkjs-ohos-arm64": "npm run configure:native-arkjs-ohos-arm64 && cd native && meson compile -C build-arkjs-ohos-arm64 && meson install -C build-arkjs-ohos-arm64", - "configure:native-arkjs-ohos-arm32": "npm run --prefix ../../../ohos-sdk download && cd native && node ../../../../skoala-bridges/scripts/configure.mjs arkjs-ohos-arm32 --reconfigure --vm_kind=arkjs", + "configure:native-arkjs-ohos-arm32": "npm run --prefix ../ohos-sdk download && cd native && node scripts/configure.mjs arkjs-ohos-arm32 --reconfigure --vm_kind=arkjs", "compile:native-arkjs-ohos-arm32": "npm run configure:native-arkjs-ohos-arm32 && cd native && meson compile -C build-arkjs-ohos-arm32 && meson install -C build-arkjs-ohos-arm32", "configure:native-arkjs-ohos": "npm run configure:native-arkjs-ohos-arm64", "compile:native-arkjs-ohos": "tsc -b . && npm run compile:native-arkjs-ohos-arm64",