Support for custom repository

A dependency or a dependency group may now declare a repository in case
it is not available in Maven central

Closes gh-125
This commit is contained in:
Stephane Nicoll
2015-07-09 13:52:21 +02:00
parent f42e72f319
commit 4e868b053f
18 changed files with 300 additions and 43 deletions

View File

@@ -20,6 +20,7 @@ import groovy.util.logging.Slf4j
import io.spring.initializr.metadata.BillOfMaterials
import io.spring.initializr.metadata.Dependency
import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.metadata.Repository
import io.spring.initializr.metadata.Type
import io.spring.initializr.util.Version
import io.spring.initializr.util.VersionRange
@@ -62,6 +63,8 @@ class ProjectRequest {
final List<BillOfMaterials> boms = []
final Map<String, Repository> repositories = [:]
def facets = []
def build
@@ -116,6 +119,12 @@ class ProjectRequest {
boms << metadata.configuration.env.boms[bomId]
}
}
if (it.repository) {
String repositoryId = it.repository
if (!repositories[repositoryId]) {
repositories[repositoryId] = metadata.configuration.env.repositories[repositoryId]
}
}
}
if (this.type) {
@@ -133,6 +142,11 @@ class ProjectRequest {
this.applicationName = metadata.configuration.generateApplicationName(this.name)
}
if (!'RELEASE'.equals(requestedVersion.qualifier.qualifier)) {
repositories['spring-snapshots'] = metadata.configuration.env.repositories['spring-snapshots']
repositories['spring-milestones'] = metadata.configuration.env.repositories['spring-milestones']
}
afterResolution(metadata)
}

View File

@@ -74,6 +74,9 @@ class DependenciesCapability extends ServiceCapability<List<DependencyGroup>> {
if (!dependency.bom && group.bom) {
dependency.bom = group.bom
}
if (!dependency.repository && group.repository) {
dependency.repository = group.repository
}
dependency.resolve()
indexDependency(dependency.id, dependency)

View File

@@ -61,6 +61,8 @@ class Dependency extends MetadataElement {
String bom
String repository
void setScope(String scope) {
if (!SCOPE_ALL.contains(scope)) {
throw new InvalidInitializrMetadataException("Invalid scope $scope must be one of $SCOPE_ALL")

View File

@@ -45,6 +45,13 @@ class DependencyGroup {
@JsonIgnore
String bom
/**
* The default repository to associate to all dependencies of this group unless
* specified otherwise.
*/
@JsonIgnore
String repository
final List<Dependency> content = []
}

View File

@@ -15,6 +15,7 @@
*/
package io.spring.initializr.metadata
/**
* Various configuration options used by the service.
*
@@ -117,6 +118,18 @@ class InitializrConfiguration {
*/
final Map<String, BillOfMaterials> boms = [:]
/**
* The {@link Repository} instances that are referenced in this instance.
*/
final Map<String, Repository> repositories = [:]
Env() {
repositories['spring-snapshots'] = new Repository(name: 'Spring Snapshots',
url: new URL('https://repo.spring.io/snapshot'), snapshotsEnabled: true)
repositories['spring-milestones'] = new Repository(name: 'Spring Milestones',
url: new URL('https://repo.spring.io/milestone'), snapshotsEnabled: false)
}
void setArtifactRepository(String artifactRepository) {
if (!artifactRepository.endsWith('/')) {
artifactRepository = artifactRepository + '/'
@@ -135,6 +148,11 @@ class InitializrConfiguration {
boms[id] = bom
}
}
other.repositories.each { id, repo ->
if (!repositories[id]) {
repositories[id] = repo
}
}
}
}

View File

@@ -94,6 +94,11 @@ class InitializrMetadata {
throw new InvalidInitializrMetadataException("Dependency $dependency " +
"defines an invalid BOM id $dependency.bom, available boms $boms")
}
def repositories = configuration.env.repositories
if (dependency.repository && !repositories[dependency.repository]) {
throw new InvalidInitializrMetadataException("Dependency $dependency " +
"defines an invalid repository id $dependency.repository, available repositores $repositories")
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.metadata
/**
* Define a repository to be represented in the generated project
* if a dependency refers to it.
*
* @author Stephane Nicoll
* @since 1.0
*/
class Repository {
String name
URL url
boolean snapshotsEnabled
}

View File

@@ -30,9 +30,8 @@ sourceCompatibility = ${javaVersion}
targetCompatibility = ${javaVersion}
repositories {
mavenCentral()<% if (!bootVersion.contains("RELEASE")) { %>
maven { url "https://repo.spring.io/snapshot" }
maven { url "https://repo.spring.io/milestone" }<% } %>
mavenCentral()<% if (repositories) { repositories.each { key, repo -> %>
maven { url "${repo.url}" }<% }} %>
}
<% if (packaging=='war') { %>configurations {

View File

@@ -107,26 +107,18 @@
<extensions>true</extensions>
</plugin><% } %>
</plugins>
</build><% if (!bootVersion.contains("RELEASE")) { %>
<repositories>
</build>
<% if (repositories) { %>
<repositories><% repositories.each { key, repo -> %>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<id>${key}</id>
<name>${repo.name}</name>
<url>${repo.url}</url>
<snapshots>
<enabled>true</enabled>
<enabled>${repo.snapshotsEnabled}</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</repository><% } %>
</repositories><% } %><% if (!bootVersion.contains("RELEASE")) { %>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
@@ -144,7 +136,6 @@
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<% } %>
</pluginRepositories><% } %>
</project>

View File

@@ -247,6 +247,13 @@ class ProjectGeneratorTests {
.hasSpringBootStarterDependency('web')
}
@Test
void gradleBuildWithBootSnapshot() {
def request = createProjectRequest('web')
request.bootVersion = '1.0.1.BUILD-SNAPSHOT'
generateGradleBuild(request).hasSnapshotRepository()
}
@Test
void gradleBuildWithCustomVersion() {
def whatever = new Dependency(id: 'whatever', groupId: 'org.acme', artifactId: 'whatever', version: '1.2.3')
@@ -364,6 +371,44 @@ class ProjectGeneratorTests {
.contains("mavenBom \"org.acme:foo-bom:1.2.3\"")
}
@Test
void mavenRepository() {
def foo = new Dependency(id: 'foo', groupId: 'org.acme', artifactId: 'foo', repository: 'foo-repo')
def metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup('foo', foo)
.addRepository('foo-repo', 'foo', 'http://example.com/repo', false).build()
projectGenerator.metadata = metadata
def request = createProjectRequest('foo')
generateMavenPom(request).hasDependency(foo)
.hasRepository('foo-repo', 'foo', 'http://example.com/repo', false)
}
@Test
void mavenRepositoryWithSeveralDependenciesOnSameRepository() {
def foo = new Dependency(id: 'foo', groupId: 'org.acme', artifactId: 'foo', repository: 'the-repo')
def bar = new Dependency(id: 'bar', groupId: 'org.acme', artifactId: 'bar', repository: 'the-repo')
def metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup('group', foo, bar)
.addRepository('the-repo', 'repo', 'http://example.com/repo', true).build()
projectGenerator.metadata = metadata
def request = createProjectRequest('foo', 'bar')
generateMavenPom(request).hasDependency(foo)
.hasRepository('the-repo', 'repo', 'http://example.com/repo', true)
.hasRepositoriesCount(1)
}
@Test
void gradleRepository() {
def foo = new Dependency(id: 'foo', groupId: 'org.acme', artifactId: 'foo', repository: 'foo-repo')
def metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup('foo', foo)
.addRepository('foo-repo', 'foo', 'http://example.com/repo', false).build()
projectGenerator.metadata = metadata
def request = createProjectRequest('foo')
generateGradleBuild(request)
.hasRepository('http://example.com/repo')
}
PomAssert generateMavenPom(ProjectRequest request) {
def content = new String(projectGenerator.generateMavenPom(request))
new PomAssert(content).validateProjectRequest(request)

View File

@@ -131,6 +131,21 @@ class DependenciesCapabilityTest {
assertEquals 'da-bom', capability.get('second').bom
}
@Test
void addDefaultRepository() {
def first = new Dependency(id: 'first')
def second = new Dependency(id: 'second', repository: 'da-repo')
def group = createDependencyGroup('test', first, second)
group.repository = 'test-repo'
DependenciesCapability capability = new DependenciesCapability()
capability.content << group
capability.validate()
assertEquals 'test-repo', capability.get('first').repository
assertEquals 'da-repo', capability.get('second').repository
}
private static DependenciesCapability createDependenciesCapability(String groupName, Dependency... dependencies) {
DependenciesCapability capability = new DependenciesCapability()

View File

@@ -105,6 +105,26 @@ class InitializrMetadataBuilderTests {
assertEquals '4.5.6.RELEASE', anotherBom.version
}
@Test
void mergeMetadataWithRepository() {
def metadata = InitializrMetadataBuilder.create().withInitializrMetadata(
new ClassPathResource('metadata/config/test-repository.json')).build()
def repositories = metadata.configuration.env.repositories
assertEquals 4, repositories.size() // 2 standard repos
Repository myRepo = repositories['my-repo']
assertNotNull myRepo
assertEquals 'my repo', myRepo.name
assertEquals new URL('http://example.com/my'), myRepo.url
assertEquals true, myRepo.snapshotsEnabled
Repository anotherRepo = repositories['another-repo']
assertNotNull anotherRepo
assertEquals 'another repo', anotherRepo.name
assertEquals new URL('http://example.com/another'), anotherRepo.url
assertEquals false, anotherRepo.snapshotsEnabled
}
@Test
void mergeConfigurationDisabledByDefault() {
def config = load(new ClassPathResource("application-test-default.yml"))

View File

@@ -42,4 +42,17 @@ class InitializrMetadataTests {
builder.build()
}
@Test
void invalidRepository() {
def foo = new Dependency(id: 'foo', groupId: 'org.acme', artifactId: 'foo', repository: 'foo-repo')
InitializrMetadataTestBuilder builder = InitializrMetadataTestBuilder
.withDefaults().addRepository('my-repo', 'repo', 'http://example.com/repo', true)
.addDependencyGroup('test', foo);
thrown.expect(InvalidInitializrMetadataException)
thrown.expectMessage("foo-repo")
thrown.expectMessage("my-repo")
builder.build()
}
}

View File

@@ -60,6 +60,14 @@ class GradleBuildAssert {
contains("targetCompatibility = $javaVersion")
}
GradleBuildAssert hasSnapshotRepository() {
contains('https://repo.spring.io/snapshot')
}
GradleBuildAssert hasRepository(String url) {
contains("maven { url \"$url\" }")
}
GradleBuildAssert contains(String expression) {
assertTrue "$expression has not been found in gradle build $content", content.contains(expression)
this

View File

@@ -22,6 +22,7 @@ import io.spring.initializr.metadata.Dependency
import io.spring.initializr.metadata.DependencyGroup
import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.metadata.InitializrMetadataBuilder
import io.spring.initializr.metadata.Repository
import io.spring.initializr.metadata.Type
/**
@@ -149,4 +150,13 @@ class InitializrMetadataTestBuilder {
this
}
InitializrMetadataTestBuilder addRepository(String id, String name, String url, boolean snapshotsEnabled) {
builder.withCustomizer {
Repository repo = new Repository(
name: name, url: new URL(url), snapshotsEnabled: snapshotsEnabled)
it.configuration.env.repositories[id] = repo
}
this
}
}

View File

@@ -19,6 +19,7 @@ package io.spring.initializr.test
import io.spring.initializr.generator.ProjectRequest
import io.spring.initializr.metadata.BillOfMaterials
import io.spring.initializr.metadata.Dependency
import io.spring.initializr.metadata.Repository
import org.custommonkey.xmlunit.SimpleNamespaceContext
import org.custommonkey.xmlunit.XMLUnit
import org.custommonkey.xmlunit.XpathEngine
@@ -41,6 +42,7 @@ class PomAssert {
final Document doc
final Map<String, Dependency> dependencies = [:]
final Map<String, BillOfMaterials> boms = [:]
final Map<String, Repository> repositories = [:]
PomAssert(String content) {
eng = XMLUnit.newXpathEngine()
@@ -51,6 +53,7 @@ class PomAssert {
doc = XMLUnit.buildControlDocument(content)
parseDependencies()
parseBoms()
parseRepositories()
}
/**
@@ -165,21 +168,34 @@ class PomAssert {
}
PomAssert hasSnapshotRepository() {
hasRepository('spring-snapshots')
hasRepository('spring-snapshots', 'Spring Snapshots',
'https://repo.spring.io/snapshot', true)
hasPluginRepository('spring-snapshots')
this
}
def hasRepository(String name) {
def nodes = eng.getMatchingNodes(createRootNodeXPath('repositories/pom:repository/pom:id'), doc)
for (int i = 0; i < nodes.length; i++) {
if (name.equals(nodes.item(i).textContent)) {
return
}
PomAssert hasRepository(String id, String name, String url, Boolean snapshotsEnabled) {
Repository repository = repositories[id]
assertNotNull "No repository found with '$id' --> ${repositories.keySet()}", repository
if (name) {
assertEquals "Wrong name for $repository", name, repository.name
}
throw new IllegalArgumentException("No repository found with id $name")
if (url) {
assertEquals "Wrong url for $repository", new URL(url), repository.url
}
if (snapshotsEnabled) {
assertEquals "Wrong snapshots enabled flag for $repository", snapshotsEnabled, repository.snapshotsEnabled
}
this
}
PomAssert hasRepositoriesCount(int count) {
assertEquals "Wrong number of declared repositories -->'${repositories.keySet()}",
count, repositories.size()
this
}
def hasPluginRepository(String name) {
def nodes = eng.getMatchingNodes(createRootNodeXPath('pluginRepositories/pom:pluginRepository/pom:id'), doc)
for (int i = 0; i < nodes.length; i++) {
@@ -258,6 +274,37 @@ class PomAssert {
}
}
private def parseRepositories() {
def nodes = eng.getMatchingNodes(createRootNodeXPath('repositories/pom:repository'), doc)
for (int i = 0; i < nodes.length; i++) {
def item = nodes.item(i)
if (item instanceof Element) {
def repository = new Repository()
def element = (Element) item
def type = element.getElementsByTagName('id')
def id = type.item(0).textContent
def name = element.getElementsByTagName('name')
if (name.length > 0) {
repository.name = name.item(0).textContent
}
def url = element.getElementsByTagName('url')
if (url.length > 0) {
repository.url = new URL(url.item(0).textContent)
}
def snapshots = element.getElementsByTagName('snapshots')
if (snapshots.length > 0) {
Element snapshotsElement = (Element) snapshots.item(0);
def snapshotsEnabled = snapshotsElement.getElementsByTagName('enabled')
if (snapshotsEnabled.length > 0) {
repository.snapshotsEnabled = snapshotsEnabled.item(0).textContent
}
}
assertFalse("Duplicate Repository with id $id", repositories.containsKey(id))
repositories[id] = repository
}
}
}
private static boolean isBom(def type, def scope) {
if (type.length == 0 || scope.length == 0) {
return false

View File

@@ -29,19 +29,29 @@
"title": "Spring Boot Version",
"type": "SINGLE_SELECT"
},
"configuration": {
"env": {
"artifactRepository": "https://repo.spring.io/release/",
"boms": {},
"fallbackApplicationName": "Application",
"forceSsl": true,
"invalidApplicationNames": [
"SpringApplication",
"SpringBootApplication"
],
"springBootMetadataUrl": "https://spring.io/project_metadata/spring-boot"
}
},
"configuration": {"env": {
"artifactRepository": "https://repo.spring.io/release/",
"boms": {},
"fallbackApplicationName": "Application",
"forceSsl": true,
"invalidApplicationNames": [
"SpringApplication",
"SpringBootApplication"
],
"repositories": {
"spring-milestones": {
"name": "Spring Milestones",
"snapshotsEnabled": false,
"url": "https://repo.spring.io/milestone"
},
"spring-snapshots": {
"name": "Spring Snapshots",
"snapshotsEnabled": true,
"url": "https://repo.spring.io/snapshot"
}
},
"springBootMetadataUrl": "https://spring.io/project_metadata/spring-boot"
}},
"dependencies": {
"content": [
{

View File

@@ -0,0 +1,18 @@
{
"configuration": {
"env": {
"repositories": {
"my-repo": {
"name": "my repo",
"url": "http://example.com/my",
"snapshotsEnabled": true
},
"another-repo": {
"name": "another repo",
"url": "http://example.com/another",
"snapshotsEnabled": false
}
}
}
}
}