понедельник, 12 ноября 2012 г.

Scala && Idea Ant command line build

С недавнего времени начал переползать с Java на Scala. Писать под виртуальную машину приходится, но уж больно Java уныла как язык программирования, чудесно совмещая в себе отсутствие плюсовых возможностей в управлении (ссылки, указатели, scoped lifetime, ...) и громоздкой вербозностью.

Что до Scala - то она пока радует. Чему способствует хорошая книжка, а так же полезные ресурсы, например, вот.
И так-же хорошая интеграция в Idea при помощи plugin'ов, позволяя совершенно без проблем совмещать scala & lrgacy java код с первых минут использования.

Единственный `затык`, встреченный на данный момент, оказалась сборка из командной строки. Сама сборка была построена на Scons, в связи с большим объемом кода плюсового, и некоторой генерации. А Java код собирался вызываемым из Scons ant'ом, по сгенерированной идеей 'build.xml'.

Но для Scala, на данный момент, не генерируется необходимых целей сборки, в связи с чем пришлось искать обходные пути.

Ставились требования разработки в Idea и поддержания скрипта сборки в валидном состоянии простыми автоматическими действиями. Были попробованы SBT и Maven, но я не понял, как после генерации ими проектов по простому получить параллельно работающие ветви и в Ide и для command line (а SBT так просто повеселила своим названием, так как она для вхождения вовсе не simple - по прочтению ~ 8 страниц мануала у меня не было четкого понимания, как нужно описывать проекты, их структуру и т.д. .. да банально не мог сходу написать простой рабочий проект где не все по умолчанию).

В результате немного подсмотрев, как по простому собирать ant для сборки scala,  был допилен модификатор Idea'вских ant'овых скриптов, собственно который и решил данную задачу (на данный момент).

Питоновский код (в виде Scons билдера) под 'катом' (python converter for idea ant build xml):



 import xml.etree.ElementTree as ET  
   
 def setMainClass(jar, className):  
   # find manifest  
   manifest = jar.find('manifest')  
   if manifest is None :  
     # create if no  
     manifest = ET.SubElement(jar, 'manifest')  
   # find main class attribute  
   for attribute in manifest.findall('attribute'):  
     if attribute.attrib.get('name', '') == Main-Class:  
       return  
   # set, if no  
   ET.SubElement(manifest, 'attribute').attrib.update({  
     'name' : 'Main-Class',  
     'value' : className  
   })  
     
   
 def setMainClasses(root, jar2MainClass):  
   for jar in root.findall('.//jar'):  
     for jarName,className in jar2MainClass.items():  
       if jar.attrib.get('destfile', '').find(jarName) != -1:  
         setMainClass(jar, className)  
         break  
   
 def addScalac(root):  
   #  
   # find scala libs  
   #  
   scala_library = None  
   scala_compiler = None  
   for pathelem in root.findall('.//pathelement'):  
     location = pathelem.attrib.get('location', '')  
     if location.endswith('scala-library.jar'):  
       scala_library = location  
     elif location.endswith('scala-compiler.jar'):  
       scala_compiler = location  
   assert(scala_library and scala_compiler)  
   
   #  
   # insert scala init target  
   #  
   scala_init = ET.SubElement(root, 'target')  
   scala_init.attrib['name'] = 'init_scalac'  
   ET.SubElement(scala_init, 'property').attrib.update({   
     'name' : 'scala-library.jar',  
     'value' : scala_library   
   })  
   scala_task = ET.SubElement(scala_init, 'taskdef')  
   scala_task.attrib['resource']='scala/tools/ant/antlib.xml'  
   scapa_cp = ET.SubElement(scala_task, 'classpath')  
   ET.SubElement(scapa_cp, 'pathelement').attrib['location'] = scala_compiler  
   ET.SubElement(scapa_cp, 'pathelement').attrib['location'] = '${scala-library.jar}'  
   
   #  
   # insert scala bild targets  
   #  
   # Nodes with name='Singapore' that have a 'javac' child  
   for target in root.findall('.//target'):  
     javac = target.find('javac')  
     if javac is None:  
       continue  
     target.attrib['depends'] = 'init_scalac' if not target.attrib.has_key('depends') else target.attrib['depends'] + ',init_scalac'  
     scalac = ET.Element('scalac')  
     target.insert(list(target).index(javac), scalac)  
     scalac.insert(0, javac.find('classpath'))  
     ET.SubElement(scalac, 'include').attrib['name'] = '**/*.scala'  
     ET.SubElement(scalac, 'include').attrib['name'] = '**/*.java'  
     scalac.attrib.update({  
       'force' : 'changed',  
       'srcref' : javac.find('src').attrib['refid'],  
       'destdir': javac.attrib['destdir']  
     })  
   
   
 def ideaAntUpdater(target, source, env):  
   tree = ET.parse(str(source[0]))  
   root = tree.getroot()  
   
   if env.get('jar2MainClass', None):  
     setMainClasses(root, env['jar2MainClass'])  
   if env.get('scala', False):  
     addScalac(root)  
   
   tree.write(str(target[0]))  
   
   return 0  
   
 def ideaAntEmitter(target, source, env):  
   env.Depends(target, env.Value(env.get('jar2MainClass', None)))  
   env.Depends(target, env.Value(env.get('scala', False)))  
   env.Depends(target, env.Value('Version: 1.0.1'))  
   return (target, source)  
   
 class Builder(object):  
   def __init__(self):  
     pass  
   
   def configure(self, env):  
     env['BUILDERS']['IdeaAntUpdater'] = env.Builder(  
       action = ideaAntUpdater,  
       emitter = ideaAntEmitter  
     )  

Комментариев нет:

Отправить комментарий