이제 Buildr 시리즈도 막바지를 향해 달려가는군요. 이번에는 빌드 결과물을 원격의 서버에 배포해보겠습니다.
Maven에 install, deploy 단계(phase)가 있는 것처럼 Buildr에도 배포와 관련된 install, upload, release 타스크가 있습니다만, 일반적인 웹어플리케이션 개발 프로젝트에서는 그닥 쓸모가 없습니다. 웹어플리케이션 프로젝트에서 배포는 대부분 WAS에 deploy하는 것을 의미하기 때문입니다.

로컬 WAS에 배포하는 건 간단하니 넘어가고, 리모트에 있는 개발서버나 운영서버에 우리가 빌드한 어플리케이션을 전송하는 방법을 알아보겠습니다.

요즘은 원격 서버에 연결하기 위한 프로토콜로 거의 ssh, scp를 이용합니다. Ant에서는 ssh,scp 프로토콜로 원격 서버에 연결하기 위해서 흔히 jsch 라이브러리를 사용했죠. Buildr에서는 net-ssh, net-scp 라이브러리를 사용하여 이런 작업을 처리합니다. 

먼저 profiles.yaml에 다음처럼 WAS 서버에 대한 접속정보가 설정되어 있습니다(이건 예제입니다).

development:
  wasserver:
    host: 111.111.111.111
    username: weblogic
    password: weblogic
    deploy_dir: /home/weblogic/webapps
    restart: sh /home/weblogic/restart.sh

production:
  wasserver:
    host: 222.222.222.222
    username: prod
    password: prod
    deploy_dir: /home/prod/webapps
    restart: sh /home/prod/restart.sh

WAS 서버에 웹어플리케이션을 업로드하는 타스크는 아래와 같이 작성할 수 있습니다. Net::SCP.start 메소드에 접속정보를 넘겨주고 블록 안에서 scp의 upload 메소드를 이용하여 'target/webapp' 디렉토리를 업로드하고 있습니다.

task :deploy_webapp => :package do
  require 'net/ssh'
  require 'net/scp' 

  wasserver = Buildr.settings.profile['wasserver']
  Net::SCP.start(wasserver['host'], wasserver['username'],
                       :password => wasserver['password']) do |scp|
    scp.upload!('target/webapp',  wasserver['deploy_dir'], :preserve=>true, :recursive=>true) 
  end
end

이렇게 업로드하면 로컬의 target/webapp/index.jsp 파일은 원격의 /home/weblogic/webapps/webapp/index.jsp에 위치하게 됩니다. 그러니까 로컬의 webapp 디렉토리 자체가 원격 타겟 디렉토리 밑으로 들어갑니다. 아 이건 우리가 원하는게 아닙니다. target/webapp 디렉토리는 빼고, 그 밑의 파일/디렉토리들만 원격 타겟 디렉토리 밑으로 보내고 싶습니다. 고민할 필요없이 FileList를 이용하면 됩니다.

FileList['target/webapp/*'].each do |file|
  scp.upload! file, file.sub('target/webapp',wasserver['deploy_dir']),:preserve=>true,:recursive=>true
end

exploded 모드로 빌드된 target/webapp 디렉토리 말고 그냥 하나의 패키징된 war 파일을 업로드하는 건 어떨까요? 

  scp.upload! package(:war).name, wasserver['deploy_dir'], :preserve=>true

패키징된 war 파일의 이름은 package(:war).name 로 구할 수 있었습니다. 무지 쉽군요. 

실제 실행해보시면 알겠지만, 이렇게 원격으로 업로드 시 콘솔에는 아무 것도 찍히지 않습니다. 재미가 없죠. 지금 어떤 파일이 전송되고 있는지 최소한 파일 이름이라도 나와주면 좋을 겁니다. 업로드 진행현황은 upload 메소드에 블록을 넘겨주어 표현할 수 있습니다. 다음은 업로드되는 파일의 이름과 파일의 전체 크기를 출력하는 예제입니다.

scp.upload!(package(:war).name, 
                 wasserver['deploy_dir'],
                 :preserve=>true) do |ch, name, sent, total|
  puts "uploading #{name} (#{total} byte)" if sent == 0
end

음... 전송파일의 크기가 큰 경우에는 콘솔에 변화가 별로 없으니 이것도 솔직히 별로 재미가 없습니다. 좀더 다이내믹하게 진짜로 현재의 업로드 진행 현황을 막대그래프에 #이나 * 문자가 늘어나는 식으로 보여주면 좋겠는데....
Buildr에서 메이븐 저장소에서 아티팩트를 다운로드 받을 때 다운로드 그래프가 나온다는 사실을 떠올리며 Buildr 소스를 여기저기 까보다가 ProgressBar 클래스를 발견했습니다. 나름 쓸만하네요.

local_file = package(:war).name
remote_dir = wasserver['deploy_dir']

ProgressBar.start :total=>File.size(local_file),:title=>local_file do |progress|
  scp.upload!(local_file, remote_dir, :preserve=>true) do  |ch, name, sent, total|
    progress.inc sent
  end
end

콘솔에 이렇게 출력됩니다.


웹어플리케이션을 업로드했으니, WAS를 재기동해볼까요? 서버에 있는 WAS를 재기동 or 웹어플리케이션을 재배포하는 여러 방법중에서 가장 간단한 방법은 아마도 쉘스크립트를 실행하는 방법일 겁니다. 원격 명령어 실행은 Net::SSH를 이용하여 처리합니다. Net::SCP와 유사하니 설명은 생략합니다.

task :restart_was do
  require 'net/ssh' 

  wasserver = Buildr.settings.profile['wasserver']
  Net::SSH.start(wasserver['host'],
                 wasserver['username'],
                 :password => wasserver['password']) do |ssh|
    ssh.exec! wasserver['restart']
  end
end

만약 WAS 재기동 스크립트 실행을 :deploy_webapp 타스크 안에서 처리하고 싶으면 다음처럼 합칠 수 있습니다. (ssh.scp로 쓰인 부분을 주목하세요)

task :deploy_webapp do
  require 'net/ssh'
  require 'net/scp'
  wasserver = Buildr.settings.profile['wasserver']
  Net::SSH.start(wasserver['host'],
                 wasserver['username'],
                 :password => wasserver['password']) do |ssh|    
    ssh.scp.upload! package(:war).name, wasserver['deploy_dir'], :preserve=>true     
    ssh.exec! wasserver['restart']
  end
end

 

'Build&Deploy > Buildr' 카테고리의 다른 글

Buildr : Hudson CI 연계하기  (0) 2009.01.14
Buildr : 설정정보 관리  (0) 2008.12.24
Buildr : 코드분석 리포트 생성  (0) 2008.12.23
Buildr : 빌드결과 패키징  (0) 2008.12.18
Buildr : 아티팩트 다운로드 받기  (0) 2008.12.17
Posted by 에코지오
,

이번에는 각종 설정값을 빌드 스크립트에 하드코딩하기 보다는 외부로 빼내어 관리하는 방법을 살펴보겠습니다.
Ant에서는 key=value 형식으로 외부 properties 파일을 이용하고, Maven에서는 사용자홈 경로의 settings.xml 또는 pom.xml 경로의 profiles.xml를 통해 설정정보를 분리합니다. Ant, Maven 모두 스크립트 실행시에 -Dkey=value 형식으로 설정을 입력하는 것도 가능합니다.

Ant와 Maven이 'key=value'라는 1차원적인 프로퍼티 형식으로 설정값을 기록하는 것을 선호한다면, rake나 Buildr은 YAML을 활용하는 2차원적인 트리 형식을 선호합니다. buildfile에서 설정값을 외부로 빼내는 방법은 크게 4가지가 있습니다.

1. 환경변수
스크립트 실행시 key=value  형식으로 입력하면 스크립트에서는 ENV['key']로 값을 받을 수 있습니다. 만약 key에 해당하는 운영체제 환경변수가 있으면 그것을 리턴합니다.

####> buildr upload password=secret

puts ENV['JAVA_HOME']
puts ENV['password']

2. settings.yaml
Buildr을 사용하게 되면 사용자홈/.buildr 디렉토리에 settings.yaml 파일이 생기는데, 여기에 (주로 개발자 개인적인) 설정을 포함할 수 있습니다.

repositories:
  remote :
  - http://www.ibiblio.org/maven2/
  - http://repository.codehaus.org/
  - http://download.java.net/maven/2/

messenger:
  server: jabber.company.com
  usr: notifier@company-jabber.com
  pwd: secret

settings.yaml에 위와 같은 설정이 있으면 스크립트에서는 Buildr.settings.user 속성(Hash)을 통해 값을 읽어옵니다.

puts repositories.remote
puts Buildr.settings.user['messenger']['server']
usr, pwd = Buildr.settings.user['messenger'].values_at('usr', 'pwd')

3. build.yaml
buildfile과 같은 위치의 build.yaml 파일에 (주로 개발자 사이에 공유할) 설정데이터를 넣을 수 있습니다.

artifacts:
  spring: org.springframework:spring:jar:2.0
  log4j: log4j:log4j:jar:1.0
  j2ee: geronimo-spec:geronimo-spec-j2ee:jar:1.4-rc4

jira:
  uri: https://jira.corp.org

build.yaml에 위와 같은 설정이 있다면, 스크립트에서는 Buildr.settings.build 속성(Hash)으로 설정에 접근할 수 있습니다.

compile.with :log4j, :j2ee
puts Buildr.settings.build['jira']['uri']

4. profiles.yaml
buildfile과 같은 위치의 profiles.yaml은 Maven의 profiles.xml과 동일한 역할을 합니다. 보통 빌드환경이 달라지면 다른 설정값이 요구되는데, 이는 프로파일을 통해 관리할 수 있습니다. 즉 프로파일은 빌드환경에 따라 설정값을 다르게 가져가는 것을 말합니다.

development, test, staging, production 등의 빌드환경 중에서 Buildr은 기본적으로 'development' 빌드환경에서 실행됩니다. 빌드 환경은 빌드스크립트 실행시 -e 옵션으로 바꿀 수 있습니다.
빌드 스크립트에서 현재 적용된 빌드환경 값은 Buildr.environment 메소드로 확인 가능합니다.

profiles.yaml에는 이렇게 선택된 빌드환경에 따라 어떤 설정값을 쓸지 YAML 형식으로 적어줍니다.

development:
  db: hsql
  jdbc: hsqldb:mem:devdb
  resource_ext: dev
  wasserver:
    host: 111.111.111.111
    username: testuser
    password: test1234
    deploy_dir: /home/test/deploy/war
    restart: sh restart.sh

production:
  db:oracle
  jdbc: oracle:thin:@bigstrong:1521:mighty
  resource_ext: prod
  wasserver:
    host: 222.222.222.222
    username: realuser
    password: realpass
    deploy_dir: /home/prod/deploy/war
    restart: cd /bea/user_projects/domains/mydomain;./stop.sh;./start.sh

프로파일의 설정값은 빌드 스크립트에서 Buildr.settings.profile 메소드로 읽어옵니다. 

puts Buildr.environment # 현재 적용된 빌드환경
puts Buildr.settings.profile['wasserver']['username'] # 현재 적용된 빌드환경의 설정값
puts Buildr.settings.profiles['production']['wasserver']['username'] # production 환경의 설정 값
Posted by 에코지오
,

Maven은 의존성 관리나 내재화된 빌드프로세스 같은 특징말고도 간단한 설정으로 여러가지 리포트를 생성해주는 좋은 기능을 가집니다. 뿐만아니라 pmd, findbugs, junit-report, cobertura 등의 코드분석 라이브러리가 생성하는 리포트들을 프로젝트 정보와 함께 하나의 사이트로 합쳐서 만들어줍니다. 아래는 메이븐 리포트 설정 예제입니다. 사실 default 설정을 따른다면 더 간단해질 수 있습니다.

 <reporting>
  <plugins>
   <!-- FindBugs 리포트 생성 플러그인 -->
   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>findbugs-maven-plugin</artifactId>
    <version>1.2</version>
    <configuration>
     <threshold>Low</threshold
     <xmlOutput>true</xmlOutput>
    </configuration>
   </plugin>
   <!-- PMD 리포트 생성 플러그인 -->
   <plugin>
    <artifactId>maven-pmd-plugin</artifactId>
    <configuration>
     <rulesets>
      <ruleset>/rulesets/basic.xml</ruleset>
      <ruleset>/rulesets/unusedcode.xml</ruleset>
     </rulesets>
     <sourceEncoding>UTF-8</sourceEncoding>
     <targetJdk>1.5</targetJdk>
    </configuration>
   </plugin>
  ... ....
 </reporting>

Ant에서 이와 동일한 일을 처리하려면 각각의 코드분석 라이브러리에 대한 복잡한 XML 설정에다가, 결과 리포트들을 하나로 합치기까지 하고 싶다면 별도의 라이브러리(Glean, QALabXRadar 등)를 사용해야 하는 수고가 따릅니다.

Buildr도 여러가지 코드분석 리포트를 생성할 수 있습니다만, 아직은 좀 약합니다. 사실 내부적으로 리포트 생성은 AntWrap을 통해서 Ant 타스크를 호출하는 식으로 처리하고 있습니다. Ant 위에 얹혀살고 있는 셈이죠.

일단 Buildr 1.3.3은 junit, jdepend, cobertura, emma에 대한 리포트 생성 능력을 보유하고 있습니다.

- junit:report
- cobertura:xml, cobertura:html
- emma:xml, emma:html
- jdepend:xml, jdepend:text, jdepend:swing

1. JUnit / TestNG
Buildr 1.3.3에서는 기본적으로 junit 4.4 버전으로 테스트를 수행하고 리포트를 생성합니다. 테스트케이스 소스는 src/test/java에 있다고 가정합니다. 리포트는 ./reports/junit 아래에 생성됩니다. 경로는 Layout을 이용하면 바꿀 수 있습니다.(그러나 리포트 생성 디렉토리가 변경되지 않는 버그가 있네요).
테스트를 JUnit이 아니라 TestNG로 하고 싶으면 빌드스크립트에 test.using :testng 라고 한줄 써주면 됩니다. 쉽죠.
그외 다양한 테스트 옵션에 대한 내용은 여기여기를 참조하세요.

2.Cobertura / Emma
우선 buildfile에 require 'buildr/cobertura'(또는 require 'buildr/emma')를 선언합니다. 그러면 이제부터 빌드시 instrument 작업이 자동으로 함께 수행됩니다. 커버리지 리포트는 reports/cobertura(또는 reports/emma)에 만들어집니다.

3. JDepend
require 'buildr/jdepend'를 선언하고 jdepend:xml을 실행하면 ./jdepend.xml 파일이 생성됩니다. 생성된 xml을 jdepend.xsl 스타일을 적용하여 html로 바꾸는게 어려워보이지는 않은데 아직 html 변환까지는 지원하지 않네요.

그외 pmd와 findbugs는 제가 직접 만들어봤습니다. 자세한 실행 옵션은 코드를 보면 응용할 수 있을 겁니다.

4. PMD

task :pmd do
  pmd_classpath = transitive('pmd:pmd:jar:4.2.1').each(&:invoke).map(&:to_s).join(File::PATH_SEPARATOR)
  ant("pmd-report") do |ant|
    ant.taskdef :name=> 'pmd',
                :classpath=>pmd_classpath,
                :classname=>'net.sourceforge.pmd.ant.PMDTask'
    ant.pmd :rulesetfiles => 'basic,imports,unusedcode' do
      ant.formatter :type=>'xml', :toFile=> _(:reports, 'pmd.xml')
      #ant.formatter :type=>'html', :toFile=> _(:reports, 'pmd.html')
      compile.sources.each do |src|
        ant.fileset :dir=> src, :includes=>'**/*.java'
      end
    end
  end
end

5. FindBugs

  task :findbugs do
    findbugs_classpath = transitive('net.sourceforge.findbugs:findbugs-ant:jar:1.3.2').each(&:invoke).map(&:to_s).join(File::PATH_SEPARATOR)
    ant("findbugs-report") do |ant|
      ant.taskdef :name=> 'findbugs',
                  :classpath=>findbugs_classpath,
                  :classname=>'edu.umd.cs.findbugs.anttask.FindBugsTask'
      ant.findbugs :home => 'C:/findbugs-1.3.6', # 파인드버그 설치 경로
                   :reportLevel=>'high',
                   :output=>'xml',
                   :outputFile=>_(:reports, 'findbugs.xml') do
        ant.method_missing(:class, {:location=> compile.target})
      end
    end
  end

메이븐은 maven site 명령으로 한큐에 모든 코드분석작업 및 리포트 생성을 처리할 수 있습니다. Buildr은 이런 타스크가 없기 때문에 하나 만들어줍니다.

task :reports =>[:pmd, :findbugs, 'jdepend:xml', 'cobertura:xml', 'junit:report']

이제 'buildr 프로젝트명:reports' 명령으로 완벽하진 않지만 우리도 한큐에 코드분석을 처리할 수 있게 됐습니다. 떨궈진 xml 파일들은 Hudson 같은 CI툴과 연동하면 메이븐처럼 예쁘게 볼 수 있겠죠.

'Build&Deploy > Buildr' 카테고리의 다른 글

Buildr : 원격 서버에 배포하기  (0) 2008.12.26
Buildr : 설정정보 관리  (0) 2008.12.24
Buildr : 빌드결과 패키징  (0) 2008.12.18
Buildr : 아티팩트 다운로드 받기  (0) 2008.12.17
Buildr : 타스크 의존 관계  (0) 2008.12.11
Posted by 에코지오
,
빌드 결과물(어플리케이션)을 WAS에 배포(deploy)하거나 공유를 위해 배포(distribute)하기 위해 우리는 먼저 패키징을 합니다.
Maven에서 패키징은 package 단계(phase)에서 수행됩니다. pom.xml에 지정된 jar,war,ear 등의 packaging 타입에 따라서 적절한 파일들을 모아서 묶어냅니다. Buildr의 패키징도 메이븐만큼 쉽습니다.

먼저 컴파일된 클래스들을 모아 jar 파일로 묶어보죠.

package :jar

buildr package 명령을 실행하면 default로 target 디렉토리에 "#{project.name}-#{project.version}.jar' 파일이 만들어집니다. 파일의 이름을 바꾸고 싶으면 :file 옵션을 추가합니다.

package :jar, :file=>'target/sample.jar'

package 메소드가 입력받는 type에는 :zip, :jar, :war, :ear, :tar, :sources, :javadoc 등이 있습니다.
이번에는 웹어플리케이션을 war 로 묶어봅니다. (위의 package :jar 문을 지우지 않아도 됩니다. package 메소드는 여러번 사용가능하고, package 타스크는 각각의 패키지 파일들을 모두 만들어줍니다)

package :war

:jar 타입과 마찬가지로 default로 target 디렉토리에 "#{project.name}-#{project.version}.war' 파일이 만들어집니다. 만들어진 war 안에는 3가지 유형의 파일들이 포함됩니다.

(1) path_to(:source, :main, :webapp) 디렉토리 밑의 모든 파일들 : war 루트에 포함됨
(2) compile.target 디렉토리와 resources.target 디렉토리의 파일들 : WEB-INF/classes 에 포함됨
(3) compile.dependencies 아티펙트 파일들 : WEB-INF/lib 에 포함됨

여기서 메이븐과의 차이가 보입니다. 메이븐은 war 타입의 프로젝트에 대해서 패키징을 할 때 war:war 골이 실행되는데, war:war 골은 원본 webapp 디렉토리(src/main/webapp)의 파일들을 war로 묶지 않습니다. 대신 war:exploded 골에 의해 'target/프로젝트명' 디렉토리'로 빌드(복사)된 파일들을 war로 묶게 됩니다.

이것은 이클립스의 WTP가 웹어플리케이션을 빌드하는 것과 유사합니다. java 소스를 컴파일하여 class를 만들듯이, html, jsp 같은 웹파일들도 별도의 디렉토리에 빌드한다는 개념이 들어있습니다. 그래서 컴파일/빌드된 파일들만을 가지고 패키징을 합니다.

그러나 Buildr에는 메이븐의 war:exploded에 해당하는 작업이 없습니다. 메이븐 war 패키징이 war:exploded => war:war 순서로 진행된다면, Buildr의 war 패키징은 war:inplace => war:war 순서로 진행하는 것과 같습니다. 제 생각에는 메이븐 방식이 더 나아보입니다. 그래야 원본 웹파일들을 건드리지 않고 리소스 치환 같은 가공작업을 추가로 수행할 수 있기 때문입니다. 

다행히도 Buildr의 유연성 덕분에 war 안에 기본적으로 패키징되는 webapp 디렉토리를 쉽게 바꿀 수 있습니다. (일단 원본 웹파일들은 target/webapp 으로 복사된다고 가정합니다. filter 메소드로 간단히 처리가능하죠.)

# 소스 webapp 폴더를 패키징에서 제외하고 그대신 빌드된 webapp 폴더 포함
package(:war).exclude(_(:source, :main, :webapp)).include(_(:target, :main, :webapp), :as=>'.')

include와 exclude 메소드를 이용해서 쉽게 작업을 마쳤습니다. include 메소드에서 :as 옵션을 통해 target/webapp 폴더의 내용물이 war 파일의 최상위에 위치하게 했습니다. 그렇지 않으면 war 파일에 webapp 폴더 자체가 들어가게 됩니다. 이런게 귀찮으면 with 메소드를 쓰면 간단해집니다. package(:war).with _(:target, :main, :webapp)

위에서 compile.dependencies의 아티펙트 파일들이 war 안의 WEB-INF/lib 경로에 자동으로 함께 묶인다고 했습니다. 근데 WEB-INF/lib 경로에 함께 묶여서는 안되는(묶일 필요가 없는) 아티팩트 파일들이 존재할 수 있습니다. 메이븐으로 치자면 provided 나 system 스코프의 의존 라이브러리들이 이에 해당합니다. 이경우 이렇게 빼주시면 됩니다.

package(:war).libs -= artifacts(스펙목록)

또는 컴파일 의존 라이브러리 설정시 provided 스코프와 system 스코프의 라이브러리들을 변수에 할당하여 사용하는 것이 깔끔한 것같네요.

PROVIDED_DEPENDENCIES = [스펙1,스펙2,...]
SYSTEM_DEPENDENCIES = [스펙a,스펙b,...]

compile.with PROVIDED_DEPENDENCIES, SYSTEM_DEPENDENCIES,
                   'org.apache.axis2:axis2:jar:1.2',
                    transitive('org.acegisecurity:acegi-security:jar:1.0.5')

package(:war).libs -= artifacts(SYSTEM_DEPENDENCIES, PROVIDED_DEPENDENCIES)


package 타스크의 실행 순서와 관련하여 주의할 점이 있습니다. package 메소드는 타스크를 리턴합니다. 예를 들어, package :war 문은 WarTask 타스크 객체를 리턴합니다. 그런데 타스크를 리턴하기 전에, 방금 생성된 타스크를 프로젝트 'package' 타스크의 선행(의존) 타스크로 추가합니다. 따라서 package 타스크 실행전에 어떤 작업을 해야한다면, 그 작업에 대한 의존관계 문장은 package 메소드 문장보다 위에 와야합니다.

# (1) war 파일이 만들어지고 난 후에 before_package 타스크가 실행됩니다.
package :war, :file=>'target/myproject.war'
task :package=>[:before_package]

# (2) before_package 타스크가 실행된 후에  war 파일이 만들어집니다.
task :package=>[:before_package]
package :war, :file=>'target/myproject.war'

또하나 package 타스크에 후행 작업을 정의할 때 package 메소드를 쓰면 안됩니다. compile, build 같은 타스크의 경우는 compile do ~~~ end 식으로 써도 되지만, package 타스크는 task 메소드로 package 타스크를 찾아서 추가 액션을 넣어 주어야합니다.

task(:package) do
  ... ...
end

'Build&Deploy > Buildr' 카테고리의 다른 글

Buildr : 설정정보 관리  (0) 2008.12.24
Buildr : 코드분석 리포트 생성  (0) 2008.12.23
Buildr : 아티팩트 다운로드 받기  (0) 2008.12.17
Buildr : 타스크 의존 관계  (0) 2008.12.11
Buildr : 리소스 교체  (3) 2008.12.09
Posted by 에코지오
,

compile.with를 통해 컴파일에 필요한 의존 라이브러리(artifacts)를 지정하면 buildr은 컴파일 작업시 해당 라이브러리를 리모트 Maven 저장소로부터 다운로드 받습니다(이미 로컬 Maven 저장소에 있는 경우는 제외).

compile.with 'org.apache.axis2:axis2:jar:1.2'

하지만 buildfile 스크립트 내에서 임의로 우리가 라이브러리를 다운로드 받을 수도 있습니다.

아, 그보다 먼저 리모트 메이븐 저장소 주소를 buildr에 알려주어야 합니다. 2가지 방법이 있습니다.

1. 사용자홈/.buildr/settings.yaml 파일에 메이븐 저장소 주소 세팅

2. 빌드스크립트(buildfile)에서 세팅

#repositories.remote << 'http://www.ibiblio.org/maven2/' << 'http://download.java.net/maven/2/'
repositories.remote.push %w{ http://www.ibiblio.org/maven2/  http://download.java.net/maven/2/ }

다시 돌아와서, buildfile 스크립트 내에서 임의로 라이브러리를 다운로드받을 수 있습니다.

한 개 다운로드 : artifact(스펙).invoke
여러 개 다운로드 : artifacts(스펙목록).each(&:invoke)

예를 들어 findbugs.jar 와 findbugs-ant.jar를 다운 받고자 한다면 이런식으로 작성합니다.

FINDBUGS = ["net.sourceforge.findbugs:findbugs:jar:1.3.2",
                    "net.sourceforge.findbugs:findbugs-ant:jar:1.3.2"]
artifacts(FINDBUGS).each(&:invoke)

Buildr::group 메소드를 써서 FINDBUGS를 다르게 표현할 수도 있습니다.

FINDBUGS = Buildr::group %w{findbugs findbugs-ant},
                                     :under=>'net.sourceforge.findbugs',
                                     :version=> '1.3.2'

Maven은 transitive dependencies(의존성 전이)라고 해서 한다리 건너의 의존 라이브러리까지도 알아서 가져오는 기능을 제공합니다. Buildr도 아직 완벽하지는 않지만 의존성 전이를 지원합니다. 바로 transitive 메소드가 있습니다.

주어진 스펙이 의존하는 라이브러리까지 몽창 다운로드 : transitive(스펙목록).each(&:invoke)

예를 들어 findbugs-antfindbugs에 의존하고 다시 findbugs는 asm,bcel,dom4j 등에 의존합니다.(pom파일의 내용은 링크에서 확인가능합니다) 따라서 transitive 메소드를 이용하여 findbugs-ant를 다운로드 받으면 findbugs와 그것이 의존하는 asm 등도 함께 다운로드됩니다.

transitive('net.sourceforge.findbugs:findbugs-ant:jar:1.3.2').each(&:invoke)


transitive는 compile.with에도 사용 가능합니다. 아래처럼 설정하면 acegi-security가 의존하는 스프링프레임워크 모듈들도 컴파일 작업시 함께 다운로드될 것입니다.

compile.with transitive('org.acegisecurity:acegi-security:jar:1.0.5')

만약 내가 원하는 라이브러리가 메이븐 형식의 저장소에 없지만 웹사이트 어딘가에서 받을 수 있다면 download 메소드를 이용하여 다운로드 받을 url 주소를 직접 지정하여 파일을 다운로드 받을 수 있습니다.

보통 웹사이트에서 다운받기 : download(artifact(스펙))=>파일주소).invoke

아래 예제는 dojo:dojo:zip:widget:0.2.2 형식의 아티팩트를 메이븐저장소가 아니라 주어진 url로부터 다운로드받아 로컬 메이븐 저장소에 설치합니다(M2_REPO/dojo/dojo/0.2.2/dojo-0.2.2-widget.zip).

url = 'http://download.dojotoolkit.org/release-0.2.2/dojo-0.2.2-widget.zip'
download(artifact("dojo:dojo:zip:widget:0.2.2")=>url).invoke

'Build&Deploy > Buildr' 카테고리의 다른 글

Buildr : 코드분석 리포트 생성  (0) 2008.12.23
Buildr : 빌드결과 패키징  (0) 2008.12.18
Buildr : 타스크 의존 관계  (0) 2008.12.11
Buildr : 리소스 교체  (3) 2008.12.09
Buildr : 리소스 필터링  (0) 2008.12.09
Posted by 에코지오
,
Ant에서 타겟들 사이의 의존관계는 target 태그의 depends 속성에 정의했습니다. 아래에서 B 타겟은 A 타겟에 의존합니다. B가 실행되기 위해서는 먼저 A가 실행되어야 합니다.

<target name="A"/>
<target name="B" depends="A"/>
<target name="C" depends="B"/>
<target name="D" depends="C,B,A"/>

D 타겟을 실행하면 D => C => B => A 로 이어지는 의존고리에 의해서 A 타겟이 제일먼저 실행되고 차례로 B -> C -> D 가 실행이 됩니다. D의 depends 속성에 B와 A가 다시 나오지만 B와 A는 이미 실행됐기 때문에 또 실행되지는 않습니다.

Buildr에서 Ant의 타겟에 해당하는 것은 타스크(Task) 입니다. 타스크는 task 메소드로 만듭니다.

desc "내 첫번째 타스크"
task :my_first_task do
    # do ~ end 블록에 타스크가 처리할 내용을 코딩합니다
   puts "첫번째 타스크"
end

task :my_second_task => :my_first_task do
   puts "두번째 타스크"
end

{:my_second_task => :my_first_task} 처럼 task 메소드에 해쉬를 넘겨주어 의존관계를 설정할 수 있습니다. 예제에서는 :my_first_task에 의존하는 :my_second_task 타스크가 만들어집니다. 의존하는 타스크가 여러개이면 배열로 나열하면 됩니다.

task :my_third_task => [:my_second_task, :my_first_task] do .... end

이미 타스크가 정의된 뒤에도 선행 타스크(prerequisites)를 추가하는 것이 가능합니다.

task :my_second_task => [:compile, :copy_images]

:my_second_task 타스크의 필요 선수조건은 이제 [:my_first_task, :compile, :copy_images] 타스크가 됩니다.
게다가 타스크가 해야할 일(action)도 나중에 추가할 수 있습니다.

task :my_second_task do
   puts "두번째 타스크에 액션 추가"
end

:my_second_task 타스크를 실행하면 ""두번째 타스크" 가 출력된 뒤에 "두번째 타스크에 액션 추가"가 출력됩니다.
사실 task 메소드는 주어진 이름의 타스크가 없으면 새로 만들고, 이미 있는거면 선행타스크와 액션을 그 타스크에 추가해줍니다.

Buildr은 Maven의 phase와 유사하게 이미 정의된 기본 타스크를 갖고 있습니다. compile, resources, build, package, javadoc, test, .... 등이죠. 전체 목록은 buidr help라고 치면 나옵니다.
내가 정의한 타스크를 이들 기본 타스크와 연계시키는 것은 매우 쉽습니다. 예를들어, compile 타스크가 실행되기 '전'에 :my_first_task 타스크가 실행되어야 한다면, task :compile => :my_first_task 이거 한줄이면 끝납니다. 메이븐보다 훠얼씬 쉽습니다.

메이븐에서는 골(goal)을 어떤 phase에 바인딩하게 되면 그 goal은 무조건 그 phase가 끝난 후(phase에 이미 바인딩된 다른 goal들이 모두 실행된 후)에 실행됩니다. phase 시작 전에 goal이 실행되어야 한다면 앞단계 phase에 바인딩해주어야 합니다. 그래서 my:first_goal이 compile 단계보다 먼저 실행되어야 한다면, compile의 앞단계인 process-resources 단계에 바인딩해주어야 합니다. 거참..좀 거시기하네요.. -.-;
아, 제가 하고 싶은 얘기는 메이븐 나뻐, 이게 아니구요 ^^ Buildr이 메이븐보다 더 유연하다는 것뿐이고...~

Buildr에서 메이븐처럼 어떤 타스크가 다른 타스크 뒤에 실행되게 하려면 명시적으로 후행 타스크의 invoke 메소드를 호출하면 됩니다.

task :after_second_task do
   puts "두번째 타스크 실행 후 추가 작업"
end

task :my_second_task do
   task(:after_second_task).invoke
end

만약에 compile 타스크가 실행된 '후'에 :my_first_task 타스크와 :process-classes 타스크가 실행되어야 한다면 이렇게 해줍니다.(invoke는 전체 빌드에서 타스크를 한번만 실행시키고, execute는 나올때마다 실행시킵니다)

compile do
     task(:my_first_task).invoke
     task(:process-classes).invoke
end

여기서 compile 타스크를 task(:compile)로 참조하지 않고 그냥 compile 메소드를 사용했습니다. Buildr에는 이미 해당 타스크를 리턴하는 resources, compile, build, ... 등의 메소드가 정의되어 있기 때문에 그냥 불러다 쓰면 됩니다.

'Build&Deploy > Buildr' 카테고리의 다른 글

Buildr : 빌드결과 패키징  (0) 2008.12.18
Buildr : 아티팩트 다운로드 받기  (0) 2008.12.17
Buildr : 리소스 교체  (3) 2008.12.09
Buildr : 리소스 필터링  (0) 2008.12.09
Buildr : 파일집합 복사하기  (0) 2008.12.05
Posted by 에코지오
,

리소스 필터링이 리소스 파일의 내용을 변경하는 것이라면, 리소스 교체는 원본 리소스 파일을 환경에 적합한 다른 리소스 파일로 교체하는 것입니다. 
예를 들어 개발서버 환경에서는 로컬PC 환경과 다른 설정값을 갖는 web.xml 파일이 사용되어야 한다면, 개발서버 환경용 web.xml.dev 파일을 만들고 개발 빌드시에 web.xml.dev => web.xml 식으로 파일 자체를 바꾸는 것입니다.
 
Ant에서 리소스 파일교체는 다음처럼 처리할 수 있습니다.

<copy todir="'target/webapp/WEB-INF" overwrite="true" preservelastmodified="true" verbose="true">
     <fileset dir="'target/webapp/WEB-INF" includes="**/*.dev" />
     <mapper type="glob" from="*.dev" to="*" />
</copy>


Buildr에서는 이렇게 처리할 수 있습니다. 

FileList['target/webapp/WEB-INF/**/*.dev'].each do |file|
    cp file, file.pathmap('%X'), :preserve=>true, :verbose => true
end

*.dev 파일을 골라내기 위해 FileList[glob패턴]을 사용했고, 각 파일들을 순회하면서 이름을 바꿔서 복사합니다.
여기서 pathmap이라는 메소드가 보이는데요, rake에서 String 클래스을 상속하여 추가한 메소드입니다.
pathmap이 받는 패턴 중에서 %X는 파일전체 패스에서 파일 확장자를 제외한 나머지 문자열을 리턴합니다. 'aa/bb.xml.dev'.pathmap('%X')는 'aa/bb.xml'을 리턴하죠.

'Build&Deploy > Buildr' 카테고리의 다른 글

Buildr : 아티팩트 다운로드 받기  (0) 2008.12.17
Buildr : 타스크 의존 관계  (0) 2008.12.11
Buildr : 리소스 필터링  (0) 2008.12.09
Buildr : 파일집합 복사하기  (0) 2008.12.05
Buildr : 파일 집합 선택  (0) 2008.12.01
Posted by 에코지오
,
리소스 설정치환은 리소스 필터링과 리소스 교체로 나눌 수 있습니다. 이 중에서 리소스 필터링은 원본 리소스 파일을 타겟으로 복사하면서 파일 내의 특정 부분을 다른 문자열로 치환하는 방법입니다. Ant, Maven, Buildr에서 이것을 어떻게 처리하는지 살펴봅니다.

1. Ant는 copy 타스크 내부에서 filter 요소를 사용하여 리소스 필터링을 지원합니다. 보통 @토큰@ 형식으로 표현된 내용이 다른 값으로 치환됩니다.

<copy file="${build.dir}/version.txt" toFile="${dist.dir}/version.txt">
  <filterset>
    <filter token="DATE" value="${TODAY}"/>
    <filtersfile file="${user.dir}/dist.properties"/> <!-- 이름/값 쌍을 프로퍼티 파일로 제공 -->
  </filterset>
</copy>

2. Maven에서는 process-resources 단계(또는 'resources:resources' 골)에서 보통 ${프로퍼티} 형식의 문자열이 실제 그 프로퍼티 값으로 바뀝니다.

<resources>
 <resource>
  <directory>src</directory>
  <filtering>true</filtering>
  <excludes>
   <exclude>**/*.java</exclude>
  </excludes>
 </resource>
</resources>

프로퍼티는 여러방식으로 제공됩니다. (1) 메이븐 실행시 -D를 통해서 제공 (2) properties 엘리먼트에 기술 (3) 프로퍼티 파일을 지정 (4) 시스템 프로퍼티. 프로퍼티 파일 형태로 이름/값 쌍을 제공하는 경우 아래처럼 build 밑에 filters 엘리먼트에 파일의 위치를 기술하면 됩니다.

<build>
  <defaultGoal>install</defaultGoal>
  <directory>${basedir}/target</directory>
  <finalName>${artifactId}-${version}</finalName>
  <filters>
     <filter>filters/filter1.properties</filter>
  </filters>
  ...
</build>


3. Buildr의 리소스 필터링은 메이븐과 유사합니다. Buildr에서는 resources 타스크를 통해 리소스의 내용을 필터링합니다. 기본적으로 src/main/resources 디렉토리의 리소스 파일들을 target/resources 디렉토리로 복사하면서 필터링을 수행합니다.

resources.from _('src/etc') # 원본 리소스 파일 디렉토리 변경
resources.include '*.html' # 포함할 파일 패턴
resources.exclude 'scratch/*' # 제외할 파일 패턴

resources
.filter.using 'version'=>'experimental''copyright'=>'Acme Inc (C) 2007' # 치환될 내용 매핑

위의 예제에서 리소스 파일내의 ${copyright} 부분은 'Acme Inc (C) 2007'로 바뀌게 됩니다. 이러한 이름/값 매핑은 당연히 외부 파일 형태로도 제공할 수 있습니다만, Buildr에서는 프로퍼티 형식이 아니라 buildr 파일과 동일한 위치에 있는 profiles.yaml 파일을 사용합니다. profiles.yaml 파일은 서로다른 환경에서 서로다른 설정을 가져가기 위한 것인데요(메이븐의 그것과 똑같은 개념입니다), 여기에 filter 엘리먼트를 추가하면 됩니다.

filter: &alpha1
  version: experimental
  copyright: Acme Inc (C) 2007
 
development:
  filter: *alpha1
test:
  filter: *alpha1

resources 타스크는 compile 타스크 실행시 자동으로 함께 수행됩니다. 아래처럼하면 단독으로 실행됩니다.

buildr 프로젝트이름:resources

그런데 이 리소스 필터링 기능이 resources 타스크가 실행될 때뿐 아니라 빌드의 다른 곳에서도 필요한 경우가 있습니다. 이때는 resources 타스크가 내부적으로 이용하고 있는 filter를 사용하면 됩니다.

filter('src/specs').into('target/specs').
  using('version'=>version, 'created'=>Time.now).run

'Build&Deploy > Buildr' 카테고리의 다른 글

Buildr : 타스크 의존 관계  (0) 2008.12.11
Buildr : 리소스 교체  (3) 2008.12.09
Buildr : 파일집합 복사하기  (0) 2008.12.05
Buildr : 파일 집합 선택  (0) 2008.12.01
Buildr : 의존성(dependencies) 설정하기  (4) 2008.11.19
Posted by 에코지오
,

지난 글에서 FileList와 glob 패턴을 이용해서 파일 집합을 선택하는 방법을 알아보았습니다. 그럼 이렇게 선택된 파일들을 다른 디렉토리에 복사하는 방법을 살펴보겠습니다. 
음... web 디렉토리내의 *.html 및 *.gif 파일들을 target/htdocs 디렉토리로 한번 복사해볼까요?

FileList['web/**/*.{html,gif}'].exclude('web/WEB-INF').each do |file|
   cp file, 'target/htdocs'
end

FileList의 파일 목록을 each로 순회하면서 cp하고 있습니다. 오 쉽군요. 그러나........
cp 메소드는 파일을 타겟 디렉토리로 복사해주긴 하는데 경로를 유지하지는 않습니다. 말하자면 web/AA/a.html 파일과 web/AA/BB/b.html 파일은 각각 target/htdocs/a.html과  target/htdocs/b.html로 복사됩니다. 경로는 다르지만 파일명이 같다면 뒤에 복사된 파일이 먼저 파일을 덮어쓰게됩니다.

우리가 원하는 건 web/AA/a.html 파일은 target/htdocs/AA/a.html 로 복사되고 web/AA/BB/b.html은 target/htdocs/AA/BB/b.html로 복사되는 것입니다.

FileList['web/**/*.{html,gif}'].exclude('web/WEB-INF').each do |file|
  target = file.sub('web','target/htdocs')
  mkdir_p File.dirname(target)
  cp file, target
end

원래 파일 경로 'web/AA/BB/b.html'에서 'web'을 'target/htdocs'로 바꾼 뒤 타켓 디렉토리를 만들고(mkdir_p) 거기에 파일을 복사해주었습니다. 그런데 좀 지저분하군요. 깔끔한 방법이 없을까요?

제가 찾아낸 방법은 buildr에서 제공하는 filter() 메소드를 이용하는 것입니다.

filter('web').into('target/htdocs').include('**/*.html', '**/*.gif').exclude('WEB-INF/**/*').run

원래 buildr의 filter는 Maven의 리소스 필터처럼 리소스 파일의 내용을 필터링하기 위한 목적으로 만들어진 것입니다만, 이런식으로 filter()를 한 디렉토리 내의 파일집합을 다른 디렉토리로 복사하는데 이용할 수 있습니다.

참고로 filter는 내부적으로 File.fnmatch를 이용하는데 File.fnmatch가 받는 패턴은 { } 표현을 지원하지 않는다고 문서에 나옵니다. 예를 들어 filter()의 include, exclude 메소드에 '**/*.{html,gif}' 패턴을 넘겨주면 *.html이나 *.gif를 찾는 것이 아니라 "{html,gif}"라는 확장자를 가진 파일을 찾게 됩니다.
또한 FileList[].exclude('web/WEB-INF')와 filter().exclude('WEB-INF/**/*')도 약간 다른 것을 볼 수 있습니다. 패턴이 제대로 적용되는지 반드시 테스트해보시기 바랍니다.

'Build&Deploy > Buildr' 카테고리의 다른 글

Buildr : 리소스 교체  (3) 2008.12.09
Buildr : 리소스 필터링  (0) 2008.12.09
Buildr : 파일 집합 선택  (0) 2008.12.01
Buildr : 의존성(dependencies) 설정하기  (4) 2008.11.19
Buildr : 컴파일 옵션 설정하기  (2) 2008.11.18
Posted by 에코지오
,
루비 'fileutils.rb' 라이브러리에 보면 FileUtils 모듈이 있습니다. FileUtils에는 파일/디렉토리를 다루기 위한 기본적인 유틸리티 메소드가 정의되어 있는데, 메소드명은 유닉스에서 우리가 쓰던 명령과 유사합니다. cp(), mkdir(), rm(), mv() 처럼요 ....

rake는 기본적으로 이 FileUtils 모듈을 include하고 있기 때문에 rake에 기반을 두고 있는 buildr에서도 거리낌없이 FileUtils의 메소드를 사용할 수 있습니다.

mkdir 'build/myproject/bin'
cp 'src/abc.xml.dev' 'build/myproject/bin/abc.xml'

Ant에서는 <copy>, <mkdir>, <delete>, <move> 등의 코어 타스크를 이용하여 이런 작업을 처리했죠.( Maven은 maven-antrun-plugin 플러그인을 통해서 ant의 타스크를 간접 호출해서 작업을 합니다. 좀 귀찮죠)

 <copy file="myfile.txt" tofile="mycopy.txt"/>

작업할 파일이 하나가 아니면 어떡할까요? Ant에서는 파일의 집합을 FileSet으로 표현합니다.
예를들어 소스 디렉토리 내의 java 소스를 제외한 파일들을 빌드 디렉토리로 복사하는 작업은 아래처럼 정의할 수 있습니다. 여기서 **/*.java 와 같은 경로 표현은 Ant에서 정의한 패턴입니다. Ant style 경로패턴이라고도 하죠(?)

  <copy todir="../build/classes">
    <fileset dir="src_dir">
      <exclude name="**/*.java"/>
    </fileset>
  </copy>
            또는
  <copy todir="../build/classes">
    <fileset dir="src_dir" excludes="**/*.java"/>
  </copy>

rake/buildr에는 Ant의 FileSet에 대응하는 Rake::FileList 클래스가 있습니다. FileList는 주어진 glob 패턴과 매치되는 파일의 목록을 리턴합니다.

FileList['src_dir/**/*.java']
   => src_dir 디렉토리 내의 모든 .java 파일 목록 리턴

FileList['web/**/*'].exclude('**/*.jsp','web/WEB-INF') 
   => web 디렉토리 내의 .jsp 파일 및 WEB-INF 디렉토리를 제외한 모든 파일 목록 리턴

FileList와 비슷한 것으로 Dir.glob()이 있는데요, FileList는 CVS, .svn, .bak 등의 디렉토리/파일을 기본적으로 목록에서 제외하는 반면 Dir.glob()은 그런 것들도 포함해서 리턴합니다. 그러니까 빌드스크립트에서는 Dir.glob() 보다는 FileList를 쓰는게 좋습니다.

참고적으로 File.fnmatch(패턴,파일경로) 메소드가 있습니다. 이 메소드는 주어진 파일의 경로가 지정된 패턴에 부합하는지 판단합니다. FileList[]나 Dir.glob()과 마찬가지로 패턴은 glob 형식입니다.
주의하실 점은 FileList[패턴]의 패턴, FileList[].exclude(패턴)의 패턴, File.fnmatch(패턴)의 패턴이 조금씩 차이가 있는 것 같다는 겁니다. 반드시 적용전 테스트를 해보셔야 합니다. 그리고 File.fnmatch가 패턴은 { } 표현을 지원하지 않습니다. 즉 File.fnmatch()에서 'A.html' 파일은 '**/*.{html,gif}' 패턴과 매치하지 않습니다.

내용이 길어져서 파일집합에서 원하는 파일들만 복사하는 작업과 리소스 파일 치환에 대한 예제는 다음글로 넘깁니다.
Posted by 에코지오
,