====== Gradle Dependencies ======
* [[http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html|Dependency Management Basic]]
* [[http://gradle.org/docs/current/userguide/dependency_management.html|Dependency Management]]
===== 의존성 소개 =====
* Gradle은 이행적(transitive) 의존성 관리를 지원한다.
* [[http://maven.apache.org/|Maven]]과 [[http://maven.apache.org/http://ant.apache.org/ivy/|Ivy]]를 지원한다.
* 이행적 의존성이 아닌 일반 파일로 저장된 외부 라이브러리도 지원한다.
* 빌드 스크립트에서 직접 의존성을 지정한다.
===== 의존성 설정 기본 =====
group, name, version 순서로 써줄 수 있다.
dependencies {
compile group: 'org.hibernate', name: 'hibernate-core', version: '3.6.7.Final'
}
// 짧게 쓰면 "group:name:version"
dependencies {
compile 'org.hibernate:hibernate-core:3.6.7.Final'
}
===== 리포지토리의 기본 =====
Maven과 Ivy 리포지토리를 지원한다.
* 가장 기본 Maven 리포지토리
repositories {
mavenCentral()
}
* 기타 Maven 리포지토리 URL 지정
repositories {
maven {
url "http://repo.mycompany.com/maven2"
}
}
* ''maven {}''을 여러번 사용하는 것이 가능하다.
* 사용자 정의 리포지토리에 접속 계정정보 추가
repositories {
maven {
credentials {
username 'user'
password 'password'
}
url "http://repo.mycompany.com/maven2"
}
}
* ''jCenter()'' [[https://bintray.com/bintray/jcenter|Bintray jCenter]]를 리포지토리로 추가(1.7)
repositories {
jcenter()
}
===== 의존성 관리 Best Practices =====
==== jar 이름에 버전 붙이기 ====
* Manifest 파일에 버전을 넣어 두기도 한다. 그래도..
* jar 파일 자체에 그 일부로 버전번호를 명시하는 것이 좋다.
* 이행적 의존성 관리를 사용할 경우 jar 파일에 버전 명시는 필수이다.
* 어느 라이브러리 버전을 사용하는지 모르면, 찾기 어려운 버그를 만나기가 쉽다.
==== 이행적 의존성 관리를 사용하라 ====
* 이행적 의존성 관리를 사용하지 않으면 최상위 의존성을 삭제할 경우 그것이 의존하는 다른 라이브러리가 무엇인지 몰라서 불필요한 jar가 계속 쌓이게 된다.
* Gradle은 Maven/Ivy 아니라도 일반 jar 파일에 대한 의존성도 지원한다.
==== 버전 충돌 ====
* 동일한 jar의 서로 다른 버전 충돌은 정확히 찾아내어 해결해야 한다.
* 이행적 의존성 관리를 하지 않으면 버전 충돌을 알아내기 힘들다.
* 서로 다른 의존성은 서로 다른 버전의 다른 라이브러리에 의존하기 마련이고, 이 경우 버전 충돌이 일어난다.
* Gradle이 제공하는 충돌 방지 전략
* 최신 우선 : 가장 최신 의존성이 기본적으로 사용된다.
* 빠른 실패 : 버전 충돌이 일어나면 빠른 시간안에 실패한다. 이렇게 되면 개발자 스스로 충돌을 제어할 수 있게 된다. [[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.ResolutionStrategy.html|ResolutionStrategy]] 참조.
* 버전 충돌을 커스터마이징할 수 있게 Gradle을 만들어가고 있다.
* 버전 충돌 해결 방법
* 충돌이 발생하는 라이브러리를 최상위 의존성으로 버전을 명시하여 강제(forced) 지정한다. [[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.html|DependencyHandler]] 참조.
* 아무 의존성(이행적이든 아니든)이든 강제로 지정한다.
==== 동적 버전(Dynamic Version) 결정과 변하는 모듈(Changing Module) ====
* 때로는 특정 의존 라이브러리에 대해 항상 최신 버전을 사용하거나, 혹은 특전 버전대(2.x 버전대중에서) 최신을 사용하고 싶을 경우가 있다. 동적 버전을 통해 가능하다.
* 특정 버전대 : ''2.+''
* 사용 가능한 최신 버전 : ''latest.integration''
* 변하는 모듈 : 어떤 경우에는 동일 버전이라도 모듈이 변경되는 때가 있다(Maven의 SNAPSHOT, 특히 사내 프로젝트의 경우 이런게 많음).
* 동적 버전은 실제 버전이 변경되고, 변하는 모듈은 버전은 그대로이지만 그 내용물(jar)이 계속해서 변경될 수 있다.
* 기본적으로 동적 버전과 변하는 모듈은 24시간 캐시된다. 설정을 통해 바꿀 수 있다.
* 특정 라이브러리의 변경을 매번 검사해야 한다면 ''changing = true'' 옵션 추가
compile ('com.some:library:0.1') { changing = true }
* [[http://forums.gradle.org/gradle/topics/how_to_get_gradle_to_download_newer_snapshots_to_gradle_cache_when_using_an_ivy_repository#reply_8068039|*-SNAPSHOT 버전은 기본으로 changing=true 설정이 된다]]
* 단, Maven Repository 일 때만 그렇다(Maven 자체의 기본정책).
* Ivy Repository는 SNAPSHOT이라도 ''changing = true'' 설정이 필요하다.
==== ResolutionStrategy ====
[[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.ResolutionStrategy.html|ResolutionStrategy]]
경험상으로 볼 때 ''failOnVersionConflict()''를 설정하고 의존성을 관리하는 것이 좋아보인다. 나도 모르는 사이에 이행성에 의해 버전이 변하는 것을 방지할 수 있다.
configurations.all {
resolutionStrategy {
// 동일 모듈에 대한 버전 충돌시 즉시 오류 발생하고 실패.
failOnVersionConflict()
// 특정 모듈의 버전을 강제 지정(최상위건 이행적 의존성이건 무관함)
force 'asm:asm-all:3.3.1', 'commons-io:commons-io:1.4'
// 이미 강제 지정된 모듈 버전을 대체함
forcedModules = ['asm:asm-all:3.3.1']
// 동적 버전 탐색을 10분 캐시
cacheDynamicVersionsFor 10, 'minutes'
// 변하는 모듈(Changing Module)을 캐시하지 않음
cacheChangingModulesFor 0, 'seconds'
}
}
==== Java와 의존성 관리 ====
* 원칙적으로 자바에는 의존성 관리 기법이 존재하지 않기 때문에 Maven, Ivy 같은 비표준 솔루션이 만들어지게 되었다.
* Maven은 완전한 빌드 시스템이며, Ivy는 의존성 관리만 한다.
* Maven과 Ivy 모두 특정 jar에 대한 의존성 정보를 기술하는 XML 파일 기술자(descriptor)를 통해 의존성을 관리한다.
* Gradle 의존성 분석 엔진은 pom(Maven)과 ivy를 기술자를 모두 지원한다.
===== 의존성 구성(dependency configurations) =====
* Java에서 의존성은 [[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.ConfigurationContainer.html|ConfigurationContainer]] ''configurations''로 그룹화 된다. 구성의 각 그룹은 클래스패스를 의미한다.
* 많은 Gradle 플러그인들이 구성를 미리 정의해 두고 있다. 사용자가 스스로 자신만의 구성을 추가할 수 있다. 예를 들면 빌드시에는 필요없지만 배포는 같이 되야하는 추가 JDBC 드라이브 같은 것들.
* 프로젝트 구성은 [[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.ConfigurationContainer.html|ConfigurationContainer]] ''configurations'' 객체로 관리한다. configurations 객체에 클로저를 전달하면 이 클래스의 API가 호출된다.
* ''configurations'' 의 각 항목은 [[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.Configuration.html|Configuration]] 객체이다.
==== Java에서는 기본적으로 네 가지 configuration이 존재한다 ====
* ''compile'' : 프로젝트를 컴파일할 때 필요한 의존 라이브러리들
* ''runtime'' : 프로젝트를 실행할 때 필요한 의존 라이브러리들. 기본적으로 compile을 모두 포함한다.
* ''testCompile'' : 프로젝트의 테스트를 컴파일할 때 필요한 라이브러리들. 기본적으로 프로젝트의 컴파일된 클래스들과 compile 의존성을 포함한다.
* ''testRuntime'' : 프로젝트의 테스트를 실행할 때 필요한 라이브러리들. 기본적으로 compile, runtime, testCompile 의존성을 포함한다.
==== configuration 선언 ====
configurations {
compile
}
==== configuration 접근 ====
println configurations.compile.name
println configurations['compile'].name
==== 구성하기 ====
* ''configurations''의 항목은 [[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.Configuration.html|Configuration]] 객체이다.
configurations {
compile {
description = 'compile classpath'
transitive = true
}
runtime {
extendsFrom compile
}
}
configurations.compile {
description = 'compile classpath'
}
===== 의존성 설정하기 =====
* 의존성에는 여러가지 타입이 있다.
* [[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.html|DependencyHandler]]에 거의 모든 의존성 설정 방법의 예가 나온다..
==== 외부 모듈 의존성 ====
리포지토리에 있는 외부 모듈에 대한 의존성. 가장 일반적인 방식이다.
dependencies {
runtime group: 'org.springframework', name: 'spring-core', version: '2.5'
runtime 'org.springframework:spring-core:2.5', 'org.springframework:spring-aop:2.5'
runtime(
[group: 'org.springframework', name: 'spring-core', version: '2.5'],
[group: 'org.springframework', name: 'spring-aop', version: '2.5']
)
runtime('org.hibernate:hibernate:3.0.5') {
transitive = true
}
runtime group: 'org.hibernate', name: 'hibernate', version: '3.0.5', transitive: true
runtime(group: 'org.hibernate', name: 'hibernate', version: '3.0.5') {
transitive = true
}
}
* 외부 의존성 설정 방식은 두가지가 존재한다. 문자열 지정방식과 맵 지정 방식.
* [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/artifacts/ExternalModuleDependency.html|ExternalModuleDependency]]에서 더 많은 프라퍼티와 메소드를 볼 수 있다.
* 문자열 지정 방식은 "그룹:아티팩트이름:버전"을 지정할 수 있다.
* 맵 지정방식은 [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/artifacts/ExternalModuleDependency.html|ExternalModuleDependency]]의 모든 프라퍼티를 지정할 수 있다.
* [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/artifacts/Dependency.html|Dependency]]의 모든 API에 접근하려면 의존성 선언문에 클로저를 인자로 넘겨주고 그 안에서 호출하면 된다.
* 의존성이 선언되면 그에 맞는 기술자 파일(pom.xml, ivy.xml)을 찾고 그에 따라 해당 모듈의 artifact jar 파일과 그 의존하는 파일들을 다운로드 한다.
* 의존성 기술자 파일이 존재하지 않으면 바로 적합한 파일명을 구성하여(hibernate-3.0.5.jar 형태) 다운로드 한다.
* Maven에서는 모듈이 하나의 artifact만 존재하지만 Gradle과 Ivy는 하나의 모듈이 여러개의 artifact를 가질 수 있다. 각 artifact는 서로 다른 의존성을 가질 수 있다.
=== 다중 artifact 모듈에 의존한다면 ===
* Maven에서는 다중 artifact 모듈이 있을 수 없다.
* Gradle이 Ivy 리포지토리의 모듈에 의존할 경우 이런 상황이 발생할 수 있다.
* Gradle에서 Ivy 모듈에 의존성을 지정할 경우, 정확히는 해당 모듈의 ''default'' 구성에 의존성을 지정한 것이다.
* 따라서 실제 의존하게 되는 artifact(보통은 *.jar)는 해당 모듈의 ''default'' 구성에 첨부된 모든 artifact들이다.
* ''default'' 구성에 원치않는 artifact가 있을 경우에 해당 모듈을 빼도록 의존성을 구성해야 한다.
* 필요한 artifact가 ''default''가 아닌 다른 구성에 들어있을 경우 어떤 구성의 artifact를 가져올지 명시해줘야 한다.
* 자세한 사항은 [[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.html|DependencyHandler]]를 참조한다.
=== 해당 Artifact만 가져오기 지정 ===
* 특정 모듈을 가져올 때 원칙적으로는 해당 모듈이 의존하는 다른 모듈까지 모두 가져오게 된다.
* 이 때 해당 모듈의 지정된 artifact(jar)만 가져오고 의존성은 무시하게 만들 수 있다.
* 문자열 지정법에서는 끝에 **@확장자**를 지정한다. 보통은 ''@jar''가 된다. (artifact 파일이 zip 일수도 있다)
* 맵 지정법에서는 ''ext'' 키에 확장자를 지정한다.
* 이런일이 가능하게 되는 이유는 의존성을 탐색할 때 지정된 확장자의 파일만 다운로드하고 ''pom.xml''이나 ''ivy.xml''같은 모듈 기술자 파일은 다운로드하지 말라는 뜻이기 때문이다. 모듈 기술자 파일을 받지 않았으므로 의존성을 분석할 수 없기 때문에 해당 모듈의 해당 확장자 파일만 받게 된다. 이미 다운로드 받은 모듈 기술자는 무시한다.
dependencies {
runtime "org.groovy:groovy:1.8.7@jar"
runtime group: 'org.groovy', name: 'groovy', version: '1.8.7', ext: 'jar'
}
=== 분류자 Classifier ===
* Maven에는 분류자(classifer)가 존재한다.
* 분류자는 @ 확장자 지정자와 함께 사용할 수 있다.
compile "org.gradle.test.classifiers:service:1.0:jdk15@jar"
otherConf group: 'org.gradle.test.classifiers', name: 'service', version: '1.0', classifier: 'jdk15'
=== 특정 구성의 외부 의존성 목록 보기 ===
다음 태스크를 만들고 ''gradle -q listJars''로 실행한다. 태스크 없이 ''gradle dependencies''만 해도 된다.
task listJars << {
configurations.compile.each { File file -> println file.name }
}
==== configuration 다루기 ====
* [[https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.Configuration.html|Configuration]] (''configurations.compile'' 객체 같은 것)
* 특정 configuration의 전체 의존성 정보를 보려면 ''configurations.[configurationName].resolvedConfiguration.resolvedArtifacts'' 를 이터레이션 돌면 된다. [[https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/ResolvedArtifact.html|ResolvedArtifact]]
* ''resolvedArtifact.getModuleVersion().getId()''는 [[https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/ModuleVersionIdentifier.html|ModuleVersionIdentifier]] 객체이며 여기에 모듈에 관한 group, name, version 정보가 들어있다.
* 따라서 아래와 같이 이터레이션을 돌면 의존성 정보를 알 수 있다.
configurations.runtime.resolvedConfiguration.resolvedArtifacts.each { raf ->
def cid = raf.getModuleVersion().getId()
println "Dependency for runtime ${cid.group}:${cid.name}:${cid.version}"
}
==== 클라이언트 모듈 의존성 ====
* 클라이언트 모듈 의존성은 빌드 스크립트에서 직접 이행적 의존성을 선언할 수 있게 해준다. 즉, pom.xml 같은 모듈 기술자를 대체하는 기법이다.
* 아래 설정에서는 현재 프로젝트가 groovy에 의존하지만, groovy 자체의 의존성은 무시하고 빌드 파일에서 직접 지정한 의존성을 따르도록 한다.
dependencies {
runtime module("org.codehaus.groovy:groovy-all:1.8.7") {
dependency("commons-cli:commons-cli:1.0") {
transitive = false
}
module(group: 'org.apache.ant', name: 'ant', version: '1.8.4') {
dependencies "org.apache.ant:ant-launcher:1.8.4@jar", "org.apache.ant:ant-junit:1.8.4"
}
}
}
* [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/artifacts/ClientModule.html|ClientModule]] 참조.
* 현시점(gradle 1.2)에서는 클라이언트 모듈 의존성을 사용한 프로젝트를 Maven 등의 리포지토리에 올릴 경우 클라이언트 모듈 의존성은 무시된다.
==== 프로젝트 의존성 ====
* 멀티 프로젝트에서 다른 프로젝트에 대한 의존성을 설정할 수 있다.
dependencies {
compile project(':shared') // shared 프로젝트에 의존
}
* [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/artifacts/ProjectDependency.html|ProjectDependency]] 참조.
==== 파일 의존성 ====
* 파일 의존성을 사용하면 jar 파일을 리포지토리에 넣지 않고도 의존성에 추가하는 것이 가능하다.
* [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/file/FileCollection.html|FileCollection]] 을 인자로 넘기면 된다.
dependencies {
runtime files('libs/a.jar', 'libs/b.jar')
runtime files('dir/to/classes') // 디렉토리 자체를 클래스패스에 추가
runtime rootProject.files('root-project-dir') // rootProject의 디렉토리를 클래스패스에 추가
runtime fileTree(dir: 'libs', include: '*.jar')
}
* 파일 의존성은 프로젝트를 리포지토리에 배포할 경우에는 의존성으로 추가되지 않는다.
* 동일 빌드 내에서는 파일 의존성이 이행적으로 추가된다. 즉, 현재 프로젝트를 다른 서브 프로젝트에서 의존하면 현재 프로젝트에 대한 파일 의존성도 함께 추가된다.
* 파일이 원래 존재하는 것이 아니라 태스크를 통해 생성되는 경우 파일 의존성에 해당하는 파일들을 어느 태스크에서 생성하는지도 명시할 수 있다.
dependencies {
compile files("$buildDir/classes") {
builtBy 'compile' // 'compile' 태스크에 의해 클래스 파일들 생성됨.
}
}
task compile << {
println 'compiling classes'
}
task list(dependsOn: configurations.compile) << {
println "classpath = ${configurations.compile.collect {File file -> file.name}}"
}
==== Gradle API 의존성 ====
Gradle 태스크나 플러그인을 만들 경우에 현재 Gradle API([[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.html#org.gradle.api.artifacts.dsl.DependencyHandler:gradleApi()|DependencyHandler.gradleApi()]]에 대해 의존성을 지정할 수 있다.
dependencies {
compile gradleApi()
}
==== 로컬 Groovy 의존성 ====
Gradle 과 함께 배포되는 Groovy에 대한 의존성([[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.html#org.gradle.api.artifacts.dsl.DependencyHandler:localGroovy()|DependencyHandler.localGroovy()]])을 지정할 수 있다. 마찬가지로 Gradle 태스크나 플러그인을 만들때 사용할 수 있다.
dependencies {
groovy localGroovy()
}
==== 이행적 의존성 제외하기 ====
이행적 의존성 중에서 일부는 제외하도록 설정할 수 있다.
// 구성 단위 제외
configurations {
compile.exclude module: 'commons' // compile configuration에서 특정 모듈 제외
all*.exclude group: 'org.gradle.test.excludes', module: 'reports' // 모든 configuration에서 특정 모듈 제외
}
// 의존성 단위 제외
dependencies {
compile("org.gradle.test.excludes:api:1.0") {
exclude module: 'shared' // 특정 의존성에 대해선만 모듈 제외.
exclude group: 'groupId', module: 'artifactId'
}
}
* 특정 구성(configuration)에서 이행적 의존성을 제거하면 의존성 구성을 분석하거나 해당 구성을 상속할 때 그 의존성은 제외된다.
* 모든 구성에서 제외시킬 때는 ''all*''으로 Groovy의 spread-dot 연산자를 사용한다.
* 제외할 의존성 지정시에는 의존성의 이름만 지정(''module: '이름''')하거나 그룹 이름(''group: '그룹이름''')만 지정하거나 혹은 둘다 함께 지정할 수 있다.
* 자세한 것은 [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/artifacts/Dependency.html|Dependency]]와 [[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.Configuration.html|Configuration]]을 참조한다.
* 모든 이행적 의존성이 제외 가능한 것은 아니다. 없어도 되는 의존성인지 주의깊게 살펴보고 지정해야 한다.
* 의존성 제외가 필요한 경우
* 의존성 제외가 아닌 다른 방식으로 해결 가능한지 항상 고려한다.
* 라이센스 때문에 해당 모듈을 빼야한다.
* 어떠한 원격 리포지토리에서도 받아 올 수 없는 모듈이다.
* 실행 시점에는 필요 없는 모듈이다.
* 의존성에 지정된 버전이 다른 버전과 충돌한다. 이때는 버전 충돌 부분으로 해결하도록 한다.
* 대부분의 경우 의존성 제외는 구성 단위로 해야 한다. 그래야 더 명시적이다.
* 의존성 단위 제외의 경우 구성의 다른 의존성에서 제외했던 모듈을 다시 의존할 경우 무용지물이 된다.
* 의존성 제외는 [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/artifacts/ModuleDependency.html|ModuleDependency]]를 참고한다.
==== 선택적 속성들 ====
* 의존성의 ''name''을 제외한 모든 속성은 선택적이다. 리토지토리 타입에 따라 어떤 속성은 꼭 필요하기도 하고 그렇지 않기도 한다.
* Maven : group, name, version 필수
* 파일 시스템 리포지토리 : name 혹은 name과 version
* 선택적 속성 예
dependencies {
runtime ":junit:4.10", ":testng"
runtime name: 'testng'
}
* 구성에 의존성의 배열을 지정할 수도 있다.
List groovy = ["org.codehaus.groovy:groovy-all:1.8.7@jar",
"commons-cli:commons-cli:1.0@jar",
"org.apache.ant:ant:1.8.4@jar"]
List hibernate = ['org.hibernate:hibernate:3.0.5@jar', 'somegroup:someorg:1.0@jar']
dependencies {
runtime groovy, hibernate // 컬렉션을 의존성에 추가함
}
==== 의존성의 구성 ====
* Gradle의 의존성은 여러가지 구성(configurations)을 가질 수 있다.
* 지정하지 않으면 기본 구성을 사용한다.
* Maven에는 기본 구성밖에 없다.
* Ivy에는 의존성에 여러 구성을 둘 수 있다.
dependencies {
runtime group: 'org.somegroup', name: 'somedependency', version: '1.0', configuration: 'someConfiguration'
}
* Gradle의 서브 프로젝트에 대한 의존성을 지정할 때는 다음과 같이한다.
dependencies {
compile project(path: ':api', configuration: 'spi')
}
==== 의존성 보고서 ====
* 명령행에서 의존성 확인하기 : ''gradle -q dependencies 서브프로젝트:dependencies''
* [[gradle:report|Gradle Report Plugin]]으로 리포트를 생성할 수도 있다.
* 의존성 확인은 API로 만들어져 있다.
* [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/artifacts/ResolvedConfiguration.html#getResolutionResult()|ResolvedConfiguration.getResolutionResult()]]
* [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/artifacts/result/ResolutionResult.html|ResolutionResult]]
===== 모든 이행적 의존성에서 제외시키기 =====
* [[http://java.dzone.com/articles/gradle-goodness-exclude|Exclude Transitive Dependency from All Configurations]]
configurations {
all*.exclude group: 'xml-apis', module: 'xmlParserAPIs'
}
// Equivalent to:
configurations {
all.collect { configuration ->
configuration.exclude group: 'xml-apis', module: 'xmlParserAPIs'
}
}
===== 의존성 사용하여 작업하기 =====
- 다음과 같은 의존성 설정이 있다고 할 때
configurations {
sealife
alllife
}
dependencies {
sealife "sea.mammals:orca:1.0", "sea.fish:shark:1.0", "sea.fish:tuna:1.0"
alllife configurations.sealife // sealife에서 상속 받음
alllife "air.birds:albatros:1.0"
}
- shark-1.0 -> seal-2.0, tuna-1.0
- orca-1.0 -> seal-1.0
- tuna-1.0 -> herring-1.0
- 각 구성의 의존성에 다음과 같이 접근할 수 있다.
task dependencies << {
configurations.alllife.dependencies.each { dep -> println dep.name }
println()
configurations.alllife.allDependencies.each { dep -> println dep.name }
println()
configurations.alllife.allDependencies.findAll { dep -> dep.name != 'orca' }.each { dep -> println dep.name }
}
- [[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.Configuration.html#org.gradle.api.artifacts.Configuration:allDependencies|allDependencies]]는 해당 구성의 부모에 있는 의존성까지 포함한 모든 의존성이다.
- [[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.Configuration.html#org.gradle.api.artifacts.Configuration:dependencies|dependencies]]는 해당 구성에만 속한(부모에 속한 것 제외) 의존성들이다.
- 구성에 속한 의존성의 모든 파일 객체는 ''files()'' 메소드로 접근할 수 있다.
task allFiles << {
configurations.sealife.files.each { file ->
println file.name
}
}
- ''Configuration.files'' 메소드는 해당 구성의 모든 artifact를 가져온다.
- 구성을 복사할 수 있다. 클로저로 복사될 대상을 지정할 수 있다.
task copy << {
configurations.alllife.copyRecursive { dep -> dep.name != 'orca' }.allDependencies.each { dep ->
println dep.name
}
println()
configurations.alllife.copy().allDependencies.each { dep ->
println dep.name
}
}
- ''copy()'' 메소드는 해당 구성에 속한 의존성만 복사한다.
- ''copyRecursive()'' 메소드는 상속한 부모의 구성의 의존성까지 복사한다.
- 복사된 구성은 원본과 항상 같지는 않다. 복사 대상 서브셋의 의존성간에 버전 충돌이 있을 경우 최종 복사된 의존성과 원본 의존상간에 차이가 발생할 수 있다.
task copyVsFiles << {
configurations.sealife.copyRecursive { dep -> dep.name == 'orca' }.each { file ->
println file.name
}
println()
// 위와는 다른 내용 출력
configurations.sealife.files { dep -> dep.name == 'orca' }.each { file ->
println file.name
}
}
// 결과
> gradle -q copyVsFiles
orca-1.0.jar
seal-1.0.jar
orca-1.0.jar
seal-2.0.jar
- ''orca''는 ''seal-1.0''에 의존하고 있는 반면, ''shark''는 ''seal-2.0''에 의존하고 있는 상태.
- 원본 구성은 버전 충돌이 있고, 기본 행동법칙에 따라 최신 버전인 ''seal-2.0''을 사용한다. 따라서 원본을 사용하는 ''files()''메소드는 ''orca''에 대한 이행적 의존성 판단 결과로 ''seal-2.0''을 선택한다.
- 복제된 구성에서는 ''orca''의 의존성만 복제했으며 따라서 버전 충돌이 없고 ''seal-1.0''이 선택된다.
- 구성이 한번 결정되면 변경할 수 없다. 이 상태를 변경하려고 하면 예외가 발생한다.
- 변경하고자 할경우 구성 복사를 사용한다. 복사된 구성은 결정되기 전 상태이며 따라서 변경 후 결정이 가능하다.
- 더 자세히 알고자 한다면 [[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.Configuration.html|Configuration]] 참조.
===== 리포지토리(Repositories) 자세히 =====
* Gradle의 리포지토리 관리는 [[http://ant.apache.org/ivy/|Apache Ivy]]에 기반하고 있다.
* Gradle은 여러 종류의 리포지토리를 지원하며 각각 독립적으로 다뤄진다.
* Gradle은 특정 리포지토리에서 모듈 기술자를 만나면 동일 리포지토리에서 해당 모듈의 artifact를 다운로드한다.
* 모듈의 메타 정보와 artifact는 동일 리포지토리의 동일 위치에 있어야 하지만, 여러 URL을 가진 단일 리포지토리를 만드는 것은 가능하다.
==== Maven 중앙 리포지토리 ====
* [[http://repo1.maven.org/maven2|Maven 중앙(central) 리포지토리]]를 탐색하도록 지정돼 있다.
repositories {
mavenCentral()
}
==== 로컬 Maven 리포지토리 ====
* 로컬 Maven 캐시를 리포지토리로 사용한다.
* 개발시 SNAPSHOT 버전 등을 Local에서 바로 받아오거나 할 때 편리하다. [[http://java.dzone.com/articles/deploying-artifact-local-cache|Deploying an Artifact to the Local Cache in Gradle]]
repositories {
mavenLocal()
}
* ''settings.xml''이 존재하면 이에 따라 로컬 리포지토리를 판단한다.
* ''$USER_HOME/.m2/repository'' 기본값
* ''$USER_HOME/.m2/settings.xml'' 우선
* ''$M2_HOME/conf/settings.xml'' 차선
==== Maven 사용자 정의 리포지토리 ====
* 사용자가 직접 리포지토리를 만들어서 지정할 수 있다.
repositories {
maven {
url "http://repo.mycompany.com/maven2"
}
}
* 때로는 POM 위치와 JAR위치가 다를 수 있는데 그럴 때는 ''artifactUrls''를 지정해 JAR를 찾을 수 있게 해준다.
repositories {
maven {
// POM과 jar등의 artifact 탐색
url "http://repo2.mycompany.com/maven2"
// 위에서 artifact가 없으면 다음에서 검색
artifactUrls "http://repo.mycompany.com/jars"
artifactUrls "http://repo.mycompany.com/jars2"
}
}
=== 인증 필요한 Maven 리포지토리 접근 ===
repositories {
maven {
credentials {
username 'user'
password 'password'
}
url "http://repo.mycompany.com/maven2"
}
}
==== 단일 디렉토리 리포지토리 ====
* 파일시스템의 단일 디렉토리를 리포지토리로 사용할 수 있다.
repositories {
flatDir {
dirs 'lib'
}
flatDir {
dirs 'lib1', 'lib2'
}
}
* 일반적으로는 ''name''만 지정해주면 된다.
repositories {
mavenCentral()
flatDir {
dirs 'libs'
}
}
/* libs 디렉토리에 imap.jar, smtp.jar, hibernate-jpa-2.0-api-1.0.0.Final.jar 가 있을 때 */
dependencies {
compile name: 'imap' // 혹은 compile ':imap'
compile name: 'smtp' // 혹은 compile ':smtp'
// 혹은 compile ':hibernate-jpa-2.0-api:1.0.0.Final'
compile name: 'hibernate-jpa-2.0-api', version: '1.0.0.Final'
}
task listJars << {
configurations.compile.each { File file -> println file.name}
}
* ''gradle -q listJars'' 실행 결과
imap.jar
smtp.jar
hibernate-jpa-2.0-api-1.0.0.Final.jar
==== Ivy 리포지토리 ====
* 표준 레이아웃으로 Ivy 리포지토리를 사용할 수 있다. [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/artifacts/repositories/IvyArtifactRepository.html|IvyArtifactRepository]] 참조.
repositories {
ivy {
url "http://repo.mycompany.com/repo"
layout "maven"
}
}
=== Ivy 리포지토리 사용자 정의 패턴 ===
* 비표준 레이아웃을 사용할 경우 해당 리포지토리의 패턴 레이아웃을 지정할 수 있다.
repositories {
ivy {
url "http://repo.mycompany.com/repo"
layout 'pattern', {
artifact "[module]/[revision]/[artifact].[ext]"
ivy "[module]/[revision]/ivy.xml"
}
}
}
=== Ivy 리포지토리에 서로 다른 artfact와 ivy 파일 위치를 지정하기 ===
* Ivy 리포지토리에서 ivy 파일과 artifact를 서로 다른 위치에서 가져오도록 하고자 할 경우에는 ivy와 artifact의 완전한 URL 패턴을 명시해주면 된다.
repositories {
ivy {
artifactPattern "http://repo.mycompany.com/3rd-party-artifacts/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
artifactPattern "http://repo.mycompany.com/company-artifacts/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
ivyPattern "http://repo.mycompany.com/ivy-files/[organisation]/[module]/[revision]/ivy.xml"
}
}
* artifactPattern과 ivyPattern의 값은 완전한 URL이어야 한다. 상대 URL불가.
* 불완전한 URL은 프로젝트에 상대적인 파일 경로로 간주된다.
=== 인증이 필요한 Ivy 리포지토리 ===
repositories {
ivy {
credentials {
username 'user'
password 'password'
}
artifactPattern "http://repo.mycompany.com/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
}
}
==== 리포지토리 다루기 ====
- 리포지토리 객체에 접근 하려면
println repositories.localRepository.name
println repositories['localRepository'].name
- 리포지토리 구성하기
repositories {
flatDir {
name 'localRepository'
}
}
repositories {
localRepository {
dirs 'lib'
}
}
repositories.localRepository {
dirs 'lib'
}
===== 의존성 결정은 어떻게 이뤄지는가? =====
Gradle은 의존성과 선언과 리포지토리 정의를 읽어서 의존성 결정 과정(dependency resolution)을 통해 모든 의존하는 파일을 다운로드한다. 그 과정은 다음과 같다.
- 지정된 의존성에 대해 Gradle은 처음에 의존성의 **모듈**을 결정한다. 각 저장소를 순서대로 탐색하여 처음에는 모듈 기술자 파일(pom 혹은 ivy)을 찾는다. 파일이 존재하면 모듈이 존재한다는 의미이다. 모듈 기술자 파일이 없다면 모듈의 아티팩트 파일(jar)이 존재하는지 찾을 것이다.
- 의존성이 동적 버전(''1.+'' 형태)으로 지정돼 있다면, Gradle이 가장 최신의 정적 버전을 리토지토리에서 찾아서 정한다. Maven에서는 ''maven-metadata.xml'' 파일로, Ivy에서는 디렉토리의 파일 목록을 확인해서 결정한다.
- 모듈 기술자가 ''pom'' 파일이고 부모 pom이 선언돼 있다면, Gradle은 해당 pom의 부모까지 재귀적으로 탐색하여 정하게 된다.
- 일단 모든 리포지토리에서 모듈을 다 탐색했으면 Gradle은 그 중에서 가장 사용에 적합한 것을 선택한다. 이 작업은 다음과 같은 조건에 따라 이뤄진다.
- 동적 버전에서는 낮은 버전보다는 더 높은 버전이 선택된다.
- 모듈 기술자 파일(pom/ivy)가 있는 모듈이 artifact 파일만 있는 모듈보다 우선 선택된다.
- 상단에 선언된 리포지토리에 있는 모듈이 하단에 선언된 리포지토리에 있는 것보다 우선 선택된다.
- 의존성이 정적 버전으로 선언 돼 있고 모듈 기술자 파일이 리포지토리에 존재할 경우에는 그 이후의 리포지토리로는 탐색을 하지 않는다.
- 모듈의 모든 artifact들은 위의 프로세스를 거쳐 선택된것과 동일한 리포지토리에서 받는다.
===== 의존성 캐시 =====
Gradle의 의존성 캐시는 두가지 키 타입의 스토리지로 이뤄져 있다.
* jar,pom,ivy 파일등을 포함한 다운로드한 artifact 파일을 저장. 다운로드한 artifact의 저장소 경로는 SHA1 체크섬을 포함하고 있으며 이 때문에 이름은 같지만 내용이 다른 2개의 artifact가 저장될 수도 있다.
* 결정된 모듈의 메타 정보(결정된 동적 버전, 모듈 기술자, artifact 포함)를 바이너리로 저장.
==== 캐시 관련 명령행 옵션 ====
=== 오프라인 ===
''%%--%%offline'' 스위치는 재검사할 필요가 있는지 여부와 무관하게 항상 캐시를 사용해 의존성을 결정하도록 한다. Gradle이 의존성 결정때문에 네트워크에 접근하는 일은 생기지 않는다. 필요한 모듈이 캐시에 없으면 빌드가 실패한다.
=== 캐시 갱신 ===
리포지토리 설정 상태에 따라 캐시의 싱크가 안 맞을 수도 있다. 리포지토리를 잘못 설정했거나, 변하지 않는 모듈을 잘못 배포했을 수 있다.
''%%--%%refresh-dependencies'' 옵션을 사용하면 모든 의존성 캐시를 갱신한다. 이 옵션을 사용하면 결정된 모듈과 artifact의 모든 캐시된 항목들을 무시한다. 모든 리포지토리에 대해 의존성 결정을 다시 수행하고 다운로드한다.
==== 의존성 캐시 세부 설정 ====
[[http://gradle.org/docs/current/dsl/org.gradle.api.artifacts.ResolutionStrategy.html|ResolutionStrategy]]를 통해 캐시 세부 설정이 가능하다.
* 기본적으로 동적 버전은 24시간동안 캐싱한다. 이를 변경하려면
configurations.all {
resolutionStrategy.cacheDynamicVersionsFor 10, 'minutes' // 10분
}
* 변하는 모듈도 24시간 캐시한다. 이를 변경하려면
configurations.all {
resolutionStrategy.cacheChangingModulesFor 30, 'days' // 30일
}
===== 이행적(transitive) 의존성 관리 전략 =====
* Maven Central이 아닌 사설 리포지토리를 운영하라. Maven Central은 느리고 가끔 다운될 수 있다.
* 리포지토리가 존재하지 않는 외부 라이브러리는 파일 시스템이 저장하여 버전 컨트롤 시스템으로 관리한다.
===== provided =====
* Gradle 2.x 에서 최종적으로 ''**compileOnly**'' Scope가 생겼다.
* [[https://github.com/gradle/gradle/blob/master/design-docs/provided-dependencies.md|Gradle Provided Dependencies design]]
* 따라서 아래 방법들은 Gradle 구버전에서만 사용하고 최신 버전에서는 ''compileOnly''를 사용한다.
* ''compileOnly'' 는 의존성이 test 로 이행되지 않기 때문에 test 에는 ''testCompile'' 의존성으로 지정하거나 혹은 아래와 같이 강제로 이행처리를 해야한다. [[https://discuss.gradle.org/t/compileonly-dependencies-are-not-available-in-tests/15366/6|compileOnly dependencies are not available in tests]]
sourceSets {
test.compileClasspath += configurations.compileOnly
test.runtimeClasspath += configurations.compileOnly
}
// 혹은
configurations {
testImplementation.extendsFrom compileOnly
}
아래 모든 방법들을 사용하기 보다는 [[gradle:web|Gradle Web(War) Plugin]] 에 나오는 exclude 방식을 추천.
* [[http://www.sinking.in/blog/provided-scope-in-gradle/|Provided Scope in Gradle | Sinking In]] 참조 문서
* Spring Source에서 [[gradle:propdeps|propdeps plugin]]을 공개한 상태라서 아래와 같은 방식은 불필요해졌다.
* [[https://github.com/nebula-plugins/gradle-extra-configurations-plugin|Nebula Gradle extra configurations plugin]]도 optional, provided 지원.
컴파일시에는 클래스패스에 넣지만, 실행/배포시에는 빠져야 하는 의존성이 있을 수 있다. 예를 들면 Servlet API 같은 것들이 그렇다. Servlet API는 Tomcat등의 WAS에 내장되어 있으므로 배포는 할 필요가 없다.
Gradle 1.2는 현재 [[gradle:web|Gradle Web(War) Plugin]]이 아닐경우에 ''provided''를 제공해주고 있지 않다. 하지만 이를 흉내낼 수 있다. [[http://issues.gradle.org/browse/GRADLE-784|[#GRADLE-784] Provide a 'provided' configuration]] 참조.
configurations {
provided
}
/* 배포시에는 빠지고, 컴파일시에는 들어가는 의존성을 provided에 지정한다. */
sourceSets {
main {
compileClasspath += configurations.provided
}
test {
compileClasspath += configurations.provided
}
}
// war 프로젝트일 경우
war {
classpath -= configurations.provided
}
[[gradle:eclipse|Gradle Ecplise Plugin]] 사용시 조정이 필요하다.
eclipse {
classpath {
// 클래스패스에는 넣지만...
plusConfigurations += configurations.provided
// .classpath 파일에서 해당 jar의 classpathentry@export를 false로 변경
noExportConfigurations += configurations.provided
// 현재 Gradle 1.2는 noExportConfigurations를 설정해도 WTP에서 export되는 문제가 있다.
// 이 문제는 멀티 프로젝트일 경우에만 발생한다. 멀티 프로젝트 아니라며 다음은 불필요함.
// .classpath XML에서 @exported == false|null인 classpathentry의
// "org.eclipse.jst.component.dependency" 삭제해야 한다
file.withXml { provider ->
provider.asNode().classpathentry.findAll {
it.@kind == 'lib' && (it.@exported == null || it.@exported == 'false')
}.each { cpe ->
def attributes = cpe.children()[0];
if (attributes == null) { return }
def componentDependency = attributes.attribute.find { it.@name == 'org.eclipse.jst.component.dependency'}
if (componentDependency == null) { return }
attributes.remove(componentDependency)
}
}
}
wtp {
component {
// WTP Deployment Assembly component 에서는 뺀다. 'war' 플러그인이 적용되지 않은 상태에서는 사용 할 수 없다.
minusConfigurations += configurations.provided
}
}
}
===== 의존하고 있는 라이브러리를 모두 모으기 =====
[[http://forums.gradle.org/gradle/topics/how_can_i_gather_all_my_projects_dependencies_into_a_folder|Gradle Community Forums - How can I gather all my project's dependencies into a folder?]]
task copyToLib(type: Copy) {
into "$buildDir/output/lib"
from configurations.runtime
}
===== dependencyInsight =====
* [[http://java.dzone.com/articles/gradle-goodness-getting-more|Gradle Goodness: Getting More Dependency Insight]]
* 특정 의존성에 관해 상세 정보 출력
./gradlew dependencyInsight --configuration testCompile --dependency junit
===== CompositeBuild =====
* [[https://docs.gradle.org/current/userguide/composite_builds.html|Gralde Composite Build]] 여러 프로젝트 연관관계가 있을 때 다른 의존 프로젝트를 리포지토리에 올리지 않고 로컬에 있는 상태로 의존할 수 있게 해준다.
* [[https://github.com/gradle/gradle/tree/c7da24ebff119eacc8899ce1c609dd0d426f1243/subprojects/docs/src/samples/compositeBuilds/basic|gradle/subprojects/docs/src/samples/compositeBuilds/basic at c7da24ebff119eacc8899ce1c609dd0d426f1243 · gradle/gradle]]
* [[https://blog.jetbrains.com/idea/2017/03/webinar-recording-composite-builds-with-gradle/|Webinar Recording: Composite Builds with Gradle and IntelliJ IDEA 2017.1 | IntelliJ IDEA Blog]]
* ''settings.gradle''에서
includeBuild '../another_project''
===== Source Dependency =====
* [[https://dzone.com/articles/introducing-source-dependencies-in-gradle|Introducing Source Dependencies in Gradle - DZone Java]]
===== 참조할 DSL =====
* [[http://www.gradle.org/docs/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:configurations(groovy.lang.Closure)|Project.configurations()]]
* [[http://www.gradle.org/docs/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:repositories(groovy.lang.Closure)|Project.repositories()]]
* [[http://www.gradle.org/docs/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:dependencies(groovy.lang.Closure)|Project.dependencies()]]
* [[http://www.gradle.org/docs/current/javadoc/org/gradle/api/artifacts/Dependency.html|Dependency]]
* [[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.ConfigurationContainer.html|ConfigurationContainer]]
* [[http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.Configuration.html|Configuration]]
===== Dependency Management Plugin =====
https://github.com/spring-gradle-plugins/dependency-management-plugin