# 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