编程语言兼容性
本文要讨论的兼容性是指上层应用软件对不同架构芯片及各类板卡硬件的兼容性。本文将聚焦于各类编程语言的兼容性都是怎么引入的,以便于为后续对拓展平台软件生态需要做哪些工作提供一些指导。
什么样的软件具有好的兼容性?
对于一款软件,如果能够提供不同平台的二进制、或者仅提供一个二进制就能够在不同平台上跑,那么说明这款软件的兼容性很完备。但实际上受限于编程语言本身的能力、性能等约束,想达到这种目标往往比较困难。
什么时候会出现兼容性问题?
- 当代码中硬编码了汇编语言
- 当代码中调用了so库,而该so库还被打包到最终的可运行包里
- 当代码中调用了so库,且该so库是由软件源码包中的c/c++等待编译文件编译出来的
以上这些情况均会导致软件与平台相关,进而导致软件必须提供不同平台编译版本才能满足软件在指定平台的可运行。
只要软件和平台相关,那么当指定平台缺少该软件的预编译包时,整个上层应用在该平台上的安装就会失败,从而带来不好的兼容性体验。
那么,如何评价一款软件在给定平台的兼容性情况?
首先需要明确的是,兼容性达成情况是一个bool类型。软件是否兼容与软件有多么不兼容,后者会影响到你去做迁移适配的动作吗?难道软件严重不兼容就不做平台适配了?并不是的,对于给定的平台,我们想让软件跑起来,无论它具有多少不兼容代码,都得一一解决。唯有此,才能达成软件运行的目的。所以,评价软件兼容性是一个非黑即白的结论。
如何识别软件是否需要做兼容性?
- 分析源码
- 直接在指定平台上编译运行
针对第一点,以下挑选了top流行语言来列举各种可能存在的硬件兼容性情况。
javascript
代码调用so
javascript调用so,使用node-ffi包:node-ffi/node-ffi: Node.js Foreign Function Interface (github.com)
1 | var ffi = require('ffi'); |
兼容性识别关键点:检查编译后的包是否包含so。
代码包含c/c++
有些javascript包中带有其他编程语言,比如node-ffi:https://api.github.com/repos/node-ffi/node-ffi/languages
1 | { |
可以看到,js包里面具有c/c++编译型语言,这种包自然会导致平台兼容性问题。
兼容性识别关键点:检查源码中是否包含编译型语言。
assembly技术
javascript运行会被浏览器翻译为assembly中间机器语言,这里的机器语言并非CPU相关,真正运行还要再转义一次。
这篇文章以一种自定义的JS-ASM来描绘java assembly是怎么工作的:JavaScript Assembler Language Specification (JS-ASM) - CodeProject
npm包
npm包背景知识
npm包制作和发布参考:How to Create and Publish an NPM Package – a Step-by-Step Guide (freecodecamp.org)
制作npm包有两种方式:
1 | # 方式1:link形式 |
npm包安装过程解读:How npm install Works Internally? - DEV Community
npm包工作的核心文件是package.json,该文件内定义了npm如何对该包进行操作,包括install、serve、test等。
如果没有特别说明,包内index.js为npm包的入口(entrypoint),新包发布所能使用的功能函数统一从这里出口(export)
了解这些背景知识后,我们看下有哪些情况npm包在安装时会触发兼容性问题
案例1:Npm install 2.8.2 fails on ARM64, asks to upgrade to 2.5.1 or later - core - Meteor.js forums
该案例里npm包内具有平台检测硬编码,之所以做这类平台限制,是因为该包依赖了mongodb,而mongodb是平台相关,为适配arm,做了如下修复:
Support ARM on Linux by aquinoit · Pull Request #12160 · meteor/meteor (github.com)
案例2:Unable to npm install
on M1 · Issue #668 · twilio/twilio-video-app-react (github.com)
该报错是因为npm安装过程中apt install chromium-browser命令执行失败导致,最后通过手动补全chromium依赖解决。
结合以上案例可以得出,npm包在安装过程中体现的兼容性问题,取决于npm包安装脚本是否有外界依赖引入。
兼容性识别关键点:直接跑一遍npm install来检验
python
代码调用c
这篇文章介绍了各种python调用c的方法,并给出了优劣势对比:Python Bindings: Calling C or C++ From Python – Real Python
ctypes内置包会处理所有跟c、so的交互:
1 | from ctypes import * |
兼容性识别关键点:识别源码中是否带有so名称,编译好的包是否具有so。
wheel包
wheel包背景知识
和npm包一样,wheel包也有一个类似package.json的核心文件来承载包的元数据信息,该核心文件有一个演进史:
What’s the difference between setup.py and setup.cfg in python projects - Stack Overflow
最开始是setup.py,然后是setup.cfg,再到现在则为pyproject.toml
打包wheel的命令也有很多种,如build、wheel工具等,示例参考:How to Create Python Packages | Towards Data Science
wheel包安装兼容性问题
安装过程触发本地编译,通过setup.py:
此时如果setup.py过程对平台有依赖,就会报错。
同npm包,wheel包安装失败的原因仅有可能是引入了外部依赖。
兼容性识别关键点:直接跑一遍pip install来检验
java
java调用so的方式是通过JNI或JNA。
jni
JNI用法:Call c function from Java - Stack Overflow
1 | class HelloWorld { |
这里还包含怎么编译so库出来的问题,详细见上述链接。
兼容性识别关键点:native、System.loadLibrary关键字
jna
JNA方法:jna/GettingStarted.md at master · java-native-access/jna · GitHub
1 | package com.sun.jna.examples; |
兼容性识别关键点:是否引用了jna依赖。
其他jvm系语言
其他基于java虚拟机开发的语言调用so方法和上述类似,均通过java官方的JNI或JNA方法:
- scala:scala c integration - Stack Overflow
- kotlin:Calling C function from Kotlin using JNI - Stack Overflow
- groovy:java - Call C API from Groovy - Stack Overflow
- clojure:Calling C++ from clojure - Stack Overflow
perl
perl语言的特点跟python类似,也是一种脚本解释语言。
代码调用c
perl调用c的方法如下:How do I call a C function from a Perl program? - Stack Overflow
1 | use Inline C => <<'__END_OF_C__'; |
ruby
ruby也是一种解释型语言。
代码调用c
ruby调用c的方式是通过rb_define_method
方法:How to create a Ruby extension in C in under 5 minutes (rubyinside.com)
1 | // Include the Ruby headers and goodies |
代码调用so
ruby调用so的方式通过借助ffi:Calling C shared libraries with ruby FFI (jertype.com)
1 | require 'ffi' |
lua
lua也是一种解释型语言。
代码调用c
lua调用c的方式是通过lua_CFunction
方法:How would I make a C-function that I can call from Lua? - Stack Overflow
代码调用so
lua调用so的方式是通过ffi:ffi - How to call a function in shared library using Lua - Stack Overflow
1 | local lib = ffi.load('some.dll') |
php
php想要调用c或so的话,就不是在php内声明c函数签名的方式了,而是通过编写php extension:Is it possible to call C code from php - Stack Overflow
c/c++/fortran/rust/go
这些语言就都是编译型语言了,编译型语言一定是平台相关,意味着不存在出一个包就能达成跨平台的效果,故此类软件一定要做迁移。
对于编译型语言,兼容性主要体现在编译过程,如:
- 按照默认的readme、官方文档编译步骤,编译过程是否顺畅?
- 是否存在某个平台相关选项导致编译失败?