脚手架

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

而在程序开发过程中,每个工程或许说公司也都需求一个脚手架东西。经过脚手架命令行的方式简化开发流程,防止发生一些人为的相对初级的问题,所以这个也便是为什么叫做脚手架的原因吧。

而由于每个公司的代码标准都不同,一般情况下会主动让开发同学进行工程方面的cv操作,便是本钱高而且容易出错。这也便是为什么咱们计划写一些这样的东西的原因。

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

这次文章会介绍的是用kotlin去构建一个二进制文件,经过这个来完结脚手架cli东西的建造。

开搞

demo 工程地址TheNext

一开端的启发在于有时分运用一些第三方东西的时分会供给一个jar包,然后只要输入java -jar xxx.jar就能够运用这个jar包中的Main函数了。

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

开发调试

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

所以咱们就借用了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> library = 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)
    }
    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工具 | 没用的技能+1

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

这样的优点便是咱们能够省掉掉java -jar xxxxx.jar的繁琐操作,经过可执行文件直接达到咱们写一个cli的便当。

结束

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

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