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.BillOfMaterials
import io.spring.initializr.metadata.Dependency import io.spring.initializr.metadata.Dependency
import io.spring.initializr.metadata.InitializrMetadata import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.metadata.Repository
import io.spring.initializr.metadata.Type import io.spring.initializr.metadata.Type
import io.spring.initializr.util.Version import io.spring.initializr.util.Version
import io.spring.initializr.util.VersionRange import io.spring.initializr.util.VersionRange
@@ -62,6 +63,8 @@ class ProjectRequest {
final List<BillOfMaterials> boms = [] final List<BillOfMaterials> boms = []
final Map<String, Repository> repositories = [:]
def facets = [] def facets = []
def build def build
@@ -116,6 +119,12 @@ class ProjectRequest {
boms << metadata.configuration.env.boms[bomId] 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) { if (this.type) {
@@ -133,6 +142,11 @@ class ProjectRequest {
this.applicationName = metadata.configuration.generateApplicationName(this.name) 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) afterResolution(metadata)
} }

View File

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

View File

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

View File

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

View File

@@ -15,6 +15,7 @@
*/ */
package io.spring.initializr.metadata package io.spring.initializr.metadata
/** /**
* Various configuration options used by the service. * Various configuration options used by the service.
* *
@@ -117,6 +118,18 @@ class InitializrConfiguration {
*/ */
final Map<String, BillOfMaterials> boms = [:] 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) { void setArtifactRepository(String artifactRepository) {
if (!artifactRepository.endsWith('/')) { if (!artifactRepository.endsWith('/')) {
artifactRepository = artifactRepository + '/' artifactRepository = artifactRepository + '/'
@@ -135,6 +148,11 @@ class InitializrConfiguration {
boms[id] = bom 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 " + throw new InvalidInitializrMetadataException("Dependency $dependency " +
"defines an invalid BOM id $dependency.bom, available boms $boms") "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} targetCompatibility = ${javaVersion}
repositories { repositories {
mavenCentral()<% if (!bootVersion.contains("RELEASE")) { %> mavenCentral()<% if (repositories) { repositories.each { key, repo -> %>
maven { url "https://repo.spring.io/snapshot" } maven { url "${repo.url}" }<% }} %>
maven { url "https://repo.spring.io/milestone" }<% } %>
} }
<% if (packaging=='war') { %>configurations { <% if (packaging=='war') { %>configurations {

View File

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

View File

@@ -247,6 +247,13 @@ class ProjectGeneratorTests {
.hasSpringBootStarterDependency('web') .hasSpringBootStarterDependency('web')
} }
@Test
void gradleBuildWithBootSnapshot() {
def request = createProjectRequest('web')
request.bootVersion = '1.0.1.BUILD-SNAPSHOT'
generateGradleBuild(request).hasSnapshotRepository()
}
@Test @Test
void gradleBuildWithCustomVersion() { void gradleBuildWithCustomVersion() {
def whatever = new Dependency(id: 'whatever', groupId: 'org.acme', artifactId: 'whatever', version: '1.2.3') 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\"") .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) { PomAssert generateMavenPom(ProjectRequest request) {
def content = new String(projectGenerator.generateMavenPom(request)) def content = new String(projectGenerator.generateMavenPom(request))
new PomAssert(content).validateProjectRequest(request) new PomAssert(content).validateProjectRequest(request)

View File

@@ -131,6 +131,21 @@ class DependenciesCapabilityTest {
assertEquals 'da-bom', capability.get('second').bom 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) { private static DependenciesCapability createDependenciesCapability(String groupName, Dependency... dependencies) {
DependenciesCapability capability = new DependenciesCapability() DependenciesCapability capability = new DependenciesCapability()

View File

@@ -105,6 +105,26 @@ class InitializrMetadataBuilderTests {
assertEquals '4.5.6.RELEASE', anotherBom.version 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 @Test
void mergeConfigurationDisabledByDefault() { void mergeConfigurationDisabledByDefault() {
def config = load(new ClassPathResource("application-test-default.yml")) def config = load(new ClassPathResource("application-test-default.yml"))

View File

@@ -42,4 +42,17 @@ class InitializrMetadataTests {
builder.build() 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") contains("targetCompatibility = $javaVersion")
} }
GradleBuildAssert hasSnapshotRepository() {
contains('https://repo.spring.io/snapshot')
}
GradleBuildAssert hasRepository(String url) {
contains("maven { url \"$url\" }")
}
GradleBuildAssert contains(String expression) { GradleBuildAssert contains(String expression) {
assertTrue "$expression has not been found in gradle build $content", content.contains(expression) assertTrue "$expression has not been found in gradle build $content", content.contains(expression)
this this

View File

@@ -22,6 +22,7 @@ import io.spring.initializr.metadata.Dependency
import io.spring.initializr.metadata.DependencyGroup import io.spring.initializr.metadata.DependencyGroup
import io.spring.initializr.metadata.InitializrMetadata import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.metadata.InitializrMetadataBuilder import io.spring.initializr.metadata.InitializrMetadataBuilder
import io.spring.initializr.metadata.Repository
import io.spring.initializr.metadata.Type import io.spring.initializr.metadata.Type
/** /**
@@ -149,4 +150,13 @@ class InitializrMetadataTestBuilder {
this 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.generator.ProjectRequest
import io.spring.initializr.metadata.BillOfMaterials import io.spring.initializr.metadata.BillOfMaterials
import io.spring.initializr.metadata.Dependency import io.spring.initializr.metadata.Dependency
import io.spring.initializr.metadata.Repository
import org.custommonkey.xmlunit.SimpleNamespaceContext import org.custommonkey.xmlunit.SimpleNamespaceContext
import org.custommonkey.xmlunit.XMLUnit import org.custommonkey.xmlunit.XMLUnit
import org.custommonkey.xmlunit.XpathEngine import org.custommonkey.xmlunit.XpathEngine
@@ -41,6 +42,7 @@ class PomAssert {
final Document doc final Document doc
final Map<String, Dependency> dependencies = [:] final Map<String, Dependency> dependencies = [:]
final Map<String, BillOfMaterials> boms = [:] final Map<String, BillOfMaterials> boms = [:]
final Map<String, Repository> repositories = [:]
PomAssert(String content) { PomAssert(String content) {
eng = XMLUnit.newXpathEngine() eng = XMLUnit.newXpathEngine()
@@ -51,6 +53,7 @@ class PomAssert {
doc = XMLUnit.buildControlDocument(content) doc = XMLUnit.buildControlDocument(content)
parseDependencies() parseDependencies()
parseBoms() parseBoms()
parseRepositories()
} }
/** /**
@@ -165,21 +168,34 @@ class PomAssert {
} }
PomAssert hasSnapshotRepository() { PomAssert hasSnapshotRepository() {
hasRepository('spring-snapshots') hasRepository('spring-snapshots', 'Spring Snapshots',
'https://repo.spring.io/snapshot', true)
hasPluginRepository('spring-snapshots') hasPluginRepository('spring-snapshots')
this this
} }
def hasRepository(String name) { PomAssert hasRepository(String id, String name, String url, Boolean snapshotsEnabled) {
def nodes = eng.getMatchingNodes(createRootNodeXPath('repositories/pom:repository/pom:id'), doc) Repository repository = repositories[id]
for (int i = 0; i < nodes.length; i++) { assertNotNull "No repository found with '$id' --> ${repositories.keySet()}", repository
if (name.equals(nodes.item(i).textContent)) { if (name) {
return 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 hasPluginRepository(String name) {
def nodes = eng.getMatchingNodes(createRootNodeXPath('pluginRepositories/pom:pluginRepository/pom:id'), doc) def nodes = eng.getMatchingNodes(createRootNodeXPath('pluginRepositories/pom:pluginRepository/pom:id'), doc)
for (int i = 0; i < nodes.length; i++) { 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) { private static boolean isBom(def type, def scope) {
if (type.length == 0 || scope.length == 0) { if (type.length == 0 || scope.length == 0) {
return false return false

View File

@@ -29,19 +29,29 @@
"title": "Spring Boot Version", "title": "Spring Boot Version",
"type": "SINGLE_SELECT" "type": "SINGLE_SELECT"
}, },
"configuration": { "configuration": {"env": {
"env": { "artifactRepository": "https://repo.spring.io/release/",
"artifactRepository": "https://repo.spring.io/release/", "boms": {},
"boms": {}, "fallbackApplicationName": "Application",
"fallbackApplicationName": "Application", "forceSsl": true,
"forceSsl": true, "invalidApplicationNames": [
"invalidApplicationNames": [ "SpringApplication",
"SpringApplication", "SpringBootApplication"
"SpringBootApplication" ],
], "repositories": {
"springBootMetadataUrl": "https://spring.io/project_metadata/spring-boot" "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": { "dependencies": {
"content": [ "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
}
}
}
}
}