熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java開源技術 >> 正文

關於build tool的構想--從ant說起

2013-11-23 20:16:46  來源: Java開源技術 

  ant——你要是不會出門都不好意思跟人打招呼的那個ant每個人都用過
  
  它是一個build tool用xml來描述target用xml來設置每個task的屬性
  
  ant的好處我們都體會到了
  
  什麼都是xml而xml地球人都知道
  
  功能強大從編譯java文件到checkin cvs反正幾乎你想得到的功能它都能作
  
  擴展容易如果你發現某個功能ant沒有自己實現一個Task類就是
  
  一些功能設計得很合理比如javac和java自動檢查時間戳和依賴關系檢查等等
  
  但是用多了發現缺點也不少
  
  什麼都是xml而xml的語法有些時候顯得很繁瑣
  
  xml用來描述邏輯異常笨拙
  
  所有的邏輯都只能在java裡用Task實現要做一些跨越不同Task之間的通訊很困難比如先讀取第一個文件的時間戳再讀取另一個文件中儲存的時間戳再根據兩個時間戳之間的距離判斷下一步調用哪個task或者target
  
  xml的代碼重用困難很難定義一些常用的xml element作為庫然後再不同文件甚至項目中重用
  
  對module的支持有限
  
  仔細想想其實需求發展到邏輯重用模塊管理不同task通訊等已經離描述數據這個xml最擅長的領域越來越遠了
  
  如果把task作為基本的組成元件那麼上面提出的幾點需求都是關注於對這些基本元件的管理和組合或者說glue
  
  到此口號呼之欲出那就是script
  
  很多script作為一個完整的語言是做glue的最理想選手
  
  下面談談我對一個基於script的built tool的構想
  
  首先這個build tool仍然需要允許通過java來自定義task
  
  我們定義這樣一個接口
  
  java代碼:
  
  interface Command{
  Object execute(CommandContext ctxt)
  throws Throwable;
  }
  
  我們計劃讓所有的task(我們這裡叫它們command)都實現這個接口
  
  CommandContext負責傳遞一些象log之類的信息
  
  這個execute返回一個Object這個值作為這個Command的返回值可以用來和其它的Command通信
  
  我們允許這個函數拋出任何異常這個framework將會處理這些異常
  
  然後定義一些基本的Command比如ReturnCommand負責直接返回某一個值
  
  java代碼:
  
  class ReturnCommand implements Command{
  private final Object v;
  public Object execute(CommandContext ctxt){
  return v;
  }
  ReturnCommand(Object v){thisv=v;}
  }
  
  PrintCommand負責打印一句話
  java代碼:
  
  class PrintCommand implements Command{
  private final String msg;
  public Object execute(CommandContext ctxt){
  ctxtgetLogger()log(msg);
  return null;
  }
  PrintCommand(String msg){thismsg=msg;}
  }
  
  FailCommand負責報告錯誤
  
  java代碼:
  
  class FailCommand implements Command{
  private final String msg;
  public Object execute(CommandContext ctxt){
  throw new CommandException(msg);
  }
  FailCommand (String msg){thismsg=msg;}
  }
  
  如此等等這樣的基本元件還有很多比如file copy javac zip jar等
  
  但是如果僅僅如此那麼這個工具的能力最多也就和ant一樣
  
  我們最需要的是把不同的command組合起來的能力
  
  而組合最常見的就是順序執行
  
  java代碼:
  
  class SeqCommand implements Command{
  private final Command c;
  private final Command c;
  public Object execute(CommandContext ctxt){
  cexecute(ctxt);
  return cexecute(ctxt);
  }
  SeqCommand (Command c Command c){
  thisc = c;
  thisc = c;
  }
  }
  
  上面這個簡單的Command就負責按照順序執行連續的兩個command
  
  除了順序執行還有錯誤處理我們也許會希望當某個步驟執行失敗時去執行另外一個動作為此我們需要先定義一個接口來描述錯誤恢復
  
  java代碼:
  
  interface CommandRecovery{
  Command recover(Throwable th)
  throws Throwable;
  }
  
  當某個command失敗的時候這個接口會被調用實現這個接口可以有選擇地對某一種或者幾種錯誤進行恢復
  
  然後定義具體的錯誤恢復邏輯
  
  java代碼:
  
  class RecoveredCommand implements Command{
  private final Command c;
  private final CommandRecovery c;
  public Object execute(CommandContext ctxt){
  try{
  return cexecute(ctxt);
  }
  catch(Throwable th){
  return crecover(th)execute(ctxt);
  }
  }
  RecoveredCommand (Command c CommandRecovery c){
  thisc = c;
  thisc = c;
  }
  }
  
  有trycatch就有tryfinally我們也可以定義一個command讓它保證某個關鍵動作必然運行
  
  java代碼:
  
  class FinallyCommand implements Command{
  private final Command c;
  private final Command c;
  public Object execute(CommandContext ctxt){
  try{
  return cexecute(ctxt);
  }
  finally{
  cexecute(ctxt);
  }
  }
  FinallyCommand (Command c Command ){
  thisc = c;
  thisc = c;
  }
  }
  
  前面的順序執行我們是直接扔掉了前一個command的返回值但是有些時候我們也許希望根據第一個command的返回值來決定下一步的走向為此仿照CommandRecovery接口定義CommandBinder接口
  
  java代碼:
  
  interface CommandBinder{
  Command bind(Object v);
  }
  
  然後定義BoundCommand類
  
  java代碼:
  
  class BoundCommand implements Command{
  private final Command c;
  private final CommandBinder c;
  public Object execute(CommandContext ctxt){
  final Object v = return cexecute(ctxt);
  return cbind(v)execute(ctxt);
  }
  BoundCommand (Command c CommandBinder c){
  thisc = c;
  thisc = c;
  }
  }
  
  先透露一下這個BoundCommand非常重要就是它負責在不同的command間傳遞信息
  
  基本上的框架搭好了下面假設我們用一個類似groovy的腳本來寫某個target我們的目標是先取得當前時間然後打印出這個時間然後調用javac最後在程序結束後打印程序結束的信息
  
  java代碼:
  
  new BoundCommand(
  new GetTimeCommand()
  new CommandBinder(){
  public Command bind(Object v){
  final Command c = new PrintCommand(build time is +v);
  final Command javacc = new JavaCCommand();
  final Command done = new PrintCommand(build successful);
  return new SeqCommand(c new SeqCommand(javacc done));
  }
  }
  );
  
  上面的代碼先調用GetTimeCommand取得當前時間然後把這個實現傳遞到這個匿名類中去這個匿名類根據這個時間創建了下一步的command c
  
  接下來它調用兩次SeqCommand來表達兩次順序執行
  
  最終當這個command被執行的時候它就會完成我們上面要求的幾個步驟
  
  不錯挺好達到了在步驟間任意傳遞信息的要求甚至我們也可以重用某些command或者函數
  
  唯一一個問題這個代碼他媽的比xml還惡心!
  
  這還是很簡單的情況如果我們綜合順序錯誤處理分支等等代碼會丑陋得不忍卒睹
  
  看來不是隨便什麼script都可以勝任的
  
  那麼讓我們先靜下心來反過來想想我們到底希望有什麼樣的語法呢?
  
  寫偽碼應該是這樣
  
  java代碼:
  
  time < getCurrentTime
  print time
  javac
  print success
  
  我們的目標是用腳本語言把前面繁雜的java代碼屏蔽起來讓語法簡潔的腳本自動調用上面那些臃腫的代碼
  
  幸好我手頭有一個腳本語言可以達到類似的語法
  
  java代碼:
  
  do {time=now} $
  infoprint time >>
  javac {classpath=; fork=; compatibility=;} >>
  infoprint build successful
  
  這些do >>等函數其實是用SeqCommand BoundCommand等實現的只不過表面上看不到了
  
  更加復雜的邏輯比如包含順序執行也包含錯誤處理的
  java代碼:
  
  auto (infoprintln build done) $
  do {time=now} $
  infoprintln (build starting at + time) >>
  do {t = readFile file} $
  do {t = readFile file} $
  let
  diff = t t;
  writeFile file diff
  end
  
  這段腳本要先讀取當前時間然後打印build start然後先後從file和file讀取兩個數然後把這兩個數的差額寫入file 最後無論成功與否打印build done
  
  auto函數的意思是當後面那些東西執行完畢後無論是否出現exception都要打印build done
  
  你如果感興趣可以試著用java或者groovy寫寫看看結果多麼可怕
  
  如此一個完整的build框架就建立起來了我們只要填空式地給系統加入各種command實現一個靈活優美的build tool就出爐了
  
  最後預告一下基於這個思想的open source 項目Neptune即將啟動歡迎有志之士參加
  
  你可以參與這個框架核心的搭建(跟我合作)也可以編寫獨立的各種Command來豐富框架的功能
From:http://tw.wingwit.com/Article/program/Java/ky/201311/28218.html
  • 上一篇文章:

  • 下一篇文章:
  • Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.