====== 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