开发者

kotlin开发cli工具小技巧详解

开发者 https://www.devze.com 2022-12-02 10:17 出处:网络 作者: 究极逮虾户
目录脚手架开搞开发调试jcommanderMain 函数声明压缩模板放飞自我生成最终产物结尾脚手架
目录
  • 脚手架
  • 开搞
    • 开发调试
      • jcommander
    • Main 函数声明
      • 压缩模板
        • 放飞自我
          • 生成最终产物
          • 结尾

            脚手架

            脚手架是为了保证各施工过程顺利进行而搭设的工作平台

            而在程序开发过程中,每个工程或者说公司也都需要一个脚手架工具。通过脚手架命令行的形式简化开发流程,避免发生一些人为的相对低级的问题,所以这个也就是为什么叫做脚手架的原因吧。

            而由于每个公司的代码规范都不同,一般情况下会主动让开发同学进行工程方面的cv操作,就是成本高并且容易出错。这也就是为什么我们打算写一些这样的工具的原因。

            在一般情况下,更多的程序猿会选择用python去写,因为脚本语言的灵活性,但是对于一个辣鸡安卓来说会增加额外的学习成本,所以这就取决于有没有天赋了,能不能对一门陌生的语言快速上手了。

            这次文章会介绍的是用kotlin去构建一个二进制文件,通过这个来完成脚手架cli工具的建设。

            开搞

            demo 工程地址 TheNext

            一开始的启发在于有时候使用一些第三方工具的时候会提供一个jar包,然后只要输入Java -jar xxx.jar就可以使用这个jar包中的Main函数了。

            因为是一个jar包,所以里面的内容肯定也都是用jvm内的几种语言来进行编写的,那么这就让我们这种老年选手看到了一丝丝的希望。

            开发调试

            先建立了一个java工程,然后构建了一个main函数,之后开始进行代码编写。但是如果每次都需要先打包之后在通过java -jar来执行的话非常不便利开发并且djsebug。而且模拟入参也灰常的恶心,你也知道的程序猿都是懒人吗。

            所以我们就借用了unittest的能力,对于入参进行mock进行简单的调试功能了。

            参考地址 github.com/Leifzhang/T…

            class Sample {
                @Test
                fun help() {
                    Next.main(
                        arrayOf(
                            "--help"
                        )
                    )
                }
                @Test
                fun testandroidModule() {
                    val file = File("")
                    val moduleName编程 = "strike-freedom"
                    val groupName = "com.kronos.common"
                    Next.main(
                        arrayOf(
                            "module", "android",
                            "-file", file.absolutePath,
                            "-name", moduleName,
                            "-group", groupName
                        )
                    )
                }
                @Test
                fun testAndroidApplication() {
                    val file = File("../app/")  
                    val projectName = "freedom"
                    Next.main(
                        arrayOf(
                            "project", "android",
                            "-name", projectName,
                            "-file", file.absolutePath
                        )
                    )
                }
            }
            

            此处我们将Main函数通过unittest来进行模拟,这样就可以方便我们在开发阶段快速调试脚手架的能力了。

            每个方法块都可以认为是一个运行的入口,通过这个来模拟出程序所需要的入参。从而一边完成了测试代码的编写,一边完成了调试入口。

            jcommander

            这是一个让我们可以更像模像样的写一个cli的入参解析工具,即使参数顺序是错乱的,我们仍然能解析出我们想要的数据结构,让我们的工程看起来更正规一点。而且这个库也被很多开源项目所使用,基本算的上是千锤百炼了,比如美团的walle

            jcommander值得你一个star的

            @Parameters(commandDescription = "args 参数")
            class CommandEntity {
                @Parameter(
                    names = ["-file", "-f"],
                    required = true,
                    converter = FileConverter::class,
                    description = "生成目标文件路径"
                )
                lateinit var file: File
                @Parameter(
                    names = ["-name"], required = true,
                    description = "文件名"
                )
                lateinit var name: String
                @Parameter(names = ["-group", "-bundle", "-g", "-b"], description = "唯一标识符")
                var group: String? = null
            }
            
            override fun handle(args: Array<String>) {
             val commandEntity = CommandEntity()
             JCommander.newBuilder().addObject(commandEntity).build().parse(*args)
            }
            

            实例demo如上,我也是参考了官方demo写的。通过JCommander将args解析成对应的数据实体结构。

            Main 函数声明

            我们要在build.gradle内的jar的task中,声明当前jar的main函数,作为命令行工具的入口。否则打出来的jar包就会报没有main函数的异常。

            jar {
                exclude("**/module-info.class")
                /* from {
                     configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
                 }*/
                manifest {
                    attributes 'Main-Class': 'com.kronos.mebium.Next'
                }
            }
            

            其中from的含义就是将一个jar包把所有的依赖都打到一起,从而形成一个fatjar,而后续因为使用了gradle提供的application插件,所以这行被我注释了。

            压缩模板

            我们这个脚手架最核心的就是把一部分工程模板压缩成一个zip资源文件,打包带入jar产物中。然后呢我这个人又比较懒,希望每次执行打包的时候都进行一次模板的压缩替换,所以这里我通过一部分gradle task来进行执行了。

            abstract class ZipTask extends DefaultTask {
                @InputDirectory
                Provider<File> librar开发者_JAVA开发y = project.objects.property(File)
                @OutputFile
                Provider<File> outputFile = project.objects.property(File)
                @TaskAction
                def doAction() {
                    def outputFile = outputFile.get()
                    createFileSafety(outputFile)
                    compress(library.get(), outputFile)
                javascript}
                static File compress(final File srcDir, final File zipFile) {
                    ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))
                    srcDir.eachFileRecurse({
                        zos.putNextEntry(new ZipEntry(it.path - srcDir.path + (it.directory ? "/" : "")))
                        if (it.file) {
                            zos << it.bytes
                        }
                        zos.closeEntry()
                    })
                    zos.close()
                    return zipFile
                }
                private static File createFileSafety(File file) {
                    if (file.exists()) {
                        file.delete()
                    }
                    if (!file.getParentFile().exists()) {
                        file.getParentFile().mkdirs()
                    }
                    return file
                }
            }
            

            首先定义出一个task,然后定义好输入输出,输入的是一个文件夹,输出的则是一个zip的压缩文件,输入输出的地址由外部来声明。

            def moduleTask = project.tasks.register("zipAndroidLib", ZipTask.class) {
                it.library.set(file("../library"))
                it.outputFile.set(file("./src/main/resources/zip/android/android.zip"))
            }
            def projectTask = project.tasks.register("zipAndroidProject", ZipTask.class) {
                it.library.set(file("../project"))
                it.outputFile.set(file("./src/main/resources/zip/android/project.zip"))
            }
            afterEvaLuate {
                project.tasks.findByName("compileJava").dependsOn(moduleTask)
                project.tasks.findByName("compileJava").dependsOn(projectTask)
            }
            

            然后直接声明处两个task,之后把compileJava依赖到这两个task上去,这样就可以保证每次compileJava,这两个task都会被执行到了。编译缓存我就不说了,大家自行领悟吧。

            java resource 读取方式  javaClass.classLoader.getResourceAsStream(name) 就可以了。

            放飞自我

            接下来我们就可以在命令行工具内放飞自我,开始很简单的通过unittest来进行代码的编写和调试了。

            我们就可以通过自己熟悉的kotlin或者java来编写一个简单的cli工具,从而来进一步的做到基于工程定制化的一些方便的脚手架工具了。

            生成最终产物

            这里我们使用了 gradle提供的application plugin,这个插件可以将java jar包装成一个可执行文件的zip的压缩包。格式如下图所示:

            kotlin开发cli工具小技巧详解

            而这个的生成指令就是,通过./gradlew impact:assembleDist 任务生成对应的二进制压缩包。

            这样的好处就是我们可以省略掉java -jar xxxxx.jar的繁琐操作,通过可http://www.devze.com执行文件直接达到我们写一个cli的便利。

            结尾

            工程内的代码还是比较简单的,有兴趣的就自己读一下,只是一个demo而已。

            还是那句因为菜,不想去学一门新语言。如果万一哪怕我的py在强那么一点点,我也考虑用py来写了,哈哈哈哈哈。

            以上就是kotlin开发cli工具小android技巧详解的详细内容,更多关于kotlin开发cli工具的资料请关注我们其它相关文章!

            0

            精彩评论

            暂无评论...
            验证码 换一张
            取 消

            关注公众号