読者です 読者をやめる 読者になる 読者になる

日頃の行い

個人的な日頃の行いをつらつら書いてます\\\\ ٩( 'ω' )و ////

embulkのpluginをScalaで作ってみようと思った時の備忘録

Embulkのpluginを書いてみようかなと思ったんですが、せっかくならScalaで書いてみようかなと思ってその時の備忘録です。

1. 概要

Embulkはこんな感じ(雑

github.com


pluginはJavaRubyで書けるようです。
Javaで書けるので、Scalaでも書けますね。

2. 準備

2.1. pluginの作成

色々ためしたレポジトリはこんな感じです。

github.com

とりあえずJavaでpluginを書く準備をします。
利用したEmbulkのversionは 0.7.4です

#embulk コマンドはインストール済み前提です。
$embulk --version
embulk 0.7.4
$embulk new java-input myappwithscala
...
$cd embulk-input-myappwithscala
$tree -L 3
.
├── LICENSE.txt
├── README.md
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── lib
│   └── embulk
│       └── input
└── src
    ├── main
    │   └── java
    └── test
        └── java

2.2. Scalaで書く準備

2つ変更があります。

  1. build.gradleにScala用の設定を追記
  2. PluginのクラスをScalaに変更
2.2.1. build.gradleにScala用の設定を追記
  • pluginsに id"scala" を追記
  • dependenciesに compile "org.scala-lang:scala-library:2.11.6" , testCompile 'org.scalatest:scalatest_2.11:2.2.4', testRuntime 'org.scala-lang.modules:scala-xml_2.11:1.0.3' を追記
  • 新しくtasks.withType(ScalaCompile) { scalaCompileOptions.useAnt = false } を追加

上記以外は初期のままです。

plugins {
    id "com.jfrog.bintray" version "1.1"
    id "com.github.jruby-gradle.base" version "0.1.5"
    id "scala" // scalaを追加 + javaは使わないつもりだったので削除した
}
import com.github.jrubygradle.JRubyExec
repositories {
    mavenCentral()
    jcenter()
}
configurations {
    provided
}

version = "0.1.0"

dependencies {
    compile "org.scala-lang:scala-library:2.11.6" // scala用に追加
    compile  "org.embulk:embulk-core:0.7.4"
    provided "org.embulk:embulk-core:0.7.4"
    // compile "YOUR_JAR_DEPENDENCY_GROUP:YOUR_JAR_DEPENDENCY_MODULE:YOUR_JAR_DEPENDENCY_VERSION"
    testCompile "junit:junit:4.+"
    testCompile 'org.scalatest:scalatest_2.11:2.2.4' // 同じくscalaように追加
    testRuntime 'org.scala-lang.modules:scala-xml_2.11:1.0.3' // 同じように(ry
}

// compile時に差分コンパイルにしてもらうための設定とかなんとか
// Activating the Zinc based compiler
tasks.withType(ScalaCompile) {
    scalaCompileOptions.useAnt = false
}

task classpath(type: Copy, dependsOn: ["jar"]) {
    doFirst { file("classpath").deleteDir() }
    from (configurations.runtime - configurations.provided + files(jar.archivePath))
    into "classpath"
}
clean { delete "classpath" }

task gem(type: JRubyExec, dependsOn: ["gemspec", "classpath"]) {
    jrubyArgs "-rrubygems/gem_runner", "-eGem::GemRunner.new.run(ARGV)", "build"
    script "${project.name}.gemspec"
    doLast { ant.move(file: "${project.name}-${project.version}.gem", todir: "pkg") }
}

task gemPush(type: JRubyExec, dependsOn: ["gem"]) {
    jrubyArgs "-rrubygems/gem_runner", "-eGem::GemRunner.new.run(ARGV)", "push"
    script "pkg/${project.name}-${project.version}.gem"
}

task "package"(dependsOn: ["gemspec", "classpath"]) << {
    println "> Build succeeded."
    println "> You can run embulk with '-L ${file(".").absolutePath}' argument."
}

task gemspec {
    ext.gemspecFile = file("${project.name}.gemspec")
    inputs.file "build.gradle"
    outputs.file gemspecFile
    doLast { gemspecFile.write($/
Gem::Specification.new do |spec|
  spec.name          = "${project.name}"
  spec.version       = "${project.version}"
  spec.authors       = ["Arata Tanaka"]
  spec.summary       = %[Myappwithscala input plugin for Embulk]
  spec.description   = %[Loads records from Myappwithscala.]
  spec.email         = ["tarata43@yahoo.co.jp"]
  spec.licenses      = ["MIT"]
  # TODO set this: spec.homepage      = "https://github.com/tarata43/embulk-input-myappwithscala"

  spec.files         = `git ls-files`.split("\n") + Dir["classpath/*.jar"]
  spec.test_files    = spec.files.grep(%r"^(test|spec)/")
  spec.require_paths = ["lib"]

  #spec.add_dependency 'YOUR_GEM_DEPENDENCY', ['~> YOUR_GEM_DEPENDENCY_VERSION']
  spec.add_development_dependency 'bundler', ['~> 1.0']
  spec.add_development_dependency 'rake', ['>= 10.0']
end
/$)
    }
}
clean { delete "${project.name}.gemspec" }
2.2.2. PluginのクラスをScalaに変更

そのままScala用に変えました。

Tips

  1. JavaのintはScalaのInt型に
  2. Listはそのままjava.util.Listに
  3. アホな事に拡張子が.scalaになっていないとgradlwでコンパイルしてくれないことにすごいハマったので注意です(´・ω・`)
package org.embulk.input.myappwithscala

import com.google.common.base.Optional
import org.embulk.config._
import org.embulk.spi._

class MyappwithscalaInputPlugin extends InputPlugin {
  trait PluginTask extends Task {
    // configuration option 1 (required integer)
    @Config("option1")
    def getOption1:Int

    // configuration option 2 (optional string, null is not allowed)
    @Config("optoin2")
    @ConfigDefault("\"myvalue\"")
    def getOption2:String

    // configuration option 3 (optional string, null is allowed)
    @Config("optoin3")
    @ConfigDefault("null")
    def getOption3:Optional[String]

    // if you get schema from config
    @Config("columns")
    def getColumns:SchemaConfig
  }

  override def transaction(config:ConfigSource, control:InputPlugin.Control):ConfigDiff = {
    val task:PluginTask = config.loadConfig(classOf[PluginTask])

    val schema = task.getColumns.toSchema
    val taskCount = 1 // number of run() method calls

    resume(task.dump(), schema, taskCount, control)
  }

  override def resume(
                       taskSource:TaskSource,
                       schema:Schema,
                       taskCount:Int,
                       control: InputPlugin.Control
                       ): ConfigDiff = {
    control.run(taskSource, schema, taskCount)
    Exec.newConfigDiff()
  }

  override def cleanup(
                        taskSource:TaskSource,
                        schema:Schema,
                        taskCount:Int,
                        successTaskReports:java.util.List[TaskReport]): Unit = ()

  @Override
  override def run(
                    taskSource:TaskSource,
                    schema:Schema,
                    taskIndex:Int,
                    output:PageOutput
                    ):TaskReport = {
    val task:PluginTask = taskSource.loadTask(classOf[PluginTask])
    // Write your code here :)
    println(schema.toString)
//    Exec.newCommitReport()
    throw new UnsupportedOperationException("MyappwithscalaInputPlugin.run method is not implemented yet")
  }

  override def guess(config:ConfigSource):ConfigDiff = {
    Exec.newConfigDiff()
  }
}

3. なんとなく動かしてみる

このまま動かしても throw new UnsupportedOperationException("MyappwithscalaInputPlugin.run method is not implemented yet") が記述されてるので例外が出ますが、今回具体的な実装はしていないので、その例外が発生することがゴールということにしようと思っています。

configファイルは下記のような感じです

in:
    type: myappwithscala
    option1: 1
    option2: example2
    columns: []
out:
    type: stdout

この状態でテスト的に動かしてみようと思います。

$./gradlew classpath
:compileJava UP-TO-DATE
:compileScala UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar UP-TO-DATE
:classpath UP-TO-DATE

BUILD SUCCESSFUL

Total time: 17.26 secs

This build could be faster, please consider using the Gradle Daemon: https://docs.gradle.org/2.6/userguide/gradle_daemon.html
$embulk run -I lib ./example.yml
2015-09-21 21:58:08.735 +0900: Embulk v0.7.4
2015-09-21 21:58:13.352 +0900 [INFO] (transaction): Loaded plugin embulk/input/myappwithscala from a load path
2015-09-21 21:58:13.511 +0900 [INFO] (transaction): {done:  0 / 1, running: 0}
Schema{
}
2015-09-21 21:58:13.774 +0900 [INFO] (transaction): {done:  1 / 1, running: 0}
org.embulk.exec.PartialExecutionException: java.lang.UnsupportedOperationException: MyappwithscalaInputPlugin.run method is not implemented yet
# 中略
Error: java.lang.UnsupportedOperationException: MyappwithscalaInputPlugin.run method is not implemented yet

エラーが出たようです。
消し忘れてたprintlnも表示されてますね
こんな感じでScalaで一応書けそうです。
そのうちplugin公開してみようかなと思います。