사용자 도구

사이트 도구


gradle:dependencies

Gradle Dependencies

의존성 소개

  • Gradle은 이행적(transitive) 의존성 관리를 지원한다.
  • MavenIvy를 지원한다.
  • 이행적 의존성이 아닌 일반 파일로 저장된 외부 라이브러리도 지원한다.
  • 빌드 스크립트에서 직접 의존성을 지정한다.

의존성 설정 기본

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() Bintray jCenter를 리포지토리로 추가(1.7)
    repositories {
        jcenter()
    }

의존성 관리 Best Practices

jar 이름에 버전 붙이기

  • Manifest 파일에 버전을 넣어 두기도 한다. 그래도..
  • jar 파일 자체에 그 일부로 버전번호를 명시하는 것이 좋다.
  • 이행적 의존성 관리를 사용할 경우 jar 파일에 버전 명시는 필수이다.
  • 어느 라이브러리 버전을 사용하는지 모르면, 찾기 어려운 버그를 만나기가 쉽다.

이행적 의존성 관리를 사용하라

  • 이행적 의존성 관리를 사용하지 않으면 최상위 의존성을 삭제할 경우 그것이 의존하는 다른 라이브러리가 무엇인지 몰라서 불필요한 jar가 계속 쌓이게 된다.
  • Gradle은 Maven/Ivy 아니라도 일반 jar 파일에 대한 의존성도 지원한다.

버전 충돌

  • 동일한 jar의 서로 다른 버전 충돌은 정확히 찾아내어 해결해야 한다.
  • 이행적 의존성 관리를 하지 않으면 버전 충돌을 알아내기 힘들다.
  • 서로 다른 의존성은 서로 다른 버전의 다른 라이브러리에 의존하기 마련이고, 이 경우 버전 충돌이 일어난다.
  • Gradle이 제공하는 충돌 방지 전략
    • 최신 우선 : 가장 최신 의존성이 기본적으로 사용된다.
    • 빠른 실패 : 버전 충돌이 일어나면 빠른 시간안에 실패한다. 이렇게 되면 개발자 스스로 충돌을 제어할 수 있게 된다. ResolutionStrategy 참조.
    • 버전 충돌을 커스터마이징할 수 있게 Gradle을 만들어가고 있다.
  • 버전 충돌 해결 방법
    • 충돌이 발생하는 라이브러리를 최상위 의존성으로 버전을 명시하여 강제(forced) 지정한다. DependencyHandler 참조.
    • 아무 의존성(이행적이든 아니든)이든 강제로 지정한다.

동적 버전(Dynamic Version) 결정과 변하는 모듈(Changing Module)

  • 때로는 특정 의존 라이브러리에 대해 항상 최신 버전을 사용하거나, 혹은 특전 버전대(2.x 버전대중에서) 최신을 사용하고 싶을 경우가 있다. 동적 버전을 통해 가능하다.
  • 특정 버전대 : 2.+
  • 사용 가능한 최신 버전 : latest.integration
  • 변하는 모듈 : 어떤 경우에는 동일 버전이라도 모듈이 변경되는 때가 있다(Maven의 SNAPSHOT, 특히 사내 프로젝트의 경우 이런게 많음).
  • 동적 버전은 실제 버전이 변경되고, 변하는 모듈은 버전은 그대로이지만 그 내용물(jar)이 계속해서 변경될 수 있다.
  • 기본적으로 동적 버전과 변하는 모듈은 24시간 캐시된다. 설정을 통해 바꿀 수 있다.
  • 특정 라이브러리의 변경을 매번 검사해야 한다면 changing = true 옵션 추가
    compile ('com.some:library:0.1') { changing = true }
    • 단, Maven Repository 일 때만 그렇다(Maven 자체의 기본정책).
    • Ivy Repository는 SNAPSHOT이라도 changing = true 설정이 필요하다.

ResolutionStrategy

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에서 의존성은 ConfigurationContainer configurations로 그룹화 된다. 구성의 각 그룹은 클래스패스를 의미한다.
  • 많은 Gradle 플러그인들이 구성를 미리 정의해 두고 있다. 사용자가 스스로 자신만의 구성을 추가할 수 있다. 예를 들면 빌드시에는 필요없지만 배포는 같이 되야하는 추가 JDBC 드라이브 같은 것들.
  • 프로젝트 구성은 ConfigurationContainer configurations 객체로 관리한다. configurations 객체에 클로저를 전달하면 이 클래스의 API가 호출된다.
  • configurations 의 각 항목은 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 {
    compile {
        description = 'compile classpath'
        transitive = true
    }
    runtime {
        extendsFrom compile
    }
}
configurations.compile {
    description = 'compile classpath'
}

의존성 설정하기

  • 의존성에는 여러가지 타입이 있다.
  • 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
    }
}
  • 외부 의존성 설정 방식은 두가지가 존재한다. 문자열 지정방식과 맵 지정 방식.
  • ExternalModuleDependency에서 더 많은 프라퍼티와 메소드를 볼 수 있다.
  • 문자열 지정 방식은 “그룹:아티팩트이름:버전”을 지정할 수 있다.
  • 맵 지정방식은 ExternalModuleDependency의 모든 프라퍼티를 지정할 수 있다.
  • 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를 가져올지 명시해줘야 한다.
  • 자세한 사항은 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 다루기

  • Configuration (configurations.compile 객체 같은 것)
  • 특정 configuration의 전체 의존성 정보를 보려면 configurations.[configurationName].resolvedConfiguration.resolvedArtifacts 를 이터레이션 돌면 된다. ResolvedArtifact
    • resolvedArtifact.getModuleVersion().getId()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"
            }
        }
    }
  • ClientModule 참조.
  • 현시점(gradle 1.2)에서는 클라이언트 모듈 의존성을 사용한 프로젝트를 Maven 등의 리포지토리에 올릴 경우 클라이언트 모듈 의존성은 무시된다.

프로젝트 의존성

  • 멀티 프로젝트에서 다른 프로젝트에 대한 의존성을 설정할 수 있다.
    dependencies {
        compile project(':shared') // shared 프로젝트에 의존
    }

파일 의존성

  • 파일 의존성을 사용하면 jar 파일을 리포지토리에 넣지 않고도 의존성에 추가하는 것이 가능하다.
  • 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(DependencyHandler.gradleApi()에 대해 의존성을 지정할 수 있다.

dependencies {
    compile gradleApi()
}

로컬 Groovy 의존성

Gradle 과 함께 배포되는 Groovy에 대한 의존성(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: '그룹이름')만 지정하거나 혹은 둘다 함께 지정할 수 있다.
  • 자세한 것은 DependencyConfiguration을 참조한다.
  • 모든 이행적 의존성이 제외 가능한 것은 아니다. 없어도 되는 의존성인지 주의깊게 살펴보고 지정해야 한다.
  • 의존성 제외가 필요한 경우
    • 의존성 제외가 아닌 다른 방식으로 해결 가능한지 항상 고려한다.
    • 라이센스 때문에 해당 모듈을 빼야한다.
    • 어떠한 원격 리포지토리에서도 받아 올 수 없는 모듈이다.
    • 실행 시점에는 필요 없는 모듈이다.
    • 의존성에 지정된 버전이 다른 버전과 충돌한다. 이때는 버전 충돌 부분으로 해결하도록 한다.
  • 대부분의 경우 의존성 제외는 구성 단위로 해야 한다. 그래야 더 명시적이다.
  • 의존성 단위 제외의 경우 구성의 다른 의존성에서 제외했던 모듈을 다시 의존할 경우 무용지물이 된다.
  • 의존성 제외는 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')
    }

의존성 보고서

모든 이행적 의존성에서 제외시키기

configurations {
    all*.exclude group: 'xml-apis', module: 'xmlParserAPIs'
}
 
// Equivalent to:
configurations {
    all.collect { configuration ->
        configuration.exclude group: 'xml-apis', module: 'xmlParserAPIs'
    }
}

의존성 사용하여 작업하기

  1. 다음과 같은 의존성 설정이 있다고 할 때
    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"
    }
    1. shark-1.0 → seal-2.0, tuna-1.0
    2. orca-1.0 → seal-1.0
    3. tuna-1.0 → herring-1.0
  2. 각 구성의 의존성에 다음과 같이 접근할 수 있다.
    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 }
    }
    1. allDependencies는 해당 구성의 부모에 있는 의존성까지 포함한 모든 의존성이다.
    2. dependencies는 해당 구성에만 속한(부모에 속한 것 제외) 의존성들이다.
  3. 구성에 속한 의존성의 모든 파일 객체는 files() 메소드로 접근할 수 있다.
    task allFiles << {
        configurations.sealife.files.each { file ->
            println file.name
        }
    }
    1. Configuration.files 메소드는 해당 구성의 모든 artifact를 가져온다.
  4. 구성을 복사할 수 있다. 클로저로 복사될 대상을 지정할 수 있다.
    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
        }
    }
    1. copy() 메소드는 해당 구성에 속한 의존성만 복사한다.
    2. copyRecursive() 메소드는 상속한 부모의 구성의 의존성까지 복사한다.
  5. 복사된 구성은 원본과 항상 같지는 않다. 복사 대상 서브셋의 의존성간에 버전 충돌이 있을 경우 최종 복사된 의존성과 원본 의존상간에 차이가 발생할 수 있다.
    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
    1. orcaseal-1.0에 의존하고 있는 반면, sharkseal-2.0에 의존하고 있는 상태.
    2. 원본 구성은 버전 충돌이 있고, 기본 행동법칙에 따라 최신 버전인 seal-2.0을 사용한다. 따라서 원본을 사용하는 files()메소드는 orca에 대한 이행적 의존성 판단 결과로 seal-2.0을 선택한다.
    3. 복제된 구성에서는 orca의 의존성만 복제했으며 따라서 버전 충돌이 없고 seal-1.0이 선택된다.
  6. 구성이 한번 결정되면 변경할 수 없다. 이 상태를 변경하려고 하면 예외가 발생한다.
  7. 변경하고자 할경우 구성 복사를 사용한다. 복사된 구성은 결정되기 전 상태이며 따라서 변경 후 결정이 가능하다.
  8. 더 자세히 알고자 한다면 Configuration 참조.

리포지토리(Repositories) 자세히

  • Gradle의 리포지토리 관리는 Apache Ivy에 기반하고 있다.
  • Gradle은 여러 종류의 리포지토리를 지원하며 각각 독립적으로 다뤄진다.
  • Gradle은 특정 리포지토리에서 모듈 기술자를 만나면 동일 리포지토리에서 해당 모듈의 artifact를 다운로드한다.
  • 모듈의 메타 정보와 artifact는 동일 리포지토리의 동일 위치에 있어야 하지만, 여러 URL을 가진 단일 리포지토리를 만드는 것은 가능하다.

Maven 중앙 리포지토리

로컬 Maven 리포지토리

  • 로컬 Maven 캐시를 리포지토리로 사용한다.
  • 개발시 SNAPSHOT 버전 등을 Local에서 바로 받아오거나 할 때 편리하다. 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 리포지토리를 사용할 수 있다. 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]"
    }
}

리포지토리 다루기

  1. 리포지토리 객체에 접근 하려면
    println repositories.localRepository.name
    println repositories['localRepository'].name
  2. 리포지토리 구성하기
    repositories {
        flatDir {
            name 'localRepository'
        }
    }
    repositories {
        localRepository {
            dirs 'lib'
        }
    }
    repositories.localRepository {
        dirs 'lib'
    }

의존성 결정은 어떻게 이뤄지는가?

Gradle은 의존성과 선언과 리포지토리 정의를 읽어서 의존성 결정 과정(dependency resolution)을 통해 모든 의존하는 파일을 다운로드한다. 그 과정은 다음과 같다.

  1. 지정된 의존성에 대해 Gradle은 처음에 의존성의 모듈을 결정한다. 각 저장소를 순서대로 탐색하여 처음에는 모듈 기술자 파일(pom 혹은 ivy)을 찾는다. 파일이 존재하면 모듈이 존재한다는 의미이다. 모듈 기술자 파일이 없다면 모듈의 아티팩트 파일(jar)이 존재하는지 찾을 것이다.
    1. 의존성이 동적 버전(1.+ 형태)으로 지정돼 있다면, Gradle이 가장 최신의 정적 버전을 리토지토리에서 찾아서 정한다. Maven에서는 maven-metadata.xml 파일로, Ivy에서는 디렉토리의 파일 목록을 확인해서 결정한다.
    2. 모듈 기술자가 pom 파일이고 부모 pom이 선언돼 있다면, Gradle은 해당 pom의 부모까지 재귀적으로 탐색하여 정하게 된다.
  2. 일단 모든 리포지토리에서 모듈을 다 탐색했으면 Gradle은 그 중에서 가장 사용에 적합한 것을 선택한다. 이 작업은 다음과 같은 조건에 따라 이뤄진다.
    1. 동적 버전에서는 낮은 버전보다는 더 높은 버전이 선택된다.
    2. 모듈 기술자 파일(pom/ivy)가 있는 모듈이 artifact 파일만 있는 모듈보다 우선 선택된다.
    3. 상단에 선언된 리포지토리에 있는 모듈이 하단에 선언된 리포지토리에 있는 것보다 우선 선택된다.
    4. 의존성이 정적 버전으로 선언 돼 있고 모듈 기술자 파일이 리포지토리에 존재할 경우에는 그 이후의 리포지토리로는 탐색을 하지 않는다.
  3. 모듈의 모든 artifact들은 위의 프로세스를 거쳐 선택된것과 동일한 리포지토리에서 받는다.

의존성 캐시

Gradle의 의존성 캐시는 두가지 키 타입의 스토리지로 이뤄져 있다.

  • jar,pom,ivy 파일등을 포함한 다운로드한 artifact 파일을 저장. 다운로드한 artifact의 저장소 경로는 SHA1 체크섬을 포함하고 있으며 이 때문에 이름은 같지만 내용이 다른 2개의 artifact가 저장될 수도 있다.
  • 결정된 모듈의 메타 정보(결정된 동적 버전, 모듈 기술자, artifact 포함)를 바이너리로 저장.

캐시 관련 명령행 옵션

오프라인

--offline 스위치는 재검사할 필요가 있는지 여부와 무관하게 항상 캐시를 사용해 의존성을 결정하도록 한다. Gradle이 의존성 결정때문에 네트워크에 접근하는 일은 생기지 않는다. 필요한 모듈이 캐시에 없으면 빌드가 실패한다.

캐시 갱신

리포지토리 설정 상태에 따라 캐시의 싱크가 안 맞을 수도 있다. 리포지토리를 잘못 설정했거나, 변하지 않는 모듈을 잘못 배포했을 수 있다.

--refresh-dependencies 옵션을 사용하면 모든 의존성 캐시를 갱신한다. 이 옵션을 사용하면 결정된 모듈과 artifact의 모든 캐시된 항목들을 무시한다. 모든 리포지토리에 대해 의존성 결정을 다시 수행하고 다운로드한다.

의존성 캐시 세부 설정

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가 생겼다.
  • 따라서 아래 방법들은 Gradle 구버전에서만 사용하고 최신 버전에서는 compileOnly를 사용한다.
  • compileOnly 는 의존성이 test 로 이행되지 않기 때문에 test 에는 testCompile 의존성으로 지정하거나 혹은 아래와 같이 강제로 이행처리를 해야한다. compileOnly dependencies are not available in tests
sourceSets {
    test.compileClasspath += configurations.compileOnly
    test.runtimeClasspath += configurations.compileOnly
}

// 혹은
configurations {
    testImplementation.extendsFrom compileOnly
}

아래 모든 방법들을 사용하기 보다는 Gradle Web(War) Plugin 에 나오는 exclude 방식을 추천.

컴파일시에는 클래스패스에 넣지만, 실행/배포시에는 빠져야 하는 의존성이 있을 수 있다. 예를 들면 Servlet API 같은 것들이 그렇다. Servlet API는 Tomcat등의 WAS에 내장되어 있으므로 배포는 할 필요가 없다.

Gradle 1.2는 현재 Gradle Web(War) Plugin이 아닐경우에 provided를 제공해주고 있지 않다. 하지만 이를 흉내낼 수 있다. [#GRADLE-784] Provide a 'provided' configuration 참조.

configurations {
    provided
}
 
/* 배포시에는 빠지고, 컴파일시에는 들어가는 의존성을 provided에 지정한다. */
sourceSets {
    main {
        compileClasspath += configurations.provided
    }
    test {
        compileClasspath += configurations.provided
    }
}
 
// war 프로젝트일 경우
war {
    classpath -= configurations.provided
}

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" <attribute> 삭제해야 한다
        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
        }
    }
}

의존하고 있는 라이브러리를 모두 모으기

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

CompositeBuild

Source Dependency

참조할 DSL

Dependency Management Plugin

gradle/dependencies.txt · 마지막으로 수정됨: 2020/09/01 12:31 저자 kwon37xi