diff --git a/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/PegasusPluginIntegrationTest.groovy b/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/PegasusPluginIntegrationTest.groovy index 0fea14908d..4e7912326b 100644 --- a/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/PegasusPluginIntegrationTest.groovy +++ b/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/PegasusPluginIntegrationTest.groovy @@ -22,7 +22,7 @@ class PegasusPluginIntegrationTest extends Specification { .withProjectDir(tempDir.root) .withPluginClasspath() .withArguments('mainDataTemplateJar') - .forwardOutput() + //.forwardOutput() .build() then: diff --git a/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/publishing/PegasusPluginIvyPublishIntegrationTest.groovy b/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/publishing/PegasusPluginIvyPublishIntegrationTest.groovy index c1f57185bb..19978be45b 100644 --- a/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/publishing/PegasusPluginIvyPublishIntegrationTest.groovy +++ b/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/publishing/PegasusPluginIvyPublishIntegrationTest.groovy @@ -28,7 +28,17 @@ class PegasusPluginIvyPublishIntegrationTest extends Specification { localIvyRepo = localRepo.newFolder('local-ivy-repo').toURI().toURL() } - def 'publishes and consumes dataTemplate configurations'() { + /** + * Regression test illustrating how to consume software components published using the modern Ivy publishing mechanism, + * augmented by Gradle Module Metadata. + * + *
By requesting a named capability instead of a specific configuration name, we can consume pegasus + * artifacts in a forward-compatible manner. No special rules are required for the consumer. + + *
For more about Gradle Module Metadata, see Understanding Gradle Module Metadata. + * + */ + def 'publishes and consumes dataTemplate configurations with Gradle Module Metadata'() { given: def gradlePropertiesFile = grandparentProject.newFile('gradle.properties') gradlePropertiesFile << ''' @@ -54,8 +64,6 @@ class PegasusPluginIvyPublishIntegrationTest extends Specification { | pegasusPlugin files(${System.getProperty('integTest.pegasusPluginDependencies')}) |} | - |//tasks.withType(GenerateModuleMetadata) { enabled=false } - | |//modern ivy-publish configuration |publishing { | publications { @@ -85,7 +93,7 @@ class PegasusPluginIvyPublishIntegrationTest extends Specification { .withProjectDir(grandparentProject.root) .withPluginClasspath() .withArguments('publish', '-is') //uploadDataTemplate - .forwardOutput() + //.forwardOutput() //.withDebug(true) def grandparentResult = grandparentRunner.build() @@ -135,7 +143,6 @@ class PegasusPluginIvyPublishIntegrationTest extends Specification { | dataTemplateCompile files(${System.getProperty('integTest.dataTemplateCompileDependencies')}) | pegasusPlugin files(${System.getProperty('integTest.pegasusPluginDependencies')}) | - | //dataModel group: 'com.linkedin.pegasus-grandparent-demo', name: 'grandparent', version: '1.0.0', configuration: 'dataTemplate' | dataModel ('com.linkedin.pegasus-grandparent-demo:grandparent:1.0.0') { | capabilities { | requireCapability('com.linkedin.pegasus-grandparent-demo:grandparent-data-template:1.0.0') // TODO Gradle 6.0 requires an explicit version, 6.? does not @@ -143,8 +150,6 @@ class PegasusPluginIvyPublishIntegrationTest extends Specification { | } |} | - |//tasks.withType(GenerateModuleMetadata) { enabled=false } - | |//modern ivy-publish configuration |publishing { | publications { @@ -175,7 +180,7 @@ class PegasusPluginIvyPublishIntegrationTest extends Specification { .withProjectDir(parentProject.root) .withPluginClasspath() .withArguments('publish', '-is') - .forwardOutput() + //.forwardOutput() //.withDebug(true) def parentResult = parentRunner.build() @@ -225,7 +230,6 @@ class PegasusPluginIvyPublishIntegrationTest extends Specification { | dataTemplateCompile files(${System.getProperty('integTest.dataTemplateCompileDependencies')}) | pegasusPlugin files(${System.getProperty('integTest.pegasusPluginDependencies')}) | - | //dataModel group: 'com.linkedin.pegasus-parent-demo', name: 'parent', version: '1.0.0', configuration: 'dataTemplate' | dataModel ('com.linkedin.pegasus-parent-demo:parent:1.0.0') { | capabilities { | requireCapability('com.linkedin.pegasus-parent-demo:parent-data-template:1.0.0') // TODO Gradle 6.0 requires an explicit version, 6.? does not @@ -233,8 +237,6 @@ class PegasusPluginIvyPublishIntegrationTest extends Specification { | } |} | - |//tasks.withType(GenerateModuleMetadata) { enabled=false } - | |//modern ivy-publish configuration |publishing { | publications { @@ -269,8 +271,287 @@ class PegasusPluginIvyPublishIntegrationTest extends Specification { .withProjectDir(childProject.root) .withPluginClasspath() .withArguments('publish', '-is') //uploadDataTemplate - .forwardOutput() - .withDebug(true) + //.forwardOutput() + //.withDebug(true) + + def childResult = childRunner.build() + + then: + childResult.task(':compileMainGeneratedDataTemplateJava').outcome == TaskOutcome.SUCCESS + childResult.task(':generateDescriptorFileForIvyPublication').outcome == TaskOutcome.SUCCESS + + def childProjectIvyDescriptor = new File(localIvyRepo.path, 'com.linkedin.pegasus-child-demo/child/1.0.0/ivy-1.0.0.xml') + childProjectIvyDescriptor.exists() + def childProjectIvyDescriptorContents = childProjectIvyDescriptor.text + def expectedChildContents = new File(Thread.currentThread().contextClassLoader.getResource('ivy/modern/expectedChildIvyDescriptorContents.txt').toURI()).text + childProjectIvyDescriptorContents.contains expectedChildContents + + def childProjectPrimaryArtifact = new File(localIvyRepo.path, 'com.linkedin.pegasus-child-demo/child/1.0.0/child-1.0.0.jar') + childProjectPrimaryArtifact.exists() + //NB note naming scheme of data-template jar changes when classifier, not appendix, is used + def childProjectDataTemplateArtifact = new File(localIvyRepo.path, 'com.linkedin.pegasus-child-demo/child/1.0.0/child-1.0.0-data-template.jar') + childProjectDataTemplateArtifact.exists() + + assertZipContains(childProjectDataTemplateArtifact, 'com/linkedin/child/Photo.class') + assertZipContains(childProjectDataTemplateArtifact, 'pegasus/com/linkedin/child/Photo.pdl') + } + + /** + * Regression test illustrating how to consume software components published using the modern Ivy format. + * + *
By requesting a named capability instead of a specific configuration name, we can consume pegasus + * artifacts in a forward-compatible manner. + * + * Note that, in order to derive information about the capabilities of a software component, we must augment + * the consumer logic with a ComponentMetadataRule. + * + *
Unlike the above test, Gradle Module Metadata is not published or consumed. + */ + def 'publishes and consumes dataTemplate configurations without Gradle Module Metadata'() { + given: + def gradlePropertiesFile = grandparentProject.newFile('gradle.properties') + gradlePropertiesFile << ''' + |group=com.linkedin.pegasus-grandparent-demo + |version=1.0.0 + |'''.stripMargin() + + def settingsFile = grandparentProject.newFile('settings.gradle') + settingsFile << "rootProject.name = 'grandparent'" + + grandparentProject.newFile('build.gradle') << """ + |plugins { + | id 'ivy-publish' + | id 'pegasus' + |} + | + |repositories { + | mavenCentral() + |} + | + |dependencies { + | dataTemplateCompile files(${System.getProperty('integTest.dataTemplateCompileDependencies')}) + | pegasusPlugin files(${System.getProperty('integTest.pegasusPluginDependencies')}) + |} + | + |tasks.withType(GenerateModuleMetadata) { enabled=false } + | + |//modern ivy-publish configuration + |publishing { + | publications { + | ivy(IvyPublication) { + | from components.java + | } + | } + | repositories { + | ivy { url '$localIvyRepo' } + | } + |} + """.stripMargin() + + // Create a simple pdl schema, borrowed from restli-example-api + def schemaFilename = 'LatLong.pdl' + def grandparentPegasusDir = grandparentProject.newFolder('src', 'main', 'pegasus', 'com', 'linkedin', 'grandparent') + def grandparentPdlFile = new File("$grandparentPegasusDir.path$File.separator$schemaFilename") + grandparentPdlFile << '''namespace com.linkedin.grandparent + | + |record LatLong { + | latitude: optional float + | longitude: optional float + |}'''.stripMargin() + + when: + def grandparentRunner = GradleRunner.create() + .withProjectDir(grandparentProject.root) + .withPluginClasspath() + .withArguments('publish', '-is') //uploadDataTemplate + //.forwardOutput() + //.withDebug(true) + + def grandparentResult = grandparentRunner.build() + + then: + grandparentResult.task(':compileMainGeneratedDataTemplateJava').outcome == TaskOutcome.SUCCESS + grandparentResult.task(':generateDescriptorFileForIvyPublication').outcome == TaskOutcome.SUCCESS + + def grandparentProjectIvyDescriptor = new File(localIvyRepo.path, 'com.linkedin.pegasus-grandparent-demo/grandparent/1.0.0/ivy-1.0.0.xml') + grandparentProjectIvyDescriptor.exists() + def grandparentProjectIvyDescriptorContents = grandparentProjectIvyDescriptor.text + def expectedGrandparentContents = new File(Thread.currentThread().contextClassLoader.getResource('ivy/modern/expectedGrandparentIvyDescriptorContents.txt').toURI()).text + grandparentProjectIvyDescriptorContents.contains expectedGrandparentContents + + def grandparentProjectPrimaryArtifact = new File(localIvyRepo.path, 'com.linkedin.pegasus-grandparent-demo/grandparent/1.0.0/grandparent-1.0.0.jar') + grandparentProjectPrimaryArtifact.exists() + //NB note naming scheme of data-template jar changes when classifier, not appendix, is used + def grandparentProjectDataTemplateArtifact = new File(localIvyRepo.path, 'com.linkedin.pegasus-grandparent-demo/grandparent/1.0.0/grandparent-1.0.0-data-template.jar') + grandparentProjectDataTemplateArtifact.exists() + + assertZipContains(grandparentProjectDataTemplateArtifact, 'com/linkedin/grandparent/LatLong.class') + assertZipContains(grandparentProjectDataTemplateArtifact, 'pegasus/com/linkedin/grandparent/LatLong.pdl') + + when: 'a parent project consumes the grandparent project data-template jar' + + gradlePropertiesFile = parentProject.newFile('gradle.properties') + gradlePropertiesFile << ''' + |group=com.linkedin.pegasus-parent-demo + |version=1.0.0 + |'''.stripMargin() + + settingsFile = parentProject.newFile('settings.gradle') + settingsFile << "rootProject.name = 'parent'" + + parentProject.newFile('build.gradle') << """ + |plugins { + | id 'ivy-publish' + | id 'pegasus' + |} + | + |repositories { + | ivy { url '$localIvyRepo' } + | mavenCentral() + |} + | + |dependencies { + | dataTemplateCompile files(${System.getProperty('integTest.dataTemplateCompileDependencies')}) + | pegasusPlugin files(${System.getProperty('integTest.pegasusPluginDependencies')}) + | + | dataModel ('com.linkedin.pegasus-grandparent-demo:grandparent:1.0.0') { + | capabilities { + | requireCapability('com.linkedin.pegasus-grandparent-demo:grandparent-data-template:1.0.0') // TODO Gradle 6.0 requires an explicit version, 6.? does not + | } + | } + | + | components.all(com.linkedin.pegasus.gradle.rules.PegasusIvyVariantDerivationRule) + |} + | + |tasks.withType(GenerateModuleMetadata) { enabled=false } + | + |//modern ivy-publish configuration + |publishing { + | publications { + | ivy(IvyPublication) { + | from components.java + | } + | } + | repositories { + | ivy { url '$localIvyRepo' } + | } + |} + """.stripMargin() + + // Create a simple pdl schema which references a grandparent type + schemaFilename = 'EXIF.pdl' + def parentPegasusDir = parentProject.newFolder('src', 'main', 'pegasus', 'com', 'linkedin', 'parent') + def parentPdlFile = new File("$parentPegasusDir.path$File.separator$schemaFilename") + parentPdlFile << '''namespace com.linkedin.parent + | + |import com.linkedin.grandparent.LatLong + | + |record EXIF { + | isFlash: optional boolean = true + | location: optional LatLong + |}'''.stripMargin() + + def parentRunner = GradleRunner.create() + .withProjectDir(parentProject.root) + .withPluginClasspath() + .withArguments('publish', '-is') + //.forwardOutput() + //.withDebug(true) + + def parentResult = parentRunner.build() + + then: + parentResult.task(':compileMainGeneratedDataTemplateJava').outcome == TaskOutcome.SUCCESS + parentResult.task(':generateDescriptorFileForIvyPublication').outcome == TaskOutcome.SUCCESS + + def parentProjectIvyDescriptor = new File(localIvyRepo.path, 'com.linkedin.pegasus-parent-demo/parent/1.0.0/ivy-1.0.0.xml') + parentProjectIvyDescriptor.exists() + def parentProjectIvyDescriptorContents = parentProjectIvyDescriptor.text + def expectedParentContents = new File(Thread.currentThread().contextClassLoader.getResource('ivy/modern/expectedParentIvyDescriptorContents.txt').toURI()).text + parentProjectIvyDescriptorContents.contains expectedParentContents + + def parentProjectPrimaryArtifact = new File(localIvyRepo.path, 'com.linkedin.pegasus-parent-demo/parent/1.0.0/parent-1.0.0.jar') + parentProjectPrimaryArtifact.exists() + //NB note naming scheme of data-template jar changes when classifier, not appendix, is used + def parentProjectDataTemplateArtifact = new File(localIvyRepo.path, 'com.linkedin.pegasus-parent-demo/parent/1.0.0/parent-1.0.0-data-template.jar') + parentProjectDataTemplateArtifact.exists() + + assertZipContains(parentProjectDataTemplateArtifact, 'com/linkedin/parent/EXIF.class') + assertZipContains(parentProjectDataTemplateArtifact, 'pegasus/com/linkedin/parent/EXIF.pdl') + + when: 'a child project transitively consumes the grandparent project data-template jar' + + gradlePropertiesFile = childProject.newFile('gradle.properties') + gradlePropertiesFile << ''' + |group=com.linkedin.pegasus-child-demo + |version=1.0.0 + |'''.stripMargin() + + settingsFile = childProject.newFile('settings.gradle') + settingsFile << "rootProject.name = 'child'" + + childProject.newFile('build.gradle') << """ + |plugins { + | id 'ivy-publish' + | id 'pegasus' + |} + | + |repositories { + | ivy { url '$localIvyRepo' } + | mavenCentral() + |} + | + |dependencies { + | dataTemplateCompile files(${System.getProperty('integTest.dataTemplateCompileDependencies')}) + | pegasusPlugin files(${System.getProperty('integTest.pegasusPluginDependencies')}) + | + | dataModel ('com.linkedin.pegasus-parent-demo:parent:1.0.0') { + | capabilities { + | requireCapability('com.linkedin.pegasus-parent-demo:parent-data-template:1.0.0') // TODO Gradle 6.0 requires an explicit version, 6.? does not + | } + | } + | + | components.all(com.linkedin.pegasus.gradle.rules.PegasusIvyVariantDerivationRule) + |} + | + |tasks.withType(GenerateModuleMetadata) { enabled=false } + | + |//modern ivy-publish configuration + |publishing { + | publications { + | ivy(IvyPublication) { + | from components.java + | } + | } + | repositories { + | ivy { url '$localIvyRepo' } + | } + |} + |""".stripMargin() + + // Create a simple pdl schema which references parent and grandparent types + schemaFilename = 'Photo.pdl' + def childPegasusDir = childProject.newFolder('src', 'main', 'pegasus', 'com', 'linkedin', 'child') + def childPdlFile = new File("$childPegasusDir.path$File.separator$schemaFilename") + childPdlFile << '''namespace com.linkedin.child + | + |import com.linkedin.grandparent.LatLong + |import com.linkedin.parent.EXIF + | + |record Photo { + | id: long + | urn: string + | title: string + | exif: EXIF + | backupLocation: optional LatLong + |}'''.stripMargin() + + def childRunner = GradleRunner.create() + .withProjectDir(childProject.root) + .withPluginClasspath() + .withArguments('publish', '-is') + //.forwardOutput() + //.withDebug(true) def childResult = childRunner.build() @@ -298,4 +579,4 @@ class PegasusPluginIvyPublishIntegrationTest extends Specification { return new ZipFile(zip).getEntry(path) } -} +} \ No newline at end of file diff --git a/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/publishing/PegasusPluginLegacyIvyPublishIntegrationTest.groovy b/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/publishing/PegasusPluginLegacyIvyPublishIntegrationTest.groovy index fdfbd8890d..1dc2694ee3 100644 --- a/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/publishing/PegasusPluginLegacyIvyPublishIntegrationTest.groovy +++ b/gradle-plugins/src/integTest/groovy/com/linkedin/pegasus/gradle/publishing/PegasusPluginLegacyIvyPublishIntegrationTest.groovy @@ -263,6 +263,258 @@ class PegasusPluginLegacyIvyPublishIntegrationTest extends Specification { assertZipContains(childProjectDataTemplateArtifact, 'pegasus/com/linkedin/child/Photo.pdl') } + /** + * Regression test illustrating how to consume software components published using the legacy Ivy format. + * + *
By requesting a named capability instead of a specific configuration name, we can consume pegasus + * artifacts in a forward-compatible manner. + * + * Note that, in order to derive information about the capabilities of a software component, we must augment + * the consumer logic with a ComponentMetadataRule. + * + *
See Modeling feature variants and optional dependencies
+ * and Consuming Feature Variants
+ * for more information about capabilities.
+ */
+ def 'publishes with legacy ivies but derives capabilities from dataTemplate configurations'() {
+ given:
+ def gradlePropertiesFile = grandparentProject.newFile('gradle.properties')
+ gradlePropertiesFile << '''
+ |group=com.linkedin.pegasus-grandparent-demo
+ |version=1.0.0
+ |'''.stripMargin()
+
+ def settingsFile = grandparentProject.newFile('settings.gradle')
+ settingsFile << "rootProject.name = 'grandparent'"
+
+ grandparentProject.newFile('build.gradle') << """
+ |plugins {
+ | id 'pegasus'
+ |}
+ |
+ |repositories {
+ | mavenCentral()
+ |}
+ |
+ |dependencies {
+ | dataTemplateCompile files(${System.getProperty('integTest.dataTemplateCompileDependencies')})
+ | pegasusPlugin files(${System.getProperty('integTest.pegasusPluginDependencies')})
+ |}
+ |
+ |//legacy publishing configuration
+ |tasks.withType(Upload) {
+ | repositories {
+ | ivy { url '$localIvyRepo' }
+ | }
+ |}""".stripMargin()
+
+ // Create a simple pdl schema, borrowed from restli-example-api
+ def schemaFilename = 'LatLong.pdl'
+ def grandparentPegasusDir = grandparentProject.newFolder('src', 'main', 'pegasus', 'com', 'linkedin', 'grandparent')
+ def grandparentPdlFile = new File("$grandparentPegasusDir.path$File.separator$schemaFilename")
+ grandparentPdlFile << '''namespace com.linkedin.grandparent
+ |
+ |record LatLong {
+ | latitude: optional float
+ | longitude: optional float
+ |}'''.stripMargin()
+
+ when:
+ def grandparentRunner = GradleRunner.create()
+ .withProjectDir(grandparentProject.root)
+ .withPluginClasspath()
+ .withArguments('uploadDataTemplate', 'uploadTestDataTemplate', 'uploadAvroSchema', 'uploadTestAvroSchema', 'uploadArchives', '-is')
+ //.forwardOutput()
+ //.withDebug(true)
+
+ def grandparentResult = grandparentRunner.build()
+
+ then:
+ grandparentResult.task(':compileMainGeneratedDataTemplateJava').outcome == TaskOutcome.SUCCESS
+ grandparentResult.task(':uploadDataTemplate').outcome == TaskOutcome.SUCCESS
+ grandparentResult.task(':uploadArchives').outcome == TaskOutcome.SUCCESS
+
+ def grandparentProjectIvyDescriptor = new File(localIvyRepo.path, 'com.linkedin.pegasus-grandparent-demo/grandparent/1.0.0/ivy-1.0.0.xml')
+ grandparentProjectIvyDescriptor.exists()
+ def grandparentProjectIvyDescriptorContents = grandparentProjectIvyDescriptor.text
+ def expectedGrandparentContents = new File(Thread.currentThread().contextClassLoader.getResource('ivy/legacyWithVariantDerivation/expectedGrandparentIvyDescriptorContents.txt').toURI()).text
+ grandparentProjectIvyDescriptorContents.contains expectedGrandparentContents
+
+ def grandparentProjectPrimaryArtifact = new File(localIvyRepo.path, 'com.linkedin.pegasus-grandparent-demo/grandparent/1.0.0/grandparent-1.0.0.jar')
+ grandparentProjectPrimaryArtifact.exists()
+ def grandparentProjectDataTemplateArtifact = new File(localIvyRepo.path, 'com.linkedin.pegasus-grandparent-demo/grandparent/1.0.0/grandparent-data-template-1.0.0.jar')
+ grandparentProjectDataTemplateArtifact.exists()
+
+ assertZipContains(grandparentProjectDataTemplateArtifact, 'com/linkedin/grandparent/LatLong.class')
+ assertZipContains(grandparentProjectDataTemplateArtifact, 'pegasus/com/linkedin/grandparent/LatLong.pdl')
+
+ when: 'a parent project consumes the grandparent project data-template jar'
+
+ gradlePropertiesFile = parentProject.newFile('gradle.properties')
+ gradlePropertiesFile << '''
+ |group=com.linkedin.pegasus-parent-demo
+ |version=1.0.0
+ |'''.stripMargin()
+
+ settingsFile = parentProject.newFile('settings.gradle')
+ settingsFile << "rootProject.name = 'parent'"
+
+ parentProject.newFile('build.gradle') << """
+ |plugins {
+ | id 'pegasus'
+ |}
+ |
+ |repositories {
+ | ivy { url '$localIvyRepo' }
+ | mavenCentral()
+ |}
+ |
+ |dependencies {
+ | dataTemplateCompile files(${System.getProperty('integTest.dataTemplateCompileDependencies')})
+ | pegasusPlugin files(${System.getProperty('integTest.pegasusPluginDependencies')})
+ |
+ | dataModel ('com.linkedin.pegasus-grandparent-demo:grandparent:1.0.0') {
+ | capabilities {
+ | requireCapability('com.linkedin.pegasus-grandparent-demo:grandparent-data-template:1.0.0')
+ | }
+ | }
+ | components.all(com.linkedin.pegasus.gradle.rules.PegasusIvyVariantDerivationRule)
+ |}
+ |
+ |//legacy publishing configuration
+ |tasks.withType(Upload) {
+ | repositories {
+ | ivy { url '$localIvyRepo' }
+ | }
+ |}""".stripMargin()
+
+ // Create a simple pdl schema which references a grandparent type
+ schemaFilename = 'EXIF.pdl'
+ def parentPegasusDir = parentProject.newFolder('src', 'main', 'pegasus', 'com', 'linkedin', 'parent')
+ def parentPdlFile = new File("$parentPegasusDir.path$File.separator$schemaFilename")
+ parentPdlFile << '''namespace com.linkedin.parent
+ |
+ |import com.linkedin.grandparent.LatLong
+ |
+ |record EXIF {
+ | isFlash: optional boolean = true
+ | location: optional LatLong
+ |}'''.stripMargin()
+
+ def parentRunner = GradleRunner.create()
+ .withProjectDir(parentProject.root)
+ .withPluginClasspath()
+ .withArguments('uploadDataTemplate', 'uploadTestDataTemplate', 'uploadAvroSchema', 'uploadTestAvroSchema', 'uploadArchives', '-is')
+ //.forwardOutput()
+ //.withDebug(true)
+
+ def parentResult = parentRunner.build()
+
+ then:
+ parentResult.task(':compileMainGeneratedDataTemplateJava').outcome == TaskOutcome.SUCCESS
+ parentResult.task(':uploadDataTemplate').outcome == TaskOutcome.SUCCESS
+ parentResult.task(':uploadArchives').outcome == TaskOutcome.SUCCESS
+
+ def parentProjectIvyDescriptor = new File(localIvyRepo.path, 'com.linkedin.pegasus-parent-demo/parent/1.0.0/ivy-1.0.0.xml')
+ parentProjectIvyDescriptor.exists()
+ def parentProjectIvyDescriptorContents = parentProjectIvyDescriptor.text
+ def expectedParentContents = new File(Thread.currentThread().contextClassLoader.getResource('ivy/legacyWithVariantDerivation/expectedParentIvyDescriptorContents.txt').toURI()).text
+ parentProjectIvyDescriptorContents.contains expectedParentContents
+
+ def parentProjectPrimaryArtifact = new File(localIvyRepo.path, 'com.linkedin.pegasus-parent-demo/parent/1.0.0/parent-1.0.0.jar')
+ parentProjectPrimaryArtifact.exists()
+ def parentProjectDataTemplateArtifact = new File(localIvyRepo.path, 'com.linkedin.pegasus-parent-demo/parent/1.0.0/parent-data-template-1.0.0.jar')
+ parentProjectDataTemplateArtifact.exists()
+
+ assertZipContains(parentProjectDataTemplateArtifact, 'com/linkedin/parent/EXIF.class')
+ assertZipContains(parentProjectDataTemplateArtifact, 'pegasus/com/linkedin/parent/EXIF.pdl')
+
+ when: 'a child project transitively consumes the grandparent project data-template jar'
+
+ gradlePropertiesFile = childProject.newFile('gradle.properties')
+ gradlePropertiesFile << '''
+ |group=com.linkedin.pegasus-child-demo
+ |version=1.0.0
+ |'''.stripMargin()
+
+ settingsFile = childProject.newFile('settings.gradle')
+ settingsFile << "rootProject.name = 'child'"
+
+ childProject.newFile('build.gradle') << """
+ |plugins {
+ | id 'pegasus'
+ |}
+ |
+ |repositories {
+ | ivy { url '$localIvyRepo' }
+ | mavenCentral()
+ |}
+ |
+ |dependencies {
+ | dataTemplateCompile files(${System.getProperty('integTest.dataTemplateCompileDependencies')})
+ | pegasusPlugin files(${System.getProperty('integTest.pegasusPluginDependencies')})
+ |
+ | dataModel ('com.linkedin.pegasus-parent-demo:parent:1.0.0') {
+ | capabilities {
+ | requireCapability('com.linkedin.pegasus-parent-demo:parent-data-template:1.0.0')
+ | }
+ | }
+ | components.all(com.linkedin.pegasus.gradle.rules.PegasusIvyVariantDerivationRule)
+ |}
+ |
+ |//legacy publishing configuration
+ |tasks.withType(Upload) {
+ | repositories {
+ | ivy { url '$localIvyRepo' }
+ | }
+ |}""".stripMargin()
+
+ // Create a simple pdl schema which references parent and grandparent types
+ schemaFilename = 'Photo.pdl'
+ def childPegasusDir = childProject.newFolder('src', 'main', 'pegasus', 'com', 'linkedin', 'child')
+ def childPdlFile = new File("$childPegasusDir.path$File.separator$schemaFilename")
+ childPdlFile << '''namespace com.linkedin.child
+ |
+ |import com.linkedin.grandparent.LatLong
+ |import com.linkedin.parent.EXIF
+ |
+ |record Photo {
+ | id: long
+ | urn: string
+ | title: string
+ | exif: EXIF
+ | backupLocation: optional LatLong
+ |}'''.stripMargin()
+
+ def childRunner = GradleRunner.create()
+ .withProjectDir(childProject.root)
+ .withPluginClasspath()
+ .withArguments('uploadDataTemplate', 'uploadTestDataTemplate', 'uploadAvroSchema', 'uploadTestAvroSchema', 'uploadArchives', '-is')
+ //.forwardOutput()
+ //.withDebug(true)
+
+ def childResult = childRunner.build()
+
+ then:
+ childResult.task(':compileMainGeneratedDataTemplateJava').outcome == TaskOutcome.SUCCESS
+ childResult.task(':uploadDataTemplate').outcome == TaskOutcome.SUCCESS
+ childResult.task(':uploadArchives').outcome == TaskOutcome.SUCCESS
+
+ def childProjectIvyDescriptor = new File(localIvyRepo.path, 'com.linkedin.pegasus-child-demo/child/1.0.0/ivy-1.0.0.xml')
+ childProjectIvyDescriptor.exists()
+ def childProjectIvyDescriptorContents = childProjectIvyDescriptor.text
+ def expectedChildContents = new File(Thread.currentThread().contextClassLoader.getResource('ivy/legacyWithVariantDerivation/expectedChildIvyDescriptorContents.txt').toURI()).text
+ childProjectIvyDescriptorContents.contains expectedChildContents
+
+ def childProjectPrimaryArtifact = new File(localIvyRepo.path, 'com.linkedin.pegasus-child-demo/child/1.0.0/child-1.0.0.jar')
+ childProjectPrimaryArtifact.exists()
+ def childProjectDataTemplateArtifact = new File(localIvyRepo.path, 'com.linkedin.pegasus-child-demo/child/1.0.0/child-data-template-1.0.0.jar')
+ childProjectDataTemplateArtifact.exists()
+
+ assertZipContains(childProjectDataTemplateArtifact, 'com/linkedin/child/Photo.class')
+ assertZipContains(childProjectDataTemplateArtifact, 'pegasus/com/linkedin/child/Photo.pdl')
+ }
+
private static boolean assertZipContains(File zip, String path) {
return new ZipFile(zip).getEntry(path)
}
diff --git a/gradle-plugins/src/integTest/resources/ivy/legacyWithVariantDerivation/expectedChildIvyDescriptorContents.txt b/gradle-plugins/src/integTest/resources/ivy/legacyWithVariantDerivation/expectedChildIvyDescriptorContents.txt
new file mode 100644
index 0000000000..a3be89ecf8
--- /dev/null
+++ b/gradle-plugins/src/integTest/resources/ivy/legacyWithVariantDerivation/expectedChildIvyDescriptorContents.txt
@@ -0,0 +1,73 @@
+ Instead of consuming the dataTemplate configuration directly, this rule adds a "-data-template" capability to the
+ * primary GAV coordinates of the component.
+ *
+ * build.gradle usage example before this change:
+ *
+ * configurations {
+ * dataModel group: 'com.acme.foo', name: 'foo', version: '1.0.0', configuration: 'dataTemplate'
+ * }
+ *
+ *
+ * build.gradle usage example with this rule:
+ *
+ * configurations {
+ * dataModel ('com.acme.foo:foo:1.0.0') {
+ * capabilities {
+ * requireCapability('com.acme.foo:foo-data-template:1.0.0')
+ * }
+ * }
+ * components.all(com.linkedin.pegasus.gradle.rules.PegasusIvyVariantDerivationRule)
+ * }
+ *
+ *
+ */
+public class PegasusIvyVariantDerivationRule implements ComponentMetadataRule {
+
+ private final ObjectFactory objects;
+
+ @Inject
+ public PegasusIvyVariantDerivationRule(ObjectFactory objects) {
+ this.objects = objects;
+ }
+
+ @Override
+ public void execute(ComponentMetadataContext context) {
+ if (context.getDescriptor(IvyModuleDescriptor.class) == null) {
+ return; // this component's metadata is not Ivy-based; bail out
+ }
+
+ // for backwards-compatibility with older Ivy descriptors, first try to derive a variant from the dataTemplate configuration
+ context.getDetails().maybeAddVariant("dataTemplateApiElements", "dataTemplate", variantMetadata -> {
+ variantMetadata.attributes(attributes -> {
+ attributes.attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.class, Category.LIBRARY));
+ attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.class, LibraryElements.JAR));
+ attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.class, Usage.JAVA_API));
+ });
+ ModuleVersionIdentifier id = context.getDetails().getId();
+ variantMetadata.withCapabilities(capabilities -> capabilities.addCapability(id.getGroup(), id.getName() + "-data-template", id.getVersion()));
+ });
+
+ context.getDetails().maybeAddVariant("dataTemplateRuntimeElements", "mainGeneratedDataTemplateRuntimeElements", variantMetadata -> {
+ variantMetadata.attributes(attributes -> {
+ attributes.attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.class, Category.LIBRARY));
+ attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.class, LibraryElements.JAR));
+ attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.class, Usage.JAVA_RUNTIME));
+ });
+ ModuleVersionIdentifier id = context.getDetails().getId();
+ variantMetadata.withCapabilities(capabilities -> capabilities.addCapability(id.getGroup(), id.getName() + "-data-template", id.getVersion()));
+ });
+
+ context.getDetails().maybeAddVariant("dataTemplateApiElements", "mainGeneratedDataTemplateApiElements", variantMetadata -> {
+ variantMetadata.attributes(attributes -> {
+ attributes.attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.class, Category.LIBRARY));
+ attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.class, LibraryElements.JAR));
+ attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.class, Usage.JAVA_API));
+ });
+ ModuleVersionIdentifier id = context.getDetails().getId();
+ variantMetadata.withCapabilities(capabilities -> capabilities.addCapability(id.getGroup(), id.getName() + "-data-template", id.getVersion()));
+ });
+
+ }
+}