@@ -37,94 +37,272 @@ quickjs 主要有两个版本:
37371 )  git clone quickjs 并 checkout 到 v0.7.0 tag
38382 )  编写 xmake 脚本,见[ xmake quickjs-ng 交叉编译实践] ( https://blog.dang8080.cn/2024/11/17/09/ ) 
39393 )  编译生成跨平台动态库
40+ 4 )  将各个平台的动态库打包为 tar.gz 
4041
41422、新建 flutter ffi plugin project
4243
4344#android 
44- 在 ` build.gradle `  ` dependencies `  中添加 ` implementation('cn.humphreyd.flujs-android:quickjs:0.6.1.2') ` 
45- #iOS
45+ ~~ 在 ` build.gradle `  ` dependencies `  中添加 ` implementation('cn.humphreyd.flujs-android:quickjs:0.6.1.2') ` ~~ 
46+ >  上面是需要手动将 qjs 动态库打包为aar,然后发布到 mvn 仓库.详情见[ 安卓发布纯so库到maven] ( https://blog.dang8080.cn/2024/12/01/00/ ) 
47+ 
48+ >  相比较之下还是比较麻烦,所以直接使用 gradle 脚本下载 so 到 jniLibs 并打包
49+ 
50+ ``` groovy 
51+ def targetAbis = android.defaultConfig.ndk.abiFilters 
52+ def vers = Prop("flujs.qjs.qjs_version", "8.0.0") 
53+ def baseUrl = Prop("flujs.qjs.baseUrl", "https://gitee.com/flujs_project/flujs_libs_quickjs/releases/download/${vers}") 
54+ def forceDownload = Prop("flujs.qjs.force_download", "false").toBoolean() 
55+ 
56+ targetAbis.each { abi -> 
57+     println("[🌹] ${abi}") 
58+ 
59+     def jniLibsDir = file("$projectDir/src/main/jniLibs/${abi}") 
60+     def soFile = file("${jniLibsDir}/libqjs.so") 
61+ 
62+     if (forceDownload || !soFile.exists()) { 
63+         def downloadTaskName = "downloadQjs${abi.capitalize()}" 
64+         tasks.register(downloadTaskName, DownloadTask) { 
65+             group 'QuickJS' 
66+             arch = abi 
67+             sourceUrl = "${baseUrl}/qjs_android_${abi}.tar.gz" 
68+             destDir = file("${project.buildDir}/tmp/qjs/${abi}") 
69+         } 
70+ 
71+         def tarFile = file("${tasks.getByName(downloadTaskName).destDir}/qjs_android_${abi}.tar.gz") 
72+ 
73+         def extractTaskName = "extractQjs${abi.capitalize()}" 
74+         tasks.register(extractTaskName, Copy) { 
75+             group 'QuickJS' 
76+             dependsOn tasks.getByName(downloadTaskName) 
77+             from({ tarTree( 
78+                 tarFile.path.endsWith('.gz') ?  
79+                     resources.gzip(tarFile) :  
80+                     resources.bzip2(tarFile) 
81+             )}) 
82+             into "$projectDir/src/main/jniLibs/${abi}" 
83+             includeEmptyDirs = false 
84+         } 
85+         preBuild.dependsOn tasks.getByName(extractTaskName) 
86+     } else { 
87+         println("[🌹] libqjs.so already exists for ${abi}, skipping download and extraction. use flutter clean, and rerun  if you insist on download and extraction. or Set flujs.qjs.force_download=true to force download.") 
88+     } 
89+ } 
90+ 
91+ class DownloadTask extends DefaultTask { 
92+     @Input String arch 
93+     @Input String sourceUrl 
94+     @Input boolean overwrite = false 
95+     @OutputDirectory File destDir 
96+ 
97+     @TaskAction 
98+     void download() { 
99+         destDir.mkdirs() 
100+         def outputFile = new File(destDir, "qjs_android_${arch}.tar.gz") 
101+         println("[🌹] DownloadTask ✈️ arch=${arch},sourceUrl=${sourceUrl},overwrite=${overwrite},outputFile=${outputFile}") 
102+         if (!outputFile.exists() || overwrite) { 
103+             URL url = new URL(sourceUrl) 
104+             HttpURLConnection connection = (HttpURLConnection) url.openConnection() 
105+             connection.setInstanceFollowRedirects(true) // 启用自动重定向 
106+             connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") // 添加 User-Agent 
107+             connection.connect() 
108+ 
109+             if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { 
110+                 connection.getInputStream().withStream { input -> 
111+                     outputFile.withOutputStream { it << input } 
112+                 } 
113+             } else { 
114+                 throw new IOException("Failed to download file: HTTP ${connection.getResponseCode()}, ${connection.getURL().toString()}") 
115+             } 
116+         } 
117+     } 
118+ } 
119+ 
120+ def Prop(String name, String defaultValue = null) { 
121+     if (project.hasProperty(name)) { 
122+         return project.findProperty(name) 
123+     } else if (System.getenv(name) != null) { 
124+         return System.getenv(name) 
125+     } else { 
126+         return defaultValue 
127+     } 
128+ } 
129+ ``` 
130+ 
46131``` ruby 
47- archs =  ENV [' ARCHS'  ] ||  ' arm64' 
48- spec.subspec ' Framework'   do  |framework | 
49- framework.vendored_frameworks =  " Frameworks/qjs.xcframework/ios-#{ archs } /qjs.framework" 
132+ require  " open-uri" 
133+ require  " pathname" 
134+ require  " fileutils" 
135+ require  " digest" 
136+ 
137+ QJS_VERSION  =  ENV [" QJS_VERSION"  ] ||  " 0.8.0"  .freeze
138+ QJS_BASEURL  =  ENV [" QJS_BASEURL"  ] ||  " https://gitee.com/flujs_project/flujs_libs_quickjs/releases/download/#{ QJS_VERSION } " 
139+ 
140+ class  FlujsUtil 
141+   attr_reader  :framework_path 
142+ 
143+   def  initialize (platform )
144+     @framework_path  =  " " 
145+     @force_download  =  ENV [" FORCE_DOWNLOAD"  ] ==  " true" 
146+ 
147+     framework_lib =  " Frameworks/libqjs.dylib" 
148+     cache_lib =  " .cache/libqjs.dylib" 
149+ 
150+     if  File .exist?(framework_lib) &&  ! @force_download 
151+       puts  " [darwin] libqjs.dylib already exists in Frameworks/, skipping download and extraction.use flutter clean, and rerun  if you insist on download and extraction. or set FORCE_DOWNLOAD=true" 
152+       @framework_path  =  " Frameworks/" 
153+       return 
154+     elsif  File .exist?(cache_lib)
155+       puts  " [darwin] libqjs.dylib found in .cache/, execute extraction." 
156+       @framework_path  =  " Frameworks/" 
157+       FileUtils .mkdir_p(@framework_path )
158+       FileUtils .cp(cache_lib, @framework_path )
159+       return 
160+     end 
161+ 
162+     FileUtils .mkdir_p(' .cache'  )
163+     puts  " [darwin] #{ platform }  libqjs.dylib not found, downloading and extracting..." 
164+     _download (platform)
165+   end 
166+ 
167+   def  _download (platform )
168+     #  make destdir
169+     dest_dir =  " .cache/" 
170+     tar_name =  ENV [" QJS_TAR_NAME"  ] ||  " qjs_#{ platform } .tar.gz" 
171+     tar_path =  " #{ dest_dir } /#{ tar_name } " 
172+     #  fetch from remote
173+     _fetch (" #{ QJS_BASEURL } /#{ tar_name } "  , tar_path)
174+     #  unzip
175+     _unzip (tar_path, dest_dir)
176+     @framework_path  =  " Frameworks/"   
177+     FileUtils .mkdir_p(" #{ @framework_path } "  )
178+     FileUtils .cp(" #{ dest_dir } /libqjs.dylib"  , " #{ @framework_path } "  )
179+   end 
180+ 
181+   def  _unzip (file , destination )
182+     system (" tar -xzf #{ file }  -C #{ destination } "  )
183+   end 
184+ 
185+   def  _fetch (link , dest )
186+     begin 
187+       URI .open (link) do  |file |
188+         File .open (dest, " wb"  ) do  |output |
189+           output.write(file.read)
190+         end 
191+       end 
192+     puts  " [_fetch] succeed #{ dest } " 
193+     rescue  StandardError  => e
194+       puts  " [_fetch] failed: #{ e.message } , #{ link } " 
195+     end 
196+   end 
197+ 
50198end 
51199``` 
200+ #iOS
201+ ``` ruby 
202+ flujsutil =  FlujsUtil .new (' iphoneos'  )
203+ spec.vendored_libraries =  " Frameworks/libqjs.dylib" 
204+ 
205+ spec.preserve_paths =  " Frameworks/libqjs.dylib" 
206+ ``` 
52207#macOS
53208``` ruby 
54- archs =  ENV [' ARCHS'  ] ||  ' arm64' 
55- spec.subspec ' Framework'   do  |framework |
56- #  framework.vendored_frameworks = 'Frameworks/**/*.framework'
57- framework.vendored_frameworks =  " Frameworks/qjs.xcframework/macos-#{ archs } /qjs.framework" 
58- end 
209+ flujsutil =  FlujsUtil .new (' macosx'  )
210+ spec.vendored_libraries =  " Frameworks/libqjs.dylib" 
211+ 
212+ spec.preserve_paths =  " Frameworks/libqjs.dylib" 
59213``` 
60214#linux/windows
61215linux 和 mac 都使用 cmake 编译,所以这里共用一套逻辑.
62216
63217 ` script/artifact.cmake `   
64218 ``` cmake 
65-  set(BASEURL_GITEE "https://gitee.com/flujs/flujs_libs_quickjs/releases/download/") 
66- set(BASEURL_GITHUB "https://gitee.com/flujs/flujs_libs_quickjs/releases/download/") 
67- set(QJS_VERSION "v0.6.1.1") 
68- set(QJS_LINUX_ARCHIVE_arm64_MD5 "fde244492cd7c525cad267a03955188b") 
69- set(QJS_LINUX_ARCHIVE_x86_64_MD5 "b83e80e0007be64e81593f6fd5647c30") 
70- set(QJS_MINGW_ARCHIVE_x86_64_MD5 "74150ccfa52c43ca462dd6cd8b891bfd") 
71- 
72- if(CMAKE_HOST_SYSTEM_NAME MATCHES "Linux") 
73-   if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "x86_64") 
74-     set(QJS_LIB_NAME "qjs_linux_x86_64.tar.gz") 
75-     set(QJS_ARCHIVE_MD5 "${QJS_LINUX_ARCHIVE_x86_64_MD5}") 
76-   elseif (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64") 
77-     set(QJS_LIB_NAME "qjs_linux_arm64.tar.gz") 
78-     set(QJS_ARCHIVE_MD5 "${QJS_LINUX_ARCHIVE_arm64_MD5}") 
79-   else() 
80-     message(FATAL_ERROR "Unknown CPU!") 
81-   endif() 
82- elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "Windows") 
83-   if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "X86" OR ${CMAKE_HOST_SYSTEM_PROCESSOR} STREQUAL "AMD64" OR ${CMAKE_HOST_SYSTEM_PROCESSOR} STREQUAL "IA64") 
84-     set(QJS_LIB_NAME "qjs_mingw_x86_64.zip") 
85-     set(QJS_ARCHIVE_MD5 "${QJS_MINGW_ARCHIVE_x86_64_MD5}") 
86-   elseif (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "ARM64") 
87-     set(CPU "arm64") 
88-     message(FATAL_ERROR "Unknown CPU!") 
89-   else() 
90-     message(FATAL_ERROR "Unknown CPU!") 
219+  # Set BASEURL from environment variable or use default 
220+ if(DEFINED ENV{BASEURL}) 
221+   set(BASEURL "$ENV{BASEURL}") 
222+ else() 
223+   set(BASEURL "https://gitee.com/flujs_project/flujs_libs_quickjs/releases/download") 
224+ endif() 
225+ 
226+ # Set QJS_VERSION from environment variable or use default 
227+ if(DEFINED ENV{QJS_VERSION}) 
228+   set(QJS_VERSION "$ENV{QJS_VERSION}") 
229+ else() 
230+   set(QJS_VERSION "0.8.0") 
231+ endif() 
232+ 
233+ if(DEFINED ENV{QJS_TAR_NAME}) 
234+   set(QJS_TAR_NAME "$ENV{QJS_TAR_NAME}") 
235+ elseif(NOT DEFINED QJS_TAR_NAME) 
236+   if(CMAKE_HOST_SYSTEM_NAME MATCHES "Linux") 
237+     if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "x86_64") 
238+       set(QJS_TAR_NAME "qjs_linux_x86_64.tar.gz") 
239+     elseif (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64") 
240+       set(QJS_TAR_NAME "qjs_linux_arm64.tar.gz") 
241+     else() 
242+       message(FATAL_ERROR "Unknown CPU!") 
243+     endif() 
244+   elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "Windows") 
245+     if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "X86" OR ${CMAKE_HOST_SYSTEM_PROCESSOR} STREQUAL "AMD64" OR ${CMAKE_HOST_SYSTEM_PROCESSOR} STREQUAL "IA64") 
246+       set(QJS_TAR_NAME "qjs_mingw_x86_64.zip") 
247+     elseif (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "ARM64") 
248+       set(CPU "arm64") 
249+       message(FATAL_ERROR "Unknown CPU!") 
250+     else() 
251+       message(FATAL_ERROR "Unknown CPU!") 
252+     endif() 
91253  endif() 
92254endif() 
93255
256+ if(DEFINED ENV{FORCE_DOWNLOAD}) 
257+   set(FORCE_DOWNLOAD "$ENV{FORCE_DOWNLOAD}") 
258+ else() 
259+   set(FORCE_DOWNLOAD FALSE) 
260+ endif() 
261+ 
94262# download quickjs and set QJS_LIBRARY 
95263function(prepareQJS) 
96-   set(QJS_LIB_CACHE "${CMAKE_BINARY_DIR}/${QJS_LIB_NAME}") 
97-   set(QJS_LIB_PATH "${CMAKE_BINARY_DIR}/release") 
98-   set(URL "${BASEURL_GITEE}/${QJS_VERSION}/${QJS_LIB_NAME}") 
99-   downloadVerify("${URL}" ${QJS_LIB_CACHE} "${QJS_ARCHIVE_MD5}") 
264+   set(QJS_TAR_PATH "${CMAKE_BINARY_DIR}/${QJS_TAR_NAME}") 
265+ 
266+   if(CMAKE_HOST_SYSTEM_NAME MATCHES "Linux") 
267+     set(QJS_LIBRARY "${CMAKE_BINARY_DIR}/libqjs.so") # cannot use PARENT_SCOPE, it will not check the existence of QJS_LIBRARY 
268+   elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "Windows") 
269+     set(QJS_LIBRARY "${CMAKE_BINARY_DIR}/release/lib/libqjs.dll") 
270+   endif() 
271+ 
272+   message(STATUS "[prepareQJS] QJS_LIBRARY = ${QJS_LIBRARY} ${FORCE_DOWNLOAD}") 
273+   if(EXISTS "${QJS_LIBRARY}" AND NOT FORCE_DOWNLOAD) 
274+     message(STATUS "[prepareQJS] ${QJS_LIBRARY} already exists, skipping download and extraction. use flutter clean, and rerun  if you insist on download and extraction. or set FORCE_DOWNLOAD=true") 
275+     set(QJS_LIBRARY "${QJS_LIBRARY}" PARENT_SCOPE) 
276+     return() 
277+   endif() 
278+ 
279+   message(STATUS "[prepareQJS] ${QJS_LIBRARY_PATH} not found, downloading and extracting...") 
280+   set(URL "${BASEURL}/${QJS_VERSION}/${QJS_TAR_NAME}") 
281+   downloadVerify("${URL}" ${QJS_TAR_PATH}) 
100282  if(CMAKE_HOST_SYSTEM_NAME MATCHES "Linux") 
101283    add_custom_target( 
102284      "QJS_EXTRACT" 
103285      ALL 
104-       COMMAND "${CMAKE_COMMAND}" -E tar xz "${QJS_LIB_CACHE }" 
286+       COMMAND "${CMAKE_COMMAND}" -E tar xz "${QJS_TAR_PATH }" 
105287      WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" 
106288      COMMENT "Extracting QJS library" 
107289    ) 
108-     set(QJS_LIBRARY "${QJS_LIB_PATH}/lib /libqjs.so" PARENT_SCOPE) 
290+     set(QJS_LIBRARY "${CMAKE_BINARY_DIR} /libqjs.so" PARENT_SCOPE) 
109291  elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "Windows") 
110-     execute_process(COMMAND powershell -Command "Expand-Archive -Path '${QJS_LIB_CACHE }' -DestinationPath '${CMAKE_BINARY_DIR}' -Force" RESULT_VARIABLE extractResult) 
292+     execute_process(COMMAND powershell -Command "Expand-Archive -Path '${QJS_TAR_PATH }' -DestinationPath '${CMAKE_BINARY_DIR}' -Force" RESULT_VARIABLE extractResult) 
111293    if(NOT extractResult EQUAL 0) 
112-         message(FATAL_ERROR "Failed to extract ZIP file: ${QJS_LIB_CACHE }") 
294+         message(FATAL_ERROR "Failed to extract ZIP file: ${QJS_TAR_PATH }") 
113295    endif() 
114-     set(QJS_LIBRARY "${QJS_LIB_PATH} /lib/libqjs.dll" PARENT_SCOPE) 
296+     set(QJS_LIBRARY "${CMAKE_BINARY_DIR}/release /lib/libqjs.dll" PARENT_SCOPE) 
115297  endif() 
116298endfunction(prepareQJS) 
117299
118- # download url resource to locationForArchive and check md5  
119- function(downloadVerify url locationForArchive md5  ) 
300+ # download url resource to locationForArchive 
301+ function(downloadVerify url locationForArchive ) 
120302  message(STATUS "[downloadVerify] info = ${url}") 
121-   if (EXISTS "${locationForArchive}") 
122-     file(MD5 "${locationForArchive}" ARCHIVE_MD5) 
123-     message(STATUS "[downloadVerify] md5 = ${ARCHIVE_MD5}") 
124-     if(NOT md5 STREQUAL ARCHIVE_MD5) 
125-       file(REMOVE "${locationForArchive}") 
126-       message(WARN "[downloadVerify] MD5 mismatch. ${locationForArchive} deleted!") 
127-     endif() 
303+   if (EXISTS "${locationForArchive}" AND NOT FORCE_DOWNLOAD) 
304+     message(STATUS "[downloadVerify] ${locationForArchive} exists!.") 
305+     return() 
128306  endif() 
129307
130308  if(NOT EXISTS "${locationForArchive}") 
@@ -138,14 +316,6 @@ function(downloadVerify url locationForArchive md5 )
138316    if(NOT status_code EQUAL 0) 
139317      message(FATAL_ERROR "Error downloading ${download_log}") 
140318    endif() 
141-     if(NOT md5) 
142-       file(MD5 "${locationForArchive}" ARCHIVE_MD5) 
143-       if(md5 STREQUAL ARCHIVE_MD5) 
144-         message(STATUS "[downloadVerify] ${locationForArchive} Verification successful.") 
145-       else() 
146-         message(FATAL_ERROR_ERROR "[downloadVerify] ${locationForArchive} Integrity check failed, please try to rebuild project again.") 
147-       endif() 
148-     endif(NOT md5) 
149319  endif() 
150320  message(STATUS "[downloadVerify] succeed! ${locationForArchive}") 
151321endfunction(downloadVerify url md5 locationForArchive) 
@@ -167,22 +337,16 @@ function(checkDirectoryEmpty dir result)
167337endfunction(checkDirectoryEmpty) 
168338``` 
169339
170- 这里首先需要将 linux/windows 的库上传到可访问的地址,然后通过 cmake 下载.
171- 核心点是设置 ` set(QJS_LIBRARY "${QJS_LIB_PATH}/lib/libqjs.so" PARENT_SCOPE) `  动态库的地址
172- 
173- 然后在 ` qjs/src/CMakeLists.txt `  中添加 ` QJS `  target(也可以在上面的 cmake 脚本中设置)
340+ >  因为 linux/windows 公用一套逻辑,所以将 QJS 设置为 IMPORTED 对象,并拆分为共用的 CMakeLists.txt, 然后在各自 module 中 add_subdirectory 即可
174341``` cmake 
175342cmake_minimum_required(VERSION 3.10) 
176343project(${PROJECT_NAME}  VERSION 0.0.1 LANGUAGES C) 
177344
178- set(PLUGIN_NAME "flujs") # for current scope 
179- set(PLUGIN_NAME ${PLUGIN_NAME} PARENT_SCOPE) # for parent scope 
180- 
181345set(QJS "quickjs") # for current scope 
182346set(QJS ${QJS} PARENT_SCOPE) # for parent scope 
183347
184348# add qjs prebuilt library 
185- include("${CMAKE_CURRENT_SOURCE_DIR}/../../ script/artifact.cmake") 
349+ include("${CMAKE_CURRENT_SOURCE_DIR}/../script/artifact.cmake") 
186350prepareQJS() 
187351add_library(${QJS} SHARED IMPORTED GLOBAL) 
188352set_target_properties(${QJS} PROPERTIES IMPORTED_LOCATION "${QJS_LIBRARY}") 
@@ -195,9 +359,10 @@ add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../qjs/src" "${CMAKE_CURRENT_BINAR
195359# List of absolute paths to libraries that should be bundled with the plugin. 
196360# This list could contain prebuilt libraries, or libraries created by an 
197361# external build triggered from this build file. 
198- # !!! must be called in this scope,otherwise qjs lib will not be bundled into package. 
199- set("${PLUGIN_NAME}_bundled_libraries"  $<TARGET_FILE:${QJS}> PARENT_SCOPE) 
362+ set("flujs_qjs_bundled_libraries" $<TARGET_FILE:${QJS}> PARENT_SCOPE) 
200363``` 
364+ >  注意: linux/windows cmake 中必须要设置 ` set("flujs_qjs_bundled_libraries" $<TARGET_FILE:${QJS}> PARENT_SCOPE) `   ` $<TARGET_FILE:${QJS}> `  就是 ` libqjs.dll/libqjs.so `  的位置,这样打包时 cmake 才会通过 install 命令将动态库复制到产物目录
201365
202- ` set("${PLUGIN_NAME}_bundled_libraries"  $<TARGET_FILE:${QJS}> PARENT_SCOPE) `  必须设置,否则动态库无法打包到产物里.
203- 
366+ >  [ flujs] ( https://gitee.com/flujs_project/flujs ) 
367+ >  [ flujs_libs_quickjs] ( https://gitee.com/flujs_project/flujs_libs_quickjs ) 
368+ >  [ flujs_android_quickjs] ( https://gitee.com/flujs_project/flujs_android_quickjs ) 
0 commit comments