ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Maven 빌드로 Web App 빌드 버전 관리하기
    초급 개발자 이야기/노하우 쌓아가기 2015. 3. 8. 17:58

    이 카테고리에는 더 이상 글이 올라오지 않습니다.


    별도의 개발 블로그를 열어 개발에 관련된 이야기는 블로그를 분리하였습니다.

    만약 더 많은 개발 이야기가 보고 싶으시다면 아래 링크로 이동해 주세요.


    link : Digital Blue Eye's dev-ops

    #######################################################################################################



    Web App을 배포하다보면 현재 배포된 녀석이 어느정도까지의 빌드를 담고 있는지 확인이 필요할 때가 있습니다.

    CI 툴을 사용하면 대부분 해결되기는 하지만 그래도 명시적으로 현재 배포된 빌드의 확인이 필요할 때가 있죠.


    작년 말 회사에서 개발/서비스하고 있는 플랫폼에서도 위와 같은 요구사항이 개인적(!!!)으로 발생하여 Maven 빌드 시 VCS의 빌드 넘버를 배포본에 포함하는 내용을 적용하였습니다.


    아래는 사내에 배포한 기술 문서의 내용을 옮겨왔습니다.


    1. 버전 관리 방안

    모든 Web Application은 배포된 버전을 쉽게 확인하기 어렵다.
    Maven Builder에서는 배포 직전의 Web Application Build 시 Build 정보를 추가할 수 있는 여러 가지 plug-in 들이 존재하며, 현재도 많은 해결 방법을 위해 Open Source 들이 개발되고 있다.

    본 문서에서는 Maven Builder의 plug-in 중 “buildnumber-maven-plugin”과 “maven-timestamp-plugin”을 활용하여 다수의 버전 삽입 방법을 설명한다.


    2. WAR 생성 시 버전 삽입

    첫 번째 방법은 WAR 생성 시 VCS의 Revision 번호를 지정하는 방법이다.
    이 방법은 다시 두 가지로 나뉘어 지는데, 하나의 방법은 WAR의 파일 명에 Revision 번호를 지정하는 방법이고, 다른 하나의 방법은 Manifest에 Revision 번호를 삽입하는 방법이다.

    2.1. VCS 연동을 위한 pom.xml 설정

    우선 VCS 연동을 위해 pom.xml을 아래와 같이 설정해야 한다.


    <project>	
        <build>
            <plugins>
          
                <!-- SVN의 Revision 번호를 이용해 Build Number를 생성하기 위한 plugin -->
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>buildnumber-maven-plugin</artifactId>
                    <version>1.3</version>
                    <!-- plugin이 실행되는 시점을 지정한다. 아래의 설정에서는 maven build 중 validate 단계에 실행된다. -->
                    <executions>
                        <execution>
                            <phase>validate</phase>
                            <goals>
                                <goal>create</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <doCheck>false</doCheck>
                        <doUpdate>false</doUpdate>
    
                        <!-- VCS 접속 구현체 지정. VCS에 따라 달라질 수 있다. -->
                        <providerImplementations>
                            <svn>javasvn</svn>
                        </providerImplementations>
    
                        <revisionOnScmFailure>noScm</revisionOnScmFailure>
                        <useLastCommittedRevision>true</useLastCommittedRevision>
                        <buildNumberPropertyName>buildRevision</buildNumberPropertyName>
                    </configuration>
                    <!-- buildnumber-maven-plugin이 SVN 접속을 위해 필요한 dependency. VCS에 따라 달라질 수 있다. -->
                    <dependencies>
                        <dependency>
                            <groupId>com.google.code.maven-scm-provider-svnjava</groupId>
                            <artifactId>maven-scm-provider-svnjava</artifactId>
                            <version>2.1.1</version>
                        </dependency>
    
                        <dependency>
                            <groupId>org.tmatesoft.svnkit</groupId>
                            <artifactId>svnkit</artifactId>
                            <version>1.8.3</version>
                        </dependency>
                    </dependencies>
                </plugin>
    
            </plugins>
    
        </build>
     
        <!-- buildnumber-maven-plugin에서 SVN에 접근하기 위한 connection 지정. VCS에 따라 connection의 값을 변경해야 한다. -->
        <scm>
            <connection>scm:svn:http://{VCS Address & Repository Name}</connection>
        </scm>
    
    </project>
    
    


    VCS 연동을 위해서는 <scm> 정의와 VCS connector 구현체의 dependency 추가가 필요하다.
    위의 설정에서는 “maven-scm-provider-svnjava”와 “svnkit”이 VCS Connector 구현체로 사용되었다. git, CVS 등 다른 VCS와 연동하기 위해서는 별도의 VCS Connector 구현체가 필요하다.

    VCS connector를 이용해 실제 VCS에서 Revision 정보를 가져오기 위해서 위의 설정에서는 “buildnumber-maven-plugin” plug-in이 사용되었다. 유의 깊게 보아야 할 설정은 <providerImplementations>으로 위 설정에서는 SVN을 연동하기 위한 설정이며, 타 VCS와의 연동을 위해서는 설정 값이 변경되어야 한다.

    위의 설정으로 VCS로부터 Revision 번호를 받아올 수 있게 되었다.


    2.2. WAR 파일 명에 Revision 번호 지정 및 Manifest 생성

    WAR 파일 명에 Revision 번호를 지정하고 Manifest를 생성하기 위해서 아래와 같이 pom.xml을 수정한다.


    <project>
        <build>
            <plugins>
                <!-- WAR build 시 Manifest에 build number를 추가하기 위한 plugin -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-war-plugin</artifactId>
                    <configuration>
                        <warName>${project.artifactId}-${project.version}-${buildRevision}</warName>
                        <warSourceDirectory>${basedir}/src/main/webapp</warSourceDirectory>
                        <archive>
                            <manifest>
                                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                            </manifest>
    
                            <manifestEntries>
                                <Implementation-Build>${buildRevision}</Implementation-Build>
                            </manifestEntries>
    
                        </archive>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>
    



    Maven build 시 WAR 파일을 생성하기 위해 “maven-war-plugin” plug-in을 사용한다.
    주의할 점은 <manifestEntries>의 <Implementation-Build>의 값인 ${buildRevision}이다.
    이 ${buildRevision} 값은 앞서 설정한 “buildnumber-maven-plugin” plug-in에서 생성되므로 반드시 앞의 설정이 필요하다.

    VCS와의 연동이 필요치 않은 경우 ${buildRevision}을 ${buildNumber}로 사용할 수 있다.
    이 설정은 project source의 루트 경로에 buildNumber.properties라는 임의의 파일을 생성한 후 생성 시점에 0의 값을 설정하고 Build가 발생할 때마다 값을 1씩 증가함으로써 Build 버전을 만들어 낼 수 있다. 다만, 이 ${buildNumber}는 단 하나의 머신에서만 적용되므로 다수의 개발자가 Build를 하는 환경에는 대응할 수 없다. ${buildNumber}의 사용 설정에 대해서는 생략한다.


    2.3. Maven Build 일시 사용하기

    Maven build 시의 일시를 이용하여 배포된 Web Application이 언제 Build 되었는지 확인할 수 있다. 아래의 설정을 pom.xml에 추가한다.


    <project>
        <build>
            <plugins>
                <!-- Build Date Time을 생성하기 위한 plugin -->
                <plugin>
                    <groupId>com.keyboardsamurais.maven</groupId>
    
                    <artifactId>maven-timestamp-plugin</artifactId>
    
                    <version>1.0</version>
                    <configuration>
                        <propertyName>timestamp</propertyName>
                        <timestampPattern>yyyy/MM/dd HH:mm</timestampPattern>
                    </configuration>
                    <executions>
                        <execution>
                            <phase>validate</phase>
                            <goals>
                                <goal>create</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin> 
            </plugins>
        </build>
    </project>
    
    


    Maven build 시의 build 일시를 property로 사용하기 위하여 “maven-timestamp-plugin” plug-in을 사용한다. 이 plug-in은 timestamp 값을 다양한 포맷으로 변경하여 사용할 수 있게 해 준다.


    사용되는 timestamp는 기본적으로 local Time-zone을 사용하므로 빌드 머신이 다른 시간대에 있을 경우 약간의 주의가 필요하다.


    또한 위 plug-in을 Eclipse의 maven plug-in인 m2e에서 사용할 경우 약간의 주의가 필요한데, 아래와 같은 예외 설정이 추가되어야 한다.


    <project>
        <build>
            <pluginManagement>
            	<plugins>
            		<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
            		<plugin>
            			<groupId>org.eclipse.m2e</groupId>
            			<artifactId>lifecycle-mapping</artifactId>
            			<version>1.0.0</version>
            			<configuration>
            				<lifecycleMappingMetadata>
            					<pluginExecutions>
            						<pluginExecution>
            							<pluginExecutionFilter>
            								<groupId>
            									com.keyboardsamurais.maven
            								</groupId>
            								<artifactId>
            									maven-timestamp-plugin
            								</artifactId>
            								<versionRange>
            									[1.0,)
            								</versionRange>
            								<goals>
            									<goal>create</goal>
            								</goals>
            							</pluginExecutionFilter>
            							<action>
            								<ignore></ignore>
            							</action>
            						</pluginExecution>
            					</pluginExecutions>
            				</lifecycleMappingMetadata>
            			</configuration>
            		</plugin>
            	</plugins>
            </pluginManagement>
        </build>
    </project>
    
    


    위와 같은 예외 설정이 필요한 이유는 m2e와 maven의 build phase 처리가 다르기 때문이다.

    각 plug-in 들은 실행되어야 할 phase를 지정해야 한다. 예를 들어 3.1에서 설정한 “buildnumber-maven-plugin” plug-in의 경우 3.2과 같이 WAR 생성 시에 사용하고자 한다면 package phase 이전에만 실행되면 된다. (하지만 위와 같이 validate phase로 설정된 이유는 아래에 추가할 java properties로 활용하기 위해 create-resource phase 전에 실행되기를 원했기 때문이다.)


    “maven-timestamp-plugin”은 m2e환경에서는 기본적으로 phase를 지정하지 않거나, package phase 이상으로 설정해야 한다. 하지만 이런 경우 timestamp를 빌드 일시로써 java properties로 활용하기 어려워진다. package 이전에 이미 resource는 처리되기 때문이다. (compile 이전의 process-resource phase에서 java properties는 이미 처리된다.)


    위의 예외 설정은 m2e에서만 동작하며, 기본 maven build 시(mvn 명령 사용)에는 무시된다.



    2.4. VCS Revision 번호와 Build 일시를 java properties로 사용


    이번에는 VCS Revision 번호와 Build 일시를 maven build 시에 java properties로 설정하여 Web Application에서 다양하게 활용할 수 있는 기반을 만들어 본다.


    아래와 같은 설정을 pom.xml에 추가한다.


    <project>
        <build>
            <!-- properties에 build 관련 정보를 생성하여 application에서 사용할 수 있다. -->
            <resources>
                <resource>
                    <directory>src/main/resources</directory>
                    <filtering>true</filtering>
                </resource>
            </resources>
            <filters>
                <filter>src/main/resources/buildinfo.properties</filter>
            </filters>
        </build>
    </project>
    

    resource 위치인 src/main/resource에 아래와 같은 내용으로 buildinfo.properties 파일을 생성한다.

    # Build Information
    Build.VCS.rev.num=${buildRevision}
    Build.maven.date=${timestamp}
    


    Maven build 시점에 java properties를 생성하기 위해서는 우선 properties 템플릿이 필요하다. 위와 같이 ${buildRevision}, ${timestamp}를 사용하는 properties 템플릿을 생성한다.


    pom.xml의 설정에 따라 pom에서 property로 설정된 ${buildRevision}과 ${timestamp}가 java properties의 설정으로 대체되어 값이 설정된다.


    3. Build 하기

    Maven Build를 수행하면 아래와 같은 로그를 확인할 수 있다.


    SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
    SLF4J: Defaulting to no-operation (NOP) logger implementation
    SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
    [INFO] Scanning for projects...
    [WARNING] 
    [WARNING] Some problems were encountered while building the effective model for com.prompt:push:war:2.0
    [WARNING] 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-war-plugin is missing. @ line 429, column 21
    [WARNING] 
    [WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
    [WARNING] 
    [WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
    [WARNING] 
    [INFO]                                                                         
    [INFO] ------------------------------------------------------------------------
    [INFO] Building QUARK Push 2.0
    [INFO] ------------------------------------------------------------------------
    [INFO] 
    
    [INFO] --- buildnumber-maven-plugin:1.3:create (default) @ push ---
    
    [INFO] Change the default 'svn' provider implementation to 'javasvn'.
    [INFO] Storing buildNumber: 5356 at timestamp: 1415863815921
    [INFO] Storing buildScmBranch: trunk
    [INFO] 
    
    [INFO] --- maven-timestamp-plugin:1.0:create (default) @ push ---
    
    [INFO] 
    [INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ push ---
    [debug] execute contextualize
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] Copying 16 resources
    [INFO] 
    [INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ push ---
    [INFO] Nothing to compile - all classes are up to date
    [INFO] 
    [INFO] --- maven-resources-plugin:2.5:testResources (default-testResources) @ push ---
    [debug] execute contextualize
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] Copying 2 resources
    [INFO] 
    [INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ push ---
    [INFO] Not compiling test sources
    [INFO] 
    [INFO] --- maven-surefire-plugin:2.10:test (default-test) @ push ---
    [INFO] Tests are skipped.
    [INFO] 
    
    [INFO] --- maven-war-plugin:2.1.1:war (default-war) @ push ---
    
    [INFO] Packaging webapp
    [INFO] Assembling webapp [push] in [E:\project_source\00_on_going\astron_v1.2\Astron_Push\target\push-2.0]
    [INFO] Processing war project
    [INFO] Copying webapp resources [E:\project_source\00_on_going\astron_v1.2\Astron_Push\src\main\webapp]
    [INFO] Webapp assembled in [860 msecs]
    
    [INFO] Building war: E:\project_source\00_on_going\astron_v1.2\Astron_Push\target\push-2.0-5356.war
    
    [WARNING] Warning: selected war files include a WEB-INF/web.xml which will be ignored 
    (webxml attribute is missing from war task, or ignoreWebxml attribute is specified as 'true')
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 7.506s
    [INFO] Finished at: Thu Nov 13 16:30:21 KST 2014
    [INFO] Final Memory: 10M/24M
    [INFO] ------------------------------------------------------------------------
    


    각 로그 단계에서 보아야 할 부분은 총 4 부분이다.


    line 18. buildnumber-maven-plugin:1.3:create

    이 로그를 통해 buildnumber-maven-plugin이 실행되었으며, 이에 따라 VCS의 Revision 번호를 읽어왔음을 알 수 있다. 바로 아랫줄에서 기본 SVN provider가 설정되었던 javasvn으로  변경되었음도 유의 깊게 보자.


    line 23. maven-timestamp-plugin:1.0:create

    이 로그는 빌드 시점의 timestamp가 일시로 변경되어 pom property로 저장되었음을 알 수 있다.


    line 44. maven-war-plugin:2.1.1:war

    maven-war-plugin이 생성된 resource와 compile된 결과물을 이용해 Web Application을 만드는 작업을 시작했다.


    line 50. 생성된 WAR 파일에 5356이라는 SVN Revision이 이름에 추가된 것을 알 수 있다.


    이제 생성된 WAR 파일을 압축 해제하여 다음의 Resource를 확인한다.


    push-2.0-5356/META-INF/MANIFEST.MF


    Manifest-Version: 1.0
    Archiver-Version: Plexus Archiver
    Created-By: Apache Maven
    Built-By: zephyr
    Build-Jdk: 1.6.0_34
    Implementation-Title: QUARK Push
    Implementation-Version: 2.0
    Implementation-Vendor-Id: com.prompt
    Implementation-Build: 5356
    Implementation-Build-Date: 2014/11/13 15:56
    


    Manifest 파일을 열어보면 위와 같이 Implementation-Build에 SVN Revision이, Implementation-Build-Date에 빌드 일시가 기록된 것을 볼 수 있다.


    다음으로 push-2.0-5356/WEB-INF/classes/buildinfo.properties 파일을 열어보자.


    # Build Information
    Build.VCS.rev.num=5356
    Build.maven.date=2014/11/13 16:30
    


    이 역시 ${buildRevision}과 ${timestamp} 변수가 실제 값으로 저장된 것을 볼 수 있다.

    이제 이 java properties를 이용해 Application 실행 시, 로그 출력 시, Front End 등에 다양하게 Build 번호와 빌드 일시를 활용할 수 있게 되었다.



    4. 전체 소스


    buildinfo.properties

    # Build Information
    Build.VCS.rev.num=${buildRevision}
    Build.maven.date=${timestamp}
    


    pom.xml

    </project>	
        </build>
            <plugins>
          
                <!-- SVN의 Revision 번호를 이용해 Build Number를 생성하기 위한 plugin -->
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>buildnumber-maven-plugin</artifactId>
                    <version>1.3</version>
    		<!-- plugin이 실행되는 시점을 지정한다. 아래의 설정에서는 maven build 중 validate 단계에 실행된다. -->
                    <executions>
                        <execution>
                            <phase>validate</phase>
                            <goals>
                                <goal>create</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <doCheck>false</doCheck>
                        <doUpdate>false</doUpdate>
                        <!-- VCS 접속 구현체 지정. VCS에 따라 달라질 수 있다. -->
                        <providerImplementations>
                            <svn>javasvn</svn>
                        </providerImplementations>
                        <revisionOnScmFailure>noScm</revisionOnScmFailure>
                        <useLastCommittedRevision>true</useLastCommittedRevision>
                        <buildNumberPropertyName>buildRevision</buildNumberPropertyName>
                    </configuration>
                    <!-- buildnumber-maven-plugin이 SVN 접속을 위해 필요한 dependency. VCS에 따라 달라질 수 있다. -->
                    <dependencies>
                        <dependency>
                            <groupId>com.google.code.maven-scm-provider-svnjava</groupId>
                            <artifactId>maven-scm-provider-svnjava</artifactId>
                            <version>2.1.1</version>
                        </dependency>
    
                        <dependency>
                            <groupId>org.tmatesoft.svnkit</groupId>
                            <artifactId>svnkit</artifactId>
                            <version>1.8.3</version>
                        </dependency>
                    </dependencies>
                </plugin>
        
                <!-- Build Date Time을 생성하기 위한 plugin -->
                <plugin>
                    <groupId>com.keyboardsamurais.maven</groupId>
                    <artifactId>maven-timestamp-plugin</artifactId>
                    <version>1.0</version>
                    <configuration>
                        <propertyName>timestamp</propertyName>
                        <timestampPattern>yyyy/MM/dd HH:mm</timestampPattern>
                    </configuration>
                    <executions>
                        <execution>
                            <phase>validate</phase>
                            <goals>
                                <goal>create</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
                
                <!-- WAR build 시 Manifest에 build number를 추가하기 위한 plugin -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-war-plugin</artifactId>
                    <configuration>
                        <warName>${project.artifactId}-${project.version}-${buildRevision}</warName>
                        <warSourceDirectory>${basedir}/src/main/webapp</warSourceDirectory>
                        <archive>
                            <manifest>
                                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                            </manifest>
                            <manifestEntries>
                                <Implementation-Build>${buildRevision}</Implementation-Build>
    			    <Implementation-Build-Date>${timestamp}</Implementation-Build-Date>
                            </manifestEntries>
                        </archive>
                    </configuration>
                </plugin>
    
            </plugins>
          
            <!-- properties에 build 관련 정보를 생성하여 application에서 사용할 수 있다. -->
            <resources>
                <resource>
                    <directory>src/main/resources</directory>
                    <filtering>true</filtering>
                </resource>
            </resources>
            <filters>
                <filter>src/main/resources/buildinfo.properties</filter>
            </filters>
    
            <pluginManagement>
            	<plugins>
            		<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
            		<plugin>
            			<groupId>org.eclipse.m2e</groupId>
            			<artifactId>lifecycle-mapping</artifactId>
            			<version>1.0.0</version>
            			<configuration>
            				<lifecycleMappingMetadata>
            					<pluginExecutions>
            						<pluginExecution>
            							<pluginExecutionFilter>
            								<groupId>
            									com.keyboardsamurais.maven
            								</groupId>
            								<artifactId>
            									maven-timestamp-plugin
            								</artifactId>
            								<versionRange>
            									[1.0,)
            								</versionRange>
            								<goals>
            									<goal>create</goal>
            								</goals>
            							</pluginExecutionFilter>
            							<action>
            								<ignore></ignore>
            							</action>
            						</pluginExecution>
            					</pluginExecutions>
            				</lifecycleMappingMetadata>
            			</configuration>
            		</plugin>
            	</plugins>
            </pluginManagement>
        </build>
        
        <!-- buildnumber-maven-plugin에서 SVN에 접근하기 위한 connection 지정. VCS에 따라 connection의 값을 변경해야 한다. -->
        <scm>
            <connection>scm:svn:http://{VCS Address & Repository Name}</connection>
        </scm>
    
    </project>
    



    5. Reference

    maven life cycle

    http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html


    maven resource filtering

    http://maven.apache.org/plugins/maven-resources-plugin/examples/filter.html


    buildnumber plugin

    http://mojo.codehaus.org/buildnumber-maven-plugin/


    maven timestamp plugin

    https://code.google.com/p/maven-timestamp-plugin/


    m2e plugin execution not covered

    http://wiki.eclipse.org/M2E_plugin_execution_not_covered


    댓글

Designed by Tistory.