blob: f27e25c5c9afaba2e49cc67e5d1a3c144a623abe [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with this
# work for additional information regarding copyright ownership. The ASF
# licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
# The Kotlin Module
module Buildr::Kotlin
DEFAULT_VERSION = '1.1.3-2'
class << self
def installed_version
unless @installed_version
@installed_version = if Kotlinc.installed?
begin
# try to read the value from the build.txt file
version_str = File.read(File.expand_path('build.txt', Kotlinc.kotlin_home))
if version_str
md = version_str.match(/\d+\.\d[\d\.]*/) or
fail "Unable to parse Kotlin version: #{version_str}"
md[0].sub(/.$/, "") # remove trailing dot, if any
end
rescue => e
warn "Unable to parse library.properties in $KOTLIN_HOME/build.txt: #{e}"
nil
end
end
end
@installed_version
end
def version
Buildr.settings.build['kotlin.version'] || installed_version || DEFAULT_VERSION
end
# check if version matches any of the given prefixes
def version?(*v)
v.any? { |v| version.index(v.to_s) == 0 }
end
end
# Kotlin compiler:
# compile.using(:kotlin)
# Used by default if .kt files are found in the src/main/kotlin directory (or src/test/kotlin)
# and sets the target directory to target/classes (or target/test/classes).
# Accepts the following options:
# * :warnings -- Issue warnings when compiling. True when running in verbose mode.
# * :debug -- Generates bytecode with debugging information. Set from the debug
# environment variable/global option.
# * :optimize -- Optimize the byte code generation. False by default.
# * :target -- Bytecode compatibility.
# * :noStdlib -- Include the Kotlin runtime. False by default.
# * :javac -- Arguments for javac compiler.
class Kotlinc < Buildr::Compiler::Base
class << self
def kotlin_home
env_home = ENV['KOTLIN_HOME']
@home ||= if !env_home.nil? && File.exists?(env_home + '/lib/kotlin-compiler.jar')
env_home
else
nil
end
end
def installed?
!kotlin_home.nil?
end
def use_installed?
if installed? && Buildr.settings.build['kotlin.version']
Buildr.settings.build['kotlin.version'] == Kotlin.installed_version
else
Buildr.settings.build['kotlin.version'].nil? && installed?
end
end
def dependencies
kotlin_dependencies = if use_installed?
%w(kotlin-stdlib kotlin-compiler).map { |s| File.expand_path("lib/#{s}.jar", kotlin_home) }
else
REQUIRES.artifacts.map(&:to_s)
end
# Add Java utilities (eg KotlinMessageCollector)
kotlin_dependencies |= [ File.join(File.dirname(__FILE__)) ]
(kotlin_dependencies).compact
end
def applies_to?(project, task) #:nodoc:
paths = task.sources + [sources].flatten.map { |src| Array(project.path_to(:source, task.usage, src.to_sym)) }
paths.flatten!
# Just select if we find .kt files
paths.any? { |path| !Dir["#{path}/**/*.kt"].empty? }
end
end
# The kotlin compiler jars are added to classpath at load time,
# if you want to customize artifact versions, you must set them on the
#
# artifact_ns['Buildr::Compiler::Kotlinc'].library = '1.1.3-2'
#
# namespace before this file is required. This is of course, only
# if KOTLIN_HOME is not set or invalid.
REQUIRES = ArtifactNamespace.for(self) do |ns|
version = Buildr.settings.build['kotlin.version'] || DEFAULT_VERSION
ns.compiler! 'org.jetbrains.kotlin:kotlin-compiler:jar:>=' + version
end
Javac = Buildr::Compiler::Javac
OPTIONS = [:warnings, :optimize, :target, :debug, :noStdlib, :javac]
# Lazy evaluation to allow change in buildfile
Java.classpath << lambda { dependencies }
specify :language=>:kotlin, :sources => [:kotlin, :java], :source_ext => [:kt, :java],
:target=>'classes', :target_ext=>'class', :packaging=>:jar
def initialize(project, options) #:nodoc:
super
# use common options also for javac
options[:javac] ||= Buildr::Compiler::Javac::OPTIONS.inject({}) do |hash, option|
hash[option] = options[option]
hash
end
options[:debug] = Buildr.options.debug || trace?(:kotlinc) if options[:debug].nil?
options[:warnings] = verbose if options[:warnings].nil?
options[:optimize] = false if options[:optimize].nil?
options[:noStdlib] = true if options[:noStdlib].nil?
@java = Javac.new(project, options[:javac])
end
def compile(sources, target, dependencies) #:nodoc:
check_options(options, OPTIONS)
java_sources = java_sources(sources)
unless Buildr.application.options.dryrun
messageCollector = Java.org.apache.buildr.KotlinMessageCollector.new
Java.load
begin
compiler = Java.org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.new
compilerArguments = kotlinc_args
compilerArguments.destination = File.expand_path(target)
compilerArguments.classpath = dependencies.join(File::PATH_SEPARATOR)
sources.each do |source|
compilerArguments.freeArgs.add(File.expand_path(source))
end
services = Buildr::Util.java_platform? ? Java.org.jetbrains.kotlin.config.Services::EMPTY : Java.org.jetbrains.kotlin.config.Services.EMPTY
compiler.exec(messageCollector, services, compilerArguments)
rescue => e
fail "Kotlin compiler crashed:\n#{e.inspect}"
end
unless java_sources.empty?
trace 'Compiling mixed Java/Kotlin sources'
deps = dependencies + Kotlinc.dependencies + [ File.expand_path(target) ]
@java.compile(java_sources, target, deps)
end
end
end
protected
# :nodoc: see Compiler:Base
def compile_map(sources, target)
target_ext = self.class.target_ext
ext_glob = Array(self.class.source_ext).join(',')
sources.flatten.map{|f| File.expand_path(f)}.inject({}) do |map, source|
sources = if File.directory?(source)
FileList["#{source}/**/*.{#{ext_glob}}"].reject { |file| File.directory?(file) }
else
[source]
end
sources.each do |source|
# try to extract package name from .java or .kt files
if %w(.java .kt).include? File.extname(source)
name = File.basename(source).split(".")[0]
package = findFirst(source, /^\s*package\s+([^\s;]+)\s*;?\s*/)
packages = count(source, /^\s*package\s+([^\s;]+)\s*;?\s*/)
found = findFirst(source, /((class)|(object))\s+(#{name})Kt/)
# if there's only one package statement and we know the target name, then we can depend
# directly on a specific file, otherwise, we depend on the general target
if (found && packages == 1)
map[source] = package ? File.join(target, package[1].gsub('.', '/'), name.ext(target_ext)) : target
else
map[source] = target
end
elsif
map[source] = target
end
end
map.each do |key,value|
map[key] = first_file unless map[key]
end
map
end
end
private
def count(file, pattern)
count = 0
File.open(file, 'r') do |infile|
while (line = infile.gets)
count += 1 if line.match(pattern)
end
end
count
end
def java_sources(sources)
sources.flatten.map { |source| File.directory?(source) ? FileList["#{source}/**/*.java"] : source } .
flatten.reject { |file| File.directory?(file) || File.extname(file) != '.java' }.map { |file| File.expand_path(file) }.uniq
end
# Returns Kotlinc arguments from the set of options.
def kotlinc_args #:nodoc:
compilerArguments = Java.org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments.new
compilerArguments.verbose = options[:debug]
compilerArguments.suppressWarnings = !options[:warnings]
compilerArguments.noStdlib = options[:noStdlib]
compilerArguments.noOptimize = !options[:optimize]
compilerArguments.reportOutputFiles = compilerArguments.verbose
compilerArguments.jvmTarget = options[:target] unless options[:target].nil?
compilerArguments
end
end
module ProjectExtension
def kotlinc_options
@kotlinc ||= KotlincOptions.new(self)
end
end
class KotlincOptions
attr_writer :incremental
def initialize(project)
@project = project
end
def incremental
@incremental || (@project.parent ? @project.parent.kotlinc_options.incremental : nil)
end
end
end
# Kotlin compiler comes first, ahead of Javac, this allows it to pick
# projects that mix Kotlin and Java code by spotting Kotlin code first.
Buildr::Compiler.compilers.unshift Buildr::Kotlin::Kotlinc
class Buildr::Project #:nodoc:
include Buildr::Kotlin::ProjectExtension
end