##// END OF EJS Templates
Add minimum Gradle compatibility functional test
cin -
r63:79fd940a7856 tip default
parent child
Show More
@@ -1,2 +1,3
1 group=org.implab.gradle
1 group=org.implab.gradle
2 version=0.1.0
2 version=0.1.0
3 testMinGradleVersion=8.10.2
@@ -1,61 +1,73
1 plugins {
1 plugins {
2 id "java-library"
2 id "java-library"
3 id "ivy-publish"
3 id "ivy-publish"
4 }
4 }
5
5
6 description = "Variant, source-set, and outgoing artifact model plugins for Gradle builds"
6 description = "Variant, source-set, and outgoing artifact model plugins for Gradle builds"
7
7
8 java {
8 java {
9 withJavadocJar()
9 withJavadocJar()
10 withSourcesJar()
10 withSourcesJar()
11 toolchain {
11 toolchain {
12 languageVersion = JavaLanguageVersion.of(21)
12 languageVersion = JavaLanguageVersion.of(21)
13 }
13 }
14 }
14 }
15
15
16 dependencies {
16 dependencies {
17 compileOnly libs.jdt.annotations
17 compileOnly libs.jdt.annotations
18
18
19 api gradleApi(),
19 api gradleApi(),
20 project(":common")
20 project(":common")
21
21
22 testImplementation gradleTestKit()
22 testImplementation gradleTestKit()
23 testImplementation "org.junit.jupiter:junit-jupiter-api:5.11.4"
23 testImplementation "org.junit.jupiter:junit-jupiter-api:5.11.4"
24 testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.11.4"
24 testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.11.4"
25 testRuntimeOnly "org.junit.platform:junit-platform-launcher:1.11.4"
25 testRuntimeOnly "org.junit.platform:junit-platform-launcher:1.11.4"
26 }
26 }
27
27
28 task printVersion{
28 task printVersion{
29 doLast {
29 doLast {
30 println "project: $project.group:$project.name:$project.version"
30 println "project: $project.group:$project.name:$project.version"
31 println "jar: ${->jar.archiveFileName.get()}"
31 println "jar: ${->jar.archiveFileName.get()}"
32 }
32 }
33 }
33 }
34
34
35 test {
35 test {
36 useJUnitPlatform()
36 useJUnitPlatform()
37 }
37 }
38
38
39 tasks.register("minGradleCompatibilityTest", Test) {
40 description = "Runs functional TestKit tests against the minimum supported Gradle version."
41 group = "verification"
42 useJUnitPlatform()
43 testClassesDirs = sourceSets.test.output.classesDirs
44 classpath = sourceSets.test.runtimeClasspath
45 include "**/*FunctionalTest.class"
46 systemProperty "org.implab.gradle.test.gradleVersion", providers.gradleProperty("testMinGradleVersion").get()
47 inputs.property("testMinGradleVersion", providers.gradleProperty("testMinGradleVersion"))
48 shouldRunAfter test
49 }
50
39 javadoc {
51 javadoc {
40 exclude "**/internal/**"
52 exclude "**/internal/**"
41 }
53 }
42
54
43 publishing {
55 publishing {
44 repositories {
56 repositories {
45 ivy {
57 ivy {
46 url = "${System.properties["user.home"]}/ivy-repo"
58 url = "${System.properties["user.home"]}/ivy-repo"
47 }
59 }
48 }
60 }
49 publications {
61 publications {
50 ivy(IvyPublication) {
62 ivy(IvyPublication) {
51 from components.java
63 from components.java
52 descriptor.description {
64 descriptor.description {
53 text = providers.provider({ description })
65 text = providers.provider({ description })
54 }
66 }
55 descriptor.license {
67 descriptor.license {
56 name = "BSD-2-Clause"
68 name = "BSD-2-Clause"
57 url = "https://spdx.org/licenses/BSD-2-Clause.html"
69 url = "https://spdx.org/licenses/BSD-2-Clause.html"
58 }
70 }
59 }
71 }
60 }
72 }
61 }
73 }
@@ -1,123 +1,138
1 package org.implab.gradle.variants;
1 package org.implab.gradle.variants;
2
2
3 import static org.junit.jupiter.api.Assertions.assertTrue;
3 import static org.junit.jupiter.api.Assertions.assertTrue;
4
4
5 import java.io.File;
5 import java.io.File;
6 import java.io.IOException;
6 import java.io.IOException;
7 import java.nio.file.Files;
7 import java.nio.file.Files;
8 import java.nio.file.Path;
8 import java.nio.file.Path;
9 import java.util.LinkedHashSet;
9 import java.util.LinkedHashSet;
10 import java.util.List;
10 import java.util.List;
11 import java.util.stream.Collectors;
11 import java.util.stream.Collectors;
12 import java.util.regex.Pattern;
12 import java.util.regex.Pattern;
13
13
14 import org.gradle.testkit.runner.GradleRunner;
14 import org.gradle.testkit.runner.GradleRunner;
15 import org.gradle.testkit.runner.UnexpectedBuildFailure;
15 import org.gradle.testkit.runner.UnexpectedBuildFailure;
16 import org.gradle.util.GradleVersion;
16 import org.implab.gradle.common.core.lang.Deferred;
17 import org.implab.gradle.common.core.lang.Deferred;
17 import org.implab.gradle.variants.sources.GenericSourceSet;
18 import org.implab.gradle.variants.sources.GenericSourceSet;
18 import org.junit.jupiter.api.io.TempDir;
19 import org.junit.jupiter.api.io.TempDir;
19
20
20 abstract class AbstractFunctionalTest {
21 abstract class AbstractFunctionalTest {
21 private static final String SETTINGS_FILE = "settings.gradle";
22 private static final String SETTINGS_FILE = "settings.gradle";
22 private static final String BUILD_FILE = "build.gradle";
23 private static final String BUILD_FILE = "build.gradle";
24 private static final String TEST_GRADLE_VERSION_PROPERTY = "org.implab.gradle.test.gradleVersion";
23 private static final Pattern INCLUDE_DECLARATION = Pattern.compile("^include(?:\\s|\\().*");
25 private static final Pattern INCLUDE_DECLARATION = Pattern.compile("^include(?:\\s|\\().*");
24 private static final Pattern QUOTED_LITERAL = Pattern.compile("['\"]([^'\"]+)['\"]");
26 private static final Pattern QUOTED_LITERAL = Pattern.compile("['\"]([^'\"]+)['\"]");
25
27
26 @TempDir
28 @TempDir
27 Path testProjectDir;
29 Path testProjectDir;
28
30
29 protected void writeSettings(String rootProjectName) throws IOException {
31 protected void writeSettings(String rootProjectName) throws IOException {
30 writeFile(SETTINGS_FILE, "rootProject.name = '" + rootProjectName + "'\n");
32 writeFile(SETTINGS_FILE, "rootProject.name = '" + rootProjectName + "'\n");
31 }
33 }
32
34
33 protected void writeBuildFile(String body) throws IOException {
35 protected void writeBuildFile(String body) throws IOException {
34 writeFile(BUILD_FILE, buildscriptClasspathBlock() + "\n" + body);
36 writeFile(BUILD_FILE, buildscriptClasspathBlock() + "\n" + body);
35 }
37 }
36
38
37 protected GradleRunner runner(String... arguments) {
39 protected GradleRunner runner(String... arguments) {
38 return GradleRunner.create()
40 var runner = GradleRunner.create()
39 .withProjectDir(testProjectDir.toFile())
41 .withProjectDir(testProjectDir.toFile())
40 .withArguments(arguments)
42 .withArguments(arguments)
41 .forwardOutput();
43 .forwardOutput();
44
45 var gradleVersion = System.getProperty(TEST_GRADLE_VERSION_PROPERTY);
46 if (gradleVersion != null && !gradleVersion.isBlank())
47 runner = runner.withGradleVersion(gradleVersion);
48
49 return runner;
50 }
51
52 protected GradleVersion targetGradleVersion() {
53 var gradleVersion = System.getProperty(TEST_GRADLE_VERSION_PROPERTY);
54 if (gradleVersion == null || gradleVersion.isBlank())
55 return GradleVersion.current();
56 return GradleVersion.version(gradleVersion);
42 }
57 }
43
58
44 protected void assertBuildFails(String expectedError, String... arguments) {
59 protected void assertBuildFails(String expectedError, String... arguments) {
45 var ex = org.junit.jupiter.api.Assertions.assertThrows(
60 var ex = org.junit.jupiter.api.Assertions.assertThrows(
46 UnexpectedBuildFailure.class,
61 UnexpectedBuildFailure.class,
47 () -> runner(arguments).build());
62 () -> runner(arguments).build());
48
63
49 var output = ex.getBuildResult().getOutput();
64 var output = ex.getBuildResult().getOutput();
50 assertTrue(output.contains(expectedError), () -> "Expected [" + expectedError + "] in output:\n" + output);
65 assertTrue(output.contains(expectedError), () -> "Expected [" + expectedError + "] in output:\n" + output);
51 }
66 }
52
67
53 private static String buildscriptClasspathBlock() {
68 private static String buildscriptClasspathBlock() {
54 var classpath = pluginClasspath().stream()
69 var classpath = pluginClasspath().stream()
55 .map(file -> "'" + file.getAbsolutePath().replace("\\", "\\\\") + "'")
70 .map(file -> "'" + file.getAbsolutePath().replace("\\", "\\\\") + "'")
56 .collect(Collectors.joining(", "));
71 .collect(Collectors.joining(", "));
57
72
58 return """
73 return """
59 buildscript {
74 buildscript {
60 dependencies {
75 dependencies {
61 classpath files(%s)
76 classpath files(%s)
62 }
77 }
63 }
78 }
64 """.formatted(classpath);
79 """.formatted(classpath);
65 }
80 }
66
81
67 private static List<File> pluginClasspath() {
82 private static List<File> pluginClasspath() {
68 try {
83 try {
69 var files = new LinkedHashSet<File>();
84 var files = new LinkedHashSet<File>();
70 files.add(codeSource(VariantSourcesPlugin.class));
85 files.add(codeSource(VariantSourcesPlugin.class));
71 files.add(resourceRoot("META-INF/gradle-plugins/org.implab.gradle-variants.properties"));
86 files.add(resourceRoot("META-INF/gradle-plugins/org.implab.gradle-variants.properties"));
72 files.add(codeSource(GenericSourceSet.class));
87 files.add(codeSource(GenericSourceSet.class));
73 files.add(codeSource(Deferred.class));
88 files.add(codeSource(Deferred.class));
74 return List.copyOf(files);
89 return List.copyOf(files);
75 } catch (Exception e) {
90 } catch (Exception e) {
76 throw new RuntimeException("Unable to build plugin classpath for test", e);
91 throw new RuntimeException("Unable to build plugin classpath for test", e);
77 }
92 }
78 }
93 }
79
94
80 private static File codeSource(Class<?> type) throws Exception {
95 private static File codeSource(Class<?> type) throws Exception {
81 return Path.of(type.getProtectionDomain().getCodeSource().getLocation().toURI()).toFile();
96 return Path.of(type.getProtectionDomain().getCodeSource().getLocation().toURI()).toFile();
82 }
97 }
83
98
84 private static File resourceRoot(String resourceName) throws Exception {
99 private static File resourceRoot(String resourceName) throws Exception {
85 var url = VariantSourcesPlugin.class.getClassLoader().getResource(resourceName);
100 var url = VariantSourcesPlugin.class.getClassLoader().getResource(resourceName);
86 if (url == null)
101 if (url == null)
87 throw new IllegalStateException("Classpath resource isn't found: " + resourceName);
102 throw new IllegalStateException("Classpath resource isn't found: " + resourceName);
88
103
89 var path = Path.of(url.toURI());
104 var path = Path.of(url.toURI());
90 var segments = resourceName.split("/").length;
105 var segments = resourceName.split("/").length;
91 for (var i = 0; i < segments; i++)
106 for (var i = 0; i < segments; i++)
92 path = path.getParent();
107 path = path.getParent();
93 return path.toFile();
108 return path.toFile();
94 }
109 }
95
110
96 protected void writeFile(String relativePath, String content) throws IOException {
111 protected void writeFile(String relativePath, String content) throws IOException {
97 Path path = testProjectDir.resolve(relativePath);
112 Path path = testProjectDir.resolve(relativePath);
98 Files.createDirectories(path.getParent());
113 Files.createDirectories(path.getParent());
99 Files.writeString(path, content);
114 Files.writeString(path, content);
100
115
101 if (SETTINGS_FILE.equals(relativePath))
116 if (SETTINGS_FILE.equals(relativePath))
102 ensureIncludedProjectDirectories(content);
117 ensureIncludedProjectDirectories(content);
103 }
118 }
104
119
105 private void ensureIncludedProjectDirectories(String settingsContent) throws IOException {
120 private void ensureIncludedProjectDirectories(String settingsContent) throws IOException {
106 for (var line : settingsContent.split("\\R")) {
121 for (var line : settingsContent.split("\\R")) {
107 var trimmed = line.strip();
122 var trimmed = line.strip();
108 if (!INCLUDE_DECLARATION.matcher(trimmed).matches())
123 if (!INCLUDE_DECLARATION.matcher(trimmed).matches())
109 continue;
124 continue;
110
125
111 var matcher = QUOTED_LITERAL.matcher(trimmed);
126 var matcher = QUOTED_LITERAL.matcher(trimmed);
112 while (matcher.find()) {
127 while (matcher.find()) {
113 var projectPath = matcher.group(1);
128 var projectPath = matcher.group(1);
114 if (projectPath.startsWith(":"))
129 if (projectPath.startsWith(":"))
115 projectPath = projectPath.substring(1);
130 projectPath = projectPath.substring(1);
116 if (projectPath.isBlank())
131 if (projectPath.isBlank())
117 continue;
132 continue;
118
133
119 Files.createDirectories(testProjectDir.resolve(projectPath.replace(':', File.separatorChar)));
134 Files.createDirectories(testProjectDir.resolve(projectPath.replace(':', File.separatorChar)));
120 }
135 }
121 }
136 }
122 }
137 }
123 }
138 }
@@ -1,867 +1,872
1 package org.implab.gradle.variants;
1 package org.implab.gradle.variants;
2
2
3 import static org.junit.jupiter.api.Assertions.assertTrue;
3 import static org.junit.jupiter.api.Assertions.assertTrue;
4
4
5 import org.gradle.testkit.runner.BuildResult;
5 import org.gradle.testkit.runner.BuildResult;
6 import org.gradle.testkit.runner.TaskOutcome;
6 import org.gradle.testkit.runner.TaskOutcome;
7 import org.junit.jupiter.api.Test;
7 import org.junit.jupiter.api.Test;
8
8
9 class VariantArtifactsPluginFunctionalTest extends AbstractFunctionalTest {
9 class VariantArtifactsPluginFunctionalTest extends AbstractFunctionalTest {
10
10
11 @Test
11 @Test
12 void gradleReferenceLazyOutgoingConfigurationAllowsSecondaryArtifactSelection() throws Exception {
12 void gradleReferenceLazyOutgoingConfigurationAllowsSecondaryArtifactSelection() throws Exception {
13 writeFile("settings.gradle", """
13 writeFile("settings.gradle", """
14 rootProject.name = 'gradle-reference-outgoing-resolution'
14 rootProject.name = 'gradle-reference-outgoing-resolution'
15 include 'producer', 'consumer'
15 include 'producer', 'consumer'
16 """);
16 """);
17 writeFile("producer/inputs/typesPackage", "types\n");
17 writeFile("producer/inputs/typesPackage", "types\n");
18 writeFile("producer/inputs/js", "js\n");
18 writeFile("producer/inputs/js", "js\n");
19 writeBuildFile("""
19 writeBuildFile("""
20 import org.gradle.api.attributes.Attribute
20 import org.gradle.api.attributes.Attribute
21
21
22 def variantAttr = Attribute.of('test.variant', String)
22 def variantAttr = Attribute.of('test.variant', String)
23 def slotAttr = Attribute.of('test.slot', String)
23 def slotAttr = Attribute.of('test.slot', String)
24
24
25 project(':producer') {
25 project(':producer') {
26 def browserElements = configurations.consumable('browserElements')
26 def browserElements = configurations.consumable('browserElements')
27
27
28 println('reference: registered browserElements provider')
28 println('reference: registered browserElements provider')
29
29
30 browserElements.configure { configuration ->
30 browserElements.configure { configuration ->
31 println('reference: configuring browserElements')
31 println('reference: configuring browserElements')
32
32
33 configuration.attributes.attribute(variantAttr, 'browser')
33 configuration.attributes.attribute(variantAttr, 'browser')
34 configuration.outgoing.attributes.attribute(slotAttr, 'typesPackage')
34 configuration.outgoing.attributes.attribute(slotAttr, 'typesPackage')
35 configuration.outgoing.artifact(layout.projectDirectory.file('inputs/typesPackage'))
35 configuration.outgoing.artifact(layout.projectDirectory.file('inputs/typesPackage'))
36
36
37 configuration.outgoing.variants.create('js') { secondary ->
37 configuration.outgoing.variants.create('js') { secondary ->
38 println('reference: creating js outgoing variant')
38 println('reference: creating js outgoing variant')
39
39
40 secondary.attributes.attribute(slotAttr, 'js')
40 secondary.attributes.attribute(slotAttr, 'js')
41 secondary.artifact(layout.projectDirectory.file('inputs/js'))
41 secondary.artifact(layout.projectDirectory.file('inputs/js'))
42 }
42 }
43 }
43 }
44 }
44 }
45
45
46 project(':consumer') {
46 project(':consumer') {
47 configurations {
47 configurations {
48 compileView {
48 compileView {
49 canBeResolved = true
49 canBeResolved = true
50 canBeConsumed = false
50 canBeConsumed = false
51 canBeDeclared = true
51 canBeDeclared = true
52 attributes {
52 attributes {
53 attribute(variantAttr, 'browser')
53 attribute(variantAttr, 'browser')
54 attribute(slotAttr, 'typesPackage')
54 attribute(slotAttr, 'typesPackage')
55 }
55 }
56 }
56 }
57 }
57 }
58
58
59 dependencies {
59 dependencies {
60 compileView project(':producer')
60 compileView project(':producer')
61 }
61 }
62
62
63 tasks.register('probe') {
63 tasks.register('probe') {
64 doLast {
64 doLast {
65 println('reference: resolving primary files')
65 println('reference: resolving primary files')
66
66
67 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
67 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
68
68
69 println('reference: resolving secondary files')
69 println('reference: resolving secondary files')
70
70
71 def jsFiles = configurations.compileView.incoming.artifactView {
71 def jsFiles = configurations.compileView.incoming.artifactView {
72 attributes {
72 attributes {
73 attribute(slotAttr, 'js')
73 attribute(slotAttr, 'js')
74 }
74 }
75 }.files.files.collect { it.name }.sort().join(',')
75 }.files.files.collect { it.name }.sort().join(',')
76
76
77 println('compileFiles=' + compileFiles)
77 println('compileFiles=' + compileFiles)
78 println('jsFiles=' + jsFiles)
78 println('jsFiles=' + jsFiles)
79 }
79 }
80 }
80 }
81 }
81 }
82 """);
82 """);
83
83
84 BuildResult result = runner(":consumer:probe").build();
84 BuildResult result = runner(":consumer:probe").build();
85 var output = result.getOutput();
85 var output = result.getOutput();
86 var registered = output.indexOf("reference: registered browserElements provider");
86 var registered = output.indexOf("reference: registered browserElements provider");
87 var resolvingPrimary = output.indexOf("reference: resolving primary files");
87 var resolvingPrimary = output.indexOf("reference: resolving primary files");
88 var configuring = output.indexOf("reference: configuring browserElements");
88 var configuring = output.indexOf("reference: configuring browserElements");
89 var creatingSecondary = output.indexOf("reference: creating js outgoing variant");
89 var creatingSecondary = output.indexOf("reference: creating js outgoing variant");
90 var resolvingSecondary = output.indexOf("reference: resolving secondary files");
90 var resolvingSecondary = output.indexOf("reference: resolving secondary files");
91
91
92 assertTrue(registered >= 0);
92 assertTrue(registered >= 0);
93 assertTrue(resolvingPrimary >= 0);
93 assertTrue(resolvingPrimary >= 0);
94 assertTrue(configuring >= 0);
94 assertTrue(configuring >= 0);
95 assertTrue(creatingSecondary >= 0);
95 assertTrue(creatingSecondary >= 0);
96 assertTrue(resolvingSecondary >= 0);
96 assertTrue(resolvingSecondary >= 0);
97 assertTrue(registered < resolvingPrimary);
97 assertTrue(registered < resolvingPrimary);
98 assertTrue(resolvingPrimary < configuring);
98 assertTrue(resolvingPrimary < configuring);
99 assertTrue(configuring < creatingSecondary);
99 assertTrue(configuring < creatingSecondary);
100 assertTrue(creatingSecondary < resolvingSecondary);
100 assertTrue(creatingSecondary < resolvingSecondary);
101 assertTrue(output.contains("compileFiles=typesPackage"));
101 assertTrue(output.contains("compileFiles=typesPackage"));
102 assertTrue(output.contains("jsFiles=js"));
102 assertTrue(output.contains("jsFiles=js"));
103 }
103 }
104
104
105 @Test
105 @Test
106 void gradleReferenceRegisteredSecondaryArtifactVariantAllowsSecondaryArtifactSelection() throws Exception {
106 void gradleReferenceRegisteredSecondaryArtifactVariantMatchesGradleBehavior() throws Exception {
107 writeFile("settings.gradle", """
107 writeFile("settings.gradle", """
108 rootProject.name = 'gradle-reference-registered-secondary-variant'
108 rootProject.name = 'gradle-reference-registered-secondary-variant'
109 include 'producer', 'consumer'
109 include 'producer', 'consumer'
110 """);
110 """);
111 writeFile("producer/inputs/typesPackage", "types\n");
111 writeFile("producer/inputs/typesPackage", "types\n");
112 writeFile("producer/inputs/js", "js\n");
112 writeFile("producer/inputs/js", "js\n");
113 writeFile("build.gradle", """
113 writeFile("build.gradle", """
114 import org.gradle.api.attributes.Attribute
114 import org.gradle.api.attributes.Attribute
115
115
116 def variantAttr = Attribute.of('test.variant', String)
116 def variantAttr = Attribute.of('test.variant', String)
117 def slotAttr = Attribute.of('test.slot', String)
117 def slotAttr = Attribute.of('test.slot', String)
118
118
119 project(':producer') {
119 project(':producer') {
120 def browserElements = configurations.consumable('browserElements')
120 def browserElements = configurations.consumable('browserElements')
121
121
122 browserElements.configure { configuration ->
122 browserElements.configure { configuration ->
123 configuration.attributes.attribute(variantAttr, 'browser')
123 configuration.attributes.attribute(variantAttr, 'browser')
124 configuration.outgoing.attributes.attribute(slotAttr, 'typesPackage')
124 configuration.outgoing.attributes.attribute(slotAttr, 'typesPackage')
125 configuration.outgoing.artifact(layout.projectDirectory.file('inputs/typesPackage'))
125 configuration.outgoing.artifact(layout.projectDirectory.file('inputs/typesPackage'))
126
126
127 configuration.outgoing.variants.register('js') { secondary ->
127 configuration.outgoing.variants.register('js') { secondary ->
128 secondary.attributes.attribute(slotAttr, 'js')
128 secondary.attributes.attribute(slotAttr, 'js')
129 secondary.artifact(layout.projectDirectory.file('inputs/js'))
129 secondary.artifact(layout.projectDirectory.file('inputs/js'))
130 }
130 }
131 }
131 }
132 }
132 }
133
133
134 project(':consumer') {
134 project(':consumer') {
135 configurations {
135 configurations {
136 compileView {
136 compileView {
137 canBeResolved = true
137 canBeResolved = true
138 canBeConsumed = false
138 canBeConsumed = false
139 canBeDeclared = true
139 canBeDeclared = true
140 attributes {
140 attributes {
141 attribute(variantAttr, 'browser')
141 attribute(variantAttr, 'browser')
142 attribute(slotAttr, 'typesPackage')
142 attribute(slotAttr, 'typesPackage')
143 }
143 }
144 }
144 }
145 }
145 }
146
146
147 dependencies {
147 dependencies {
148 compileView project(':producer')
148 compileView project(':producer')
149 }
149 }
150
150
151 tasks.register('probe') {
151 tasks.register('probe') {
152 doLast {
152 doLast {
153 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
153 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
154 def jsFiles = configurations.compileView.incoming.artifactView {
154 def jsFiles = configurations.compileView.incoming.artifactView {
155 attributes {
155 attributes {
156 attribute(slotAttr, 'js')
156 attribute(slotAttr, 'js')
157 }
157 }
158 }.files.files.collect { it.name }.sort().join(',')
158 }.files.files.collect { it.name }.sort().join(',')
159
159
160 println('compileFiles=' + compileFiles)
160 println('compileFiles=' + compileFiles)
161 println('jsFiles=' + jsFiles)
161 println('jsFiles=' + jsFiles)
162 }
162 }
163 }
163 }
164 }
164 }
165 """);
165 """);
166
166
167 if (targetGradleVersion().compareTo(org.gradle.util.GradleVersion.version("9.4.1")) < 0) {
168 assertBuildFails("Cannot create variant 'js' after dependency configuration ':producer:browserElements' has been resolved",
169 ":consumer:probe");
170 } else {
167 BuildResult result = runner(":consumer:probe").build();
171 BuildResult result = runner(":consumer:probe").build();
168
172
169 assertTrue(result.getOutput().contains("compileFiles=typesPackage"));
173 assertTrue(result.getOutput().contains("compileFiles=typesPackage"));
170 assertTrue(result.getOutput().contains("jsFiles=js"));
174 assertTrue(result.getOutput().contains("jsFiles=js"));
171 }
175 }
176 }
172
177
173 @Test
178 @Test
174 void materializesPrimaryAndSecondarySlotsAndInvokesOutgoingHooks() throws Exception {
179 void materializesPrimaryAndSecondarySlotsAndInvokesOutgoingHooks() throws Exception {
175 writeSettings("variant-artifacts-slots");
180 writeSettings("variant-artifacts-slots");
176 writeFile("inputs/base.js", "console.log('base')\n");
181 writeFile("inputs/base.js", "console.log('base')\n");
177 writeFile("inputs/amd.js", "console.log('amd')\n");
182 writeFile("inputs/amd.js", "console.log('amd')\n");
178 writeFile("inputs/mainJs.txt", "mainJs marker\n");
183 writeFile("inputs/mainJs.txt", "mainJs marker\n");
179 writeFile("inputs/amdJs.txt", "amdJs marker\n");
184 writeFile("inputs/amdJs.txt", "amdJs marker\n");
180 writeBuildFile("""
185 writeBuildFile("""
181 import org.gradle.api.attributes.Attribute
186 import org.gradle.api.attributes.Attribute
182
187
183 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
188 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
184
189
185 def variantAttr = Attribute.of('test.variant', String)
190 def variantAttr = Attribute.of('test.variant', String)
186 def slotAttr = Attribute.of('test.slot', String)
191 def slotAttr = Attribute.of('test.slot', String)
187
192
188 variants.layers.create('mainBase')
193 variants.layers.create('mainBase')
189 variants.layers.create('mainAmd')
194 variants.layers.create('mainAmd')
190 variants.roles.create('main')
195 variants.roles.create('main')
191 variants.roles.create('test')
196 variants.roles.create('test')
192 variants.variant('browser') {
197 variants.variant('browser') {
193 role('main') {
198 role('main') {
194 layers('mainBase', 'mainAmd')
199 layers('mainBase', 'mainAmd')
195 }
200 }
196 }
201 }
197
202
198 variantSources {
203 variantSources {
199 layer('mainBase') {
204 layer('mainBase') {
200 sourceSet {
205 sourceSet {
201 declareOutputs('js')
206 declareOutputs('js')
202 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
207 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
203 }
208 }
204 }
209 }
205 layer('mainAmd') {
210 layer('mainAmd') {
206 sourceSet {
211 sourceSet {
207 declareOutputs('js')
212 declareOutputs('js')
208 registerOutput('js', layout.projectDirectory.file('inputs/amd.js'))
213 registerOutput('js', layout.projectDirectory.file('inputs/amd.js'))
209 }
214 }
210 }
215 }
211 }
216 }
212
217
213 variantArtifacts {
218 variantArtifacts {
214 variant('browser') {
219 variant('browser') {
215 primarySlot('mainJs') {
220 primarySlot('mainJs') {
216 fromRole('main') {
221 fromRole('main') {
217 output('js')
222 output('js')
218 }
223 }
219 from(layout.projectDirectory.file('inputs/mainJs.txt'))
224 from(layout.projectDirectory.file('inputs/mainJs.txt'))
220 }
225 }
221 slot('amdJs') {
226 slot('amdJs') {
222 fromLayer('mainAmd') {
227 fromLayer('mainAmd') {
223 output('js')
228 output('js')
224 }
229 }
225 from(layout.projectDirectory.file('inputs/amdJs.txt'))
230 from(layout.projectDirectory.file('inputs/amdJs.txt'))
226 }
231 }
227 }
232 }
228
233
229 whenOutgoingConfiguration { publication ->
234 whenOutgoingConfiguration { publication ->
230 publication.configuration {
235 publication.configuration {
231 attributes.attribute(variantAttr, publication.variant.name)
236 attributes.attribute(variantAttr, publication.variant.name)
232 }
237 }
233 }
238 }
234
239
235 whenOutgoingSlot { publication ->
240 whenOutgoingSlot { publication ->
236 def slotName = publication.artifactSlot.slot.name
241 def slotName = publication.artifactSlot.slot.name
237 publication.artifactAttributes {
242 publication.artifactAttributes {
238 attribute(slotAttr, slotName)
243 attribute(slotAttr, slotName)
239 }
244 }
240 }
245 }
241 }
246 }
242
247
243 tasks.register('probe') {
248 tasks.register('probe') {
244 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_mainJs'
249 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_mainJs'
245 dependsOn 'assembleVariantArtifactSlot_v7_browser_s5_amdJs'
250 dependsOn 'assembleVariantArtifactSlot_v7_browser_s5_amdJs'
246
251
247 doLast {
252 doLast {
248 def mainDir = layout.buildDirectory.dir('variant-assemblies/browser/mainJs').get().asFile
253 def mainDir = layout.buildDirectory.dir('variant-assemblies/browser/mainJs').get().asFile
249 def amdDir = layout.buildDirectory.dir('variant-assemblies/browser/amdJs').get().asFile
254 def amdDir = layout.buildDirectory.dir('variant-assemblies/browser/amdJs').get().asFile
250
255
251 assert new File(mainDir, 'base.js').exists()
256 assert new File(mainDir, 'base.js').exists()
252 assert new File(mainDir, 'amd.js').exists()
257 assert new File(mainDir, 'amd.js').exists()
253 assert new File(mainDir, 'mainJs.txt').exists()
258 assert new File(mainDir, 'mainJs.txt').exists()
254
259
255 assert !new File(amdDir, 'base.js').exists()
260 assert !new File(amdDir, 'base.js').exists()
256 assert new File(amdDir, 'amd.js').exists()
261 assert new File(amdDir, 'amd.js').exists()
257 assert new File(amdDir, 'amdJs.txt').exists()
262 assert new File(amdDir, 'amdJs.txt').exists()
258
263
259 def elements = configurations.getByName('browserElements')
264 def elements = configurations.getByName('browserElements')
260 def amdVariant = elements.outgoing.variants.getByName('amdJs')
265 def amdVariant = elements.outgoing.variants.getByName('amdJs')
261
266
262 println('variantAttr=' + elements.attributes.getAttribute(variantAttr))
267 println('variantAttr=' + elements.attributes.getAttribute(variantAttr))
263 println('primarySlotAttr=' + elements.outgoing.attributes.getAttribute(slotAttr))
268 println('primarySlotAttr=' + elements.outgoing.attributes.getAttribute(slotAttr))
264 println('amdSlotAttr=' + amdVariant.attributes.getAttribute(slotAttr))
269 println('amdSlotAttr=' + amdVariant.attributes.getAttribute(slotAttr))
265 println('configurations=' + configurations.matching { it.name == 'browserElements' }.collect { it.name }.join(','))
270 println('configurations=' + configurations.matching { it.name == 'browserElements' }.collect { it.name }.join(','))
266 println('secondaryVariants=' + elements.outgoing.variants.collect { it.name }.sort().join(','))
271 println('secondaryVariants=' + elements.outgoing.variants.collect { it.name }.sort().join(','))
267 }
272 }
268 }
273 }
269 """);
274 """);
270
275
271 BuildResult result = runner("probe").build();
276 BuildResult result = runner("probe").build();
272
277
273 assertTrue(result.getOutput().contains("variantAttr=browser"));
278 assertTrue(result.getOutput().contains("variantAttr=browser"));
274 assertTrue(result.getOutput().contains("primarySlotAttr=mainJs"));
279 assertTrue(result.getOutput().contains("primarySlotAttr=mainJs"));
275 assertTrue(result.getOutput().contains("amdSlotAttr=amdJs"));
280 assertTrue(result.getOutput().contains("amdSlotAttr=amdJs"));
276 assertTrue(result.getOutput().contains("configurations=browserElements"));
281 assertTrue(result.getOutput().contains("configurations=browserElements"));
277 assertTrue(result.getOutput().contains("secondaryVariants=amdJs"));
282 assertTrue(result.getOutput().contains("secondaryVariants=amdJs"));
278 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
283 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
279 }
284 }
280
285
281 @Test
286 @Test
282 void outgoingSlotHookFollowsMaterializedGradleArtifactVariants() throws Exception {
287 void outgoingSlotHookFollowsMaterializedGradleArtifactVariants() throws Exception {
283 writeSettings("variant-artifacts-materialized-gradle-variant");
288 writeSettings("variant-artifacts-materialized-gradle-variant");
284 writeFile("inputs/typesPackage", "types\n");
289 writeFile("inputs/typesPackage", "types\n");
285 writeFile("inputs/js", "js\n");
290 writeFile("inputs/js", "js\n");
286 writeBuildFile("""
291 writeBuildFile("""
287 import org.gradle.api.attributes.Attribute
292 import org.gradle.api.attributes.Attribute
288
293
289 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
294 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
290
295
291 def slotAttr = Attribute.of('test.slot', String)
296 def slotAttr = Attribute.of('test.slot', String)
292
297
293 variants.layers.create('main')
298 variants.layers.create('main')
294 variants.roles.create('main')
299 variants.roles.create('main')
295 variants.variant('browser') {
300 variants.variant('browser') {
296 role('main') {
301 role('main') {
297 layers('main')
302 layers('main')
298 }
303 }
299 }
304 }
300
305
301 variantArtifacts {
306 variantArtifacts {
302 variant('browser') {
307 variant('browser') {
303 primarySlot('typesPackage') {
308 primarySlot('typesPackage') {
304 from(layout.projectDirectory.file('inputs/typesPackage'))
309 from(layout.projectDirectory.file('inputs/typesPackage'))
305 }
310 }
306 }
311 }
307
312
308 whenOutgoingConfiguration { publication ->
313 whenOutgoingConfiguration { publication ->
309 publication.configuration {
314 publication.configuration {
310 outgoing.variants.create('js') { secondary ->
315 outgoing.variants.create('js') { secondary ->
311 secondary.artifact(layout.projectDirectory.file('inputs/js'))
316 secondary.artifact(layout.projectDirectory.file('inputs/js'))
312 }
317 }
313 }
318 }
314 }
319 }
315
320
316 whenOutgoingSlot { publication ->
321 whenOutgoingSlot { publication ->
317 publication.artifactAttributes {
322 publication.artifactAttributes {
318 attribute(slotAttr, publication.artifactSlot.slot.name)
323 attribute(slotAttr, publication.artifactSlot.slot.name)
319 }
324 }
320 }
325 }
321 }
326 }
322
327
323 tasks.register('probe') {
328 tasks.register('probe') {
324 doLast {
329 doLast {
325 def elements = configurations.getByName('browserElements')
330 def elements = configurations.getByName('browserElements')
326 def jsVariant = elements.outgoing.variants.getByName('js')
331 def jsVariant = elements.outgoing.variants.getByName('js')
327
332
328 println('primarySlotAttr=' + elements.outgoing.attributes.getAttribute(slotAttr))
333 println('primarySlotAttr=' + elements.outgoing.attributes.getAttribute(slotAttr))
329 println('jsSlotAttr=' + jsVariant.attributes.getAttribute(slotAttr))
334 println('jsSlotAttr=' + jsVariant.attributes.getAttribute(slotAttr))
330 }
335 }
331 }
336 }
332 """);
337 """);
333
338
334 BuildResult result = runner("probe").build();
339 BuildResult result = runner("probe").build();
335
340
336 assertTrue(result.getOutput().contains("primarySlotAttr=typesPackage"));
341 assertTrue(result.getOutput().contains("primarySlotAttr=typesPackage"));
337 assertTrue(result.getOutput().contains("jsSlotAttr=js"));
342 assertTrue(result.getOutput().contains("jsSlotAttr=js"));
338 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
343 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
339 }
344 }
340
345
341 @Test
346 @Test
342 void allowsSingleSlotVariantWithoutExplicitPrimarySlot() throws Exception {
347 void allowsSingleSlotVariantWithoutExplicitPrimarySlot() throws Exception {
343 writeSettings("variant-artifacts-single-slot");
348 writeSettings("variant-artifacts-single-slot");
344 writeBuildFile("""
349 writeBuildFile("""
345 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
350 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
346
351
347 variants.layers.create('main')
352 variants.layers.create('main')
348 variants.roles.create('main')
353 variants.roles.create('main')
349 variants.variant('browser') {
354 variants.variant('browser') {
350 role('main') {
355 role('main') {
351 layers('main')
356 layers('main')
352 }
357 }
353 }
358 }
354
359
355 variantSources.layer('main') {
360 variantSources.layer('main') {
356 sourceSet {
361 sourceSet {
357 declareOutputs('types')
362 declareOutputs('types')
358 }
363 }
359 }
364 }
360
365
361 variantArtifacts {
366 variantArtifacts {
362 variant('browser') {
367 variant('browser') {
363 slot('typesPackage') {
368 slot('typesPackage') {
364 fromVariant {
369 fromVariant {
365 output('types')
370 output('types')
366 }
371 }
367 }
372 }
368 }
373 }
369 }
374 }
370
375
371 tasks.register('probe') {
376 tasks.register('probe') {
372 doLast {
377 doLast {
373 variantArtifacts.whenAvailable { ctx ->
378 variantArtifacts.whenAvailable { ctx ->
374 def browser = objects.named(org.implab.gradle.variants.core.Variant, 'browser')
379 def browser = objects.named(org.implab.gradle.variants.core.Variant, 'browser')
375 println('primary=' + ctx.findOutgoing(browser).get().primarySlot.get().name)
380 println('primary=' + ctx.findOutgoing(browser).get().primarySlot.get().name)
376 }
381 }
377 }
382 }
378 }
383 }
379 """);
384 """);
380
385
381 BuildResult result = runner("probe").build();
386 BuildResult result = runner("probe").build();
382
387
383 assertTrue(result.getOutput().contains("primary=typesPackage"));
388 assertTrue(result.getOutput().contains("primary=typesPackage"));
384 }
389 }
385
390
386 @Test
391 @Test
387 void materializesDirectSlotInputsWithoutVariantSourceBindings() throws Exception {
392 void materializesDirectSlotInputsWithoutVariantSourceBindings() throws Exception {
388 writeSettings("variant-artifacts-direct-input");
393 writeSettings("variant-artifacts-direct-input");
389 writeFile("inputs/bundle.js", "console.log('bundle')\n");
394 writeFile("inputs/bundle.js", "console.log('bundle')\n");
390 writeBuildFile("""
395 writeBuildFile("""
391 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
396 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
392
397
393 variants.layers.create('main')
398 variants.layers.create('main')
394 variants.roles.create('main')
399 variants.roles.create('main')
395 variants.variant('browser') {
400 variants.variant('browser') {
396 role('main') {
401 role('main') {
397 layers('main')
402 layers('main')
398 }
403 }
399 }
404 }
400
405
401 variantArtifacts {
406 variantArtifacts {
402 variant('browser') {
407 variant('browser') {
403 primarySlot('bundle') {
408 primarySlot('bundle') {
404 from(layout.projectDirectory.file('inputs/bundle.js'))
409 from(layout.projectDirectory.file('inputs/bundle.js'))
405 }
410 }
406 }
411 }
407 }
412 }
408
413
409 tasks.register('probe') {
414 tasks.register('probe') {
410 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_bundle'
415 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_bundle'
411
416
412 doLast {
417 doLast {
413 def bundleDir = layout.buildDirectory.dir('variant-assemblies/browser/bundle').get().asFile
418 def bundleDir = layout.buildDirectory.dir('variant-assemblies/browser/bundle').get().asFile
414 assert new File(bundleDir, 'bundle.js').exists()
419 assert new File(bundleDir, 'bundle.js').exists()
415 }
420 }
416 }
421 }
417 """);
422 """);
418
423
419 BuildResult result = runner("probe").build();
424 BuildResult result = runner("probe").build();
420
425
421 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
426 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
422 }
427 }
423
428
424 @Test
429 @Test
425 void publishesTaskProducedFileArtifactDirectly() throws Exception {
430 void publishesTaskProducedFileArtifactDirectly() throws Exception {
426 writeFile("settings.gradle", """
431 writeFile("settings.gradle", """
427 rootProject.name = 'variant-artifacts-task-produced-file'
432 rootProject.name = 'variant-artifacts-task-produced-file'
428 include 'producer', 'consumer'
433 include 'producer', 'consumer'
429 """);
434 """);
430 writeBuildFile("""
435 writeBuildFile("""
431 import org.gradle.api.DefaultTask
436 import org.gradle.api.DefaultTask
432 import org.gradle.api.attributes.Attribute
437 import org.gradle.api.attributes.Attribute
433 import org.gradle.api.file.RegularFileProperty
438 import org.gradle.api.file.RegularFileProperty
434 import org.gradle.api.tasks.OutputFile
439 import org.gradle.api.tasks.OutputFile
435 import org.gradle.api.tasks.TaskAction
440 import org.gradle.api.tasks.TaskAction
436
441
437 def variantAttr = Attribute.of('test.variant', String)
442 def variantAttr = Attribute.of('test.variant', String)
438 def slotAttr = Attribute.of('test.slot', String)
443 def slotAttr = Attribute.of('test.slot', String)
439
444
440 abstract class WritePackageMetadata extends DefaultTask {
445 abstract class WritePackageMetadata extends DefaultTask {
441 @OutputFile
446 @OutputFile
442 abstract RegularFileProperty getOutputFile()
447 abstract RegularFileProperty getOutputFile()
443
448
444 @TaskAction
449 @TaskAction
445 void write() {
450 void write() {
446 def file = outputFile.get().asFile
451 def file = outputFile.get().asFile
447 file.parentFile.mkdirs()
452 file.parentFile.mkdirs()
448 file.text = '{"name":"demo"}\\n'
453 file.text = '{"name":"demo"}\\n'
449 }
454 }
450 }
455 }
451
456
452 project(':producer') {
457 project(':producer') {
453 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
458 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
454
459
455 variants.layers.create('main')
460 variants.layers.create('main')
456 variants.roles.create('main')
461 variants.roles.create('main')
457 variants.variant('browser') {
462 variants.variant('browser') {
458 role('main') {
463 role('main') {
459 layers('main')
464 layers('main')
460 }
465 }
461 }
466 }
462
467
463 def writePackageMetadata = tasks.register('writePackageMetadata', WritePackageMetadata) {
468 def writePackageMetadata = tasks.register('writePackageMetadata', WritePackageMetadata) {
464 outputFile.set(layout.buildDirectory.file('generated/package.json'))
469 outputFile.set(layout.buildDirectory.file('generated/package.json'))
465 }
470 }
466
471
467 variantArtifacts {
472 variantArtifacts {
468 variant('browser') {
473 variant('browser') {
469 primarySlot('packageMetadata') {
474 primarySlot('packageMetadata') {
470 producedBy(writePackageMetadata) {
475 producedBy(writePackageMetadata) {
471 outputFile
476 outputFile
472 }
477 }
473 }
478 }
474 }
479 }
475
480
476 whenOutgoingConfiguration { publication ->
481 whenOutgoingConfiguration { publication ->
477 publication.configuration {
482 publication.configuration {
478 attributes.attribute(variantAttr, publication.variant.name)
483 attributes.attribute(variantAttr, publication.variant.name)
479 }
484 }
480 }
485 }
481
486
482 whenOutgoingSlot { publication ->
487 whenOutgoingSlot { publication ->
483 publication.artifactAttributes {
488 publication.artifactAttributes {
484 attribute(slotAttr, publication.artifactSlot.slot.name)
489 attribute(slotAttr, publication.artifactSlot.slot.name)
485 }
490 }
486 }
491 }
487 }
492 }
488
493
489 tasks.register('checkNoManagedAssembly') {
494 tasks.register('checkNoManagedAssembly') {
490 doLast {
495 doLast {
491 def assemblyTasks = tasks.names
496 def assemblyTasks = tasks.names
492 .findAll { it.startsWith('assembleVariantArtifactSlot') }
497 .findAll { it.startsWith('assembleVariantArtifactSlot') }
493 .sort()
498 .sort()
494 println('producerAssemblyTasks=' + assemblyTasks.join(','))
499 println('producerAssemblyTasks=' + assemblyTasks.join(','))
495 assert assemblyTasks.empty
500 assert assemblyTasks.empty
496 }
501 }
497 }
502 }
498 }
503 }
499
504
500 project(':consumer') {
505 project(':consumer') {
501 configurations {
506 configurations {
502 compileView {
507 compileView {
503 canBeResolved = true
508 canBeResolved = true
504 canBeConsumed = false
509 canBeConsumed = false
505 canBeDeclared = true
510 canBeDeclared = true
506 attributes {
511 attributes {
507 attribute(variantAttr, 'browser')
512 attribute(variantAttr, 'browser')
508 attribute(slotAttr, 'packageMetadata')
513 attribute(slotAttr, 'packageMetadata')
509 }
514 }
510 }
515 }
511 }
516 }
512
517
513 dependencies {
518 dependencies {
514 compileView project(':producer')
519 compileView project(':producer')
515 }
520 }
516
521
517 tasks.register('probe') {
522 tasks.register('probe') {
518 dependsOn configurations.compileView
523 dependsOn configurations.compileView
519 dependsOn ':producer:checkNoManagedAssembly'
524 dependsOn ':producer:checkNoManagedAssembly'
520
525
521 doLast {
526 doLast {
522 def files = configurations.compileView.files
527 def files = configurations.compileView.files
523 println('resolvedFiles=' + files.collect { it.name }.sort().join(','))
528 println('resolvedFiles=' + files.collect { it.name }.sort().join(','))
524 println('metadata=' + files.iterator().next().text.trim())
529 println('metadata=' + files.iterator().next().text.trim())
525 }
530 }
526 }
531 }
527 }
532 }
528 """);
533 """);
529
534
530 BuildResult result = runner(":consumer:probe").build();
535 BuildResult result = runner(":consumer:probe").build();
531
536
532 assertTrue(result.getOutput().contains("producerAssemblyTasks="));
537 assertTrue(result.getOutput().contains("producerAssemblyTasks="));
533 assertTrue(result.getOutput().contains("resolvedFiles=package.json"));
538 assertTrue(result.getOutput().contains("resolvedFiles=package.json"));
534 assertTrue(result.getOutput().contains("metadata={\"name\":\"demo\"}"));
539 assertTrue(result.getOutput().contains("metadata={\"name\":\"demo\"}"));
535 assertTrue(result.task(":producer:writePackageMetadata").getOutcome() == TaskOutcome.SUCCESS);
540 assertTrue(result.task(":producer:writePackageMetadata").getOutcome() == TaskOutcome.SUCCESS);
536 assertTrue(result.task(":consumer:probe").getOutcome() == TaskOutcome.SUCCESS);
541 assertTrue(result.task(":consumer:probe").getOutcome() == TaskOutcome.SUCCESS);
537 }
542 }
538
543
539 @Test
544 @Test
540 void failsWhenTaskProducedArtifactIsMixedWithContributionAssembly() throws Exception {
545 void failsWhenTaskProducedArtifactIsMixedWithContributionAssembly() throws Exception {
541 writeSettings("variant-artifacts-mixed-assembly-mode");
546 writeSettings("variant-artifacts-mixed-assembly-mode");
542 writeFile("inputs/marker.txt", "marker\n");
547 writeFile("inputs/marker.txt", "marker\n");
543 writeBuildFile("""
548 writeBuildFile("""
544 import org.gradle.api.DefaultTask
549 import org.gradle.api.DefaultTask
545 import org.gradle.api.file.RegularFileProperty
550 import org.gradle.api.file.RegularFileProperty
546 import org.gradle.api.tasks.OutputFile
551 import org.gradle.api.tasks.OutputFile
547 import org.gradle.api.tasks.TaskAction
552 import org.gradle.api.tasks.TaskAction
548
553
549 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
554 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
550
555
551 abstract class WritePackageMetadata extends DefaultTask {
556 abstract class WritePackageMetadata extends DefaultTask {
552 @OutputFile
557 @OutputFile
553 abstract RegularFileProperty getOutputFile()
558 abstract RegularFileProperty getOutputFile()
554
559
555 @TaskAction
560 @TaskAction
556 void write() {
561 void write() {
557 outputFile.get().asFile.text = '{}\\n'
562 outputFile.get().asFile.text = '{}\\n'
558 }
563 }
559 }
564 }
560
565
561 variants.layers.create('main')
566 variants.layers.create('main')
562 variants.roles.create('main')
567 variants.roles.create('main')
563 variants.variant('browser') {
568 variants.variant('browser') {
564 role('main') {
569 role('main') {
565 layers('main')
570 layers('main')
566 }
571 }
567 }
572 }
568
573
569 def writePackageMetadata = tasks.register('writePackageMetadata', WritePackageMetadata) {
574 def writePackageMetadata = tasks.register('writePackageMetadata', WritePackageMetadata) {
570 outputFile.set(layout.buildDirectory.file('generated/package.json'))
575 outputFile.set(layout.buildDirectory.file('generated/package.json'))
571 }
576 }
572
577
573 variantArtifacts {
578 variantArtifacts {
574 variant('browser') {
579 variant('browser') {
575 primarySlot('packageMetadata') {
580 primarySlot('packageMetadata') {
576 producedBy(writePackageMetadata) {
581 producedBy(writePackageMetadata) {
577 outputFile
582 outputFile
578 }
583 }
579 from(layout.projectDirectory.file('inputs/marker.txt'))
584 from(layout.projectDirectory.file('inputs/marker.txt'))
580 }
585 }
581 }
586 }
582 }
587 }
583 """);
588 """);
584
589
585 assertBuildFails("cannot mix task-produced artifact and contribution-based assembly", "help");
590 assertBuildFails("cannot mix task-produced artifact and contribution-based assembly", "help");
586 }
591 }
587
592
588 @Test
593 @Test
589 void combinesDirectAndTopologyAwareSlotInputs() throws Exception {
594 void combinesDirectAndTopologyAwareSlotInputs() throws Exception {
590 writeSettings("variant-artifacts-combined-inputs");
595 writeSettings("variant-artifacts-combined-inputs");
591 writeFile("inputs/base.js", "console.log('base')\n");
596 writeFile("inputs/base.js", "console.log('base')\n");
592 writeFile("inputs/marker.txt", "marker\n");
597 writeFile("inputs/marker.txt", "marker\n");
593 writeBuildFile("""
598 writeBuildFile("""
594 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
599 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
595
600
596 variants.layers.create('main')
601 variants.layers.create('main')
597 variants.roles.create('main')
602 variants.roles.create('main')
598 variants.variant('browser') {
603 variants.variant('browser') {
599 role('main') {
604 role('main') {
600 layers('main')
605 layers('main')
601 }
606 }
602 }
607 }
603
608
604 variantSources.layer('main') {
609 variantSources.layer('main') {
605 sourceSet {
610 sourceSet {
606 declareOutputs('js')
611 declareOutputs('js')
607 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
612 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
608 }
613 }
609 }
614 }
610
615
611 variantArtifacts {
616 variantArtifacts {
612 variant('browser') {
617 variant('browser') {
613 primarySlot('bundle') {
618 primarySlot('bundle') {
614 fromVariant {
619 fromVariant {
615 output('js')
620 output('js')
616 }
621 }
617 from(layout.projectDirectory.file('inputs/marker.txt'))
622 from(layout.projectDirectory.file('inputs/marker.txt'))
618 }
623 }
619 }
624 }
620 }
625 }
621
626
622 tasks.register('probe') {
627 tasks.register('probe') {
623 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_bundle'
628 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_bundle'
624
629
625 doLast {
630 doLast {
626 def bundleDir = layout.buildDirectory.dir('variant-assemblies/browser/bundle').get().asFile
631 def bundleDir = layout.buildDirectory.dir('variant-assemblies/browser/bundle').get().asFile
627 assert new File(bundleDir, 'base.js').exists()
632 assert new File(bundleDir, 'base.js').exists()
628 assert new File(bundleDir, 'marker.txt').exists()
633 assert new File(bundleDir, 'marker.txt').exists()
629 }
634 }
630 }
635 }
631 """);
636 """);
632
637
633 BuildResult result = runner("probe").build();
638 BuildResult result = runner("probe").build();
634
639
635 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
640 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
636 }
641 }
637
642
638 @Test
643 @Test
639 void failsOnUnknownVariantReference() throws Exception {
644 void failsOnUnknownVariantReference() throws Exception {
640 writeSettings("variant-artifacts-missing-variant");
645 writeSettings("variant-artifacts-missing-variant");
641 writeBuildFile("""
646 writeBuildFile("""
642 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
647 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
643
648
644 variants.layers.create('main')
649 variants.layers.create('main')
645
650
646 variantArtifacts {
651 variantArtifacts {
647 variant('browser') {
652 variant('browser') {
648 slot('mainJs') {
653 slot('mainJs') {
649 fromVariant {
654 fromVariant {
650 output('js')
655 output('js')
651 }
656 }
652 }
657 }
653 }
658 }
654 }
659 }
655 """);
660 """);
656
661
657 assertBuildFails("isn't declared", "help");
662 assertBuildFails("isn't declared", "help");
658 }
663 }
659
664
660 @Test
665 @Test
661 void failsOnUnknownRoleReference() throws Exception {
666 void failsOnUnknownRoleReference() throws Exception {
662 writeSettings("variant-artifacts-missing-role");
667 writeSettings("variant-artifacts-missing-role");
663 writeBuildFile("""
668 writeBuildFile("""
664 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
669 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
665
670
666 variants.layers.create('main')
671 variants.layers.create('main')
667 variants.roles.create('main')
672 variants.roles.create('main')
668 variants.variant('browser') {
673 variants.variant('browser') {
669 role('main') {
674 role('main') {
670 layers('main')
675 layers('main')
671 }
676 }
672 }
677 }
673
678
674 variantArtifacts {
679 variantArtifacts {
675 variant('browser') {
680 variant('browser') {
676 slot('mainJs') {
681 slot('mainJs') {
677 fromRole('test') {
682 fromRole('test') {
678 output('js')
683 output('js')
679 }
684 }
680 }
685 }
681 }
686 }
682 }
687 }
683 """);
688 """);
684
689
685 assertBuildFails("Role projection for variant 'browser' and role 'test' not found", "help");
690 assertBuildFails("Role projection for variant 'browser' and role 'test' not found", "help");
686 }
691 }
687
692
688 @Test
693 @Test
689 void failsWhenPrimarySlotIsMissingForMultipleSlots() throws Exception {
694 void failsWhenPrimarySlotIsMissingForMultipleSlots() throws Exception {
690 writeSettings("variant-artifacts-missing-primary");
695 writeSettings("variant-artifacts-missing-primary");
691 writeBuildFile("""
696 writeBuildFile("""
692 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
697 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
693
698
694 variants.layers.create('main')
699 variants.layers.create('main')
695 variants.roles.create('main')
700 variants.roles.create('main')
696 variants.variant('browser') {
701 variants.variant('browser') {
697 role('main') {
702 role('main') {
698 layers('main')
703 layers('main')
699 }
704 }
700 }
705 }
701
706
702 variantSources.layer('main') {
707 variantSources.layer('main') {
703 sourceSet {
708 sourceSet {
704 declareOutputs('types', 'js')
709 declareOutputs('types', 'js')
705 }
710 }
706 }
711 }
707
712
708 variantArtifacts {
713 variantArtifacts {
709 variant('browser') {
714 variant('browser') {
710 slot('typesPackage') {
715 slot('typesPackage') {
711 fromVariant {
716 fromVariant {
712 output('types')
717 output('types')
713 }
718 }
714 }
719 }
715 slot('js') {
720 slot('js') {
716 fromVariant {
721 fromVariant {
717 output('js')
722 output('js')
718 }
723 }
719 }
724 }
720 }
725 }
721 }
726 }
722
727
723 tasks.register('probe') {
728 tasks.register('probe') {
724 doLast {
729 doLast {
725 variantArtifacts.whenAvailable { ctx ->
730 variantArtifacts.whenAvailable { ctx ->
726 def browser = objects.named(org.implab.gradle.variants.core.Variant, 'browser')
731 def browser = objects.named(org.implab.gradle.variants.core.Variant, 'browser')
727 ctx.findOutgoing(browser).get().primarySlot.get()
732 ctx.findOutgoing(browser).get().primarySlot.get()
728 }
733 }
729 }
734 }
730 }
735 }
731 """);
736 """);
732
737
733 assertBuildFails("Multiple slots declared for browser, please specify primary slot explicitly", "probe");
738 assertBuildFails("Multiple slots declared for browser, please specify primary slot explicitly", "probe");
734 }
739 }
735
740
736 @Test
741 @Test
737 void failsOnLayerReferenceOutsideVariantTopology() throws Exception {
742 void failsOnLayerReferenceOutsideVariantTopology() throws Exception {
738 writeSettings("variant-artifacts-layer-outside-topology");
743 writeSettings("variant-artifacts-layer-outside-topology");
739 writeBuildFile("""
744 writeBuildFile("""
740 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
745 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
741
746
742 variants.layers.create('mainBase')
747 variants.layers.create('mainBase')
743 variants.layers.create('extra')
748 variants.layers.create('extra')
744 variants.roles.create('main')
749 variants.roles.create('main')
745 variants.variant('browser') {
750 variants.variant('browser') {
746 role('main') {
751 role('main') {
747 layers('mainBase')
752 layers('mainBase')
748 }
753 }
749 }
754 }
750
755
751 variantArtifacts {
756 variantArtifacts {
752 variant('browser') {
757 variant('browser') {
753 slot('extraJs') {
758 slot('extraJs') {
754 fromLayer('extra') {
759 fromLayer('extra') {
755 output('js')
760 output('js')
756 }
761 }
757 }
762 }
758 }
763 }
759 }
764 }
760 """);
765 """);
761
766
762 assertBuildFails("Compile unit for variant 'browser' and layer 'extra' not found", "help");
767 assertBuildFails("Compile unit for variant 'browser' and layer 'extra' not found", "help");
763 }
768 }
764
769
765 @Test
770 @Test
766 void preservesPrimaryResolutionAndAllowsSecondaryArtifactSelection() throws Exception {
771 void preservesPrimaryResolutionAndAllowsSecondaryArtifactSelection() throws Exception {
767 writeFile("settings.gradle", """
772 writeFile("settings.gradle", """
768 rootProject.name = 'variant-artifacts-resolution'
773 rootProject.name = 'variant-artifacts-resolution'
769 include 'producer', 'consumer'
774 include 'producer', 'consumer'
770 """);
775 """);
771 writeFile("producer/inputs/types.d.ts", "export type Foo = string\n");
776 writeFile("producer/inputs/types.d.ts", "export type Foo = string\n");
772 writeFile("producer/inputs/index.js", "export const foo = 'bar'\n");
777 writeFile("producer/inputs/index.js", "export const foo = 'bar'\n");
773 writeBuildFile("""
778 writeBuildFile("""
774 import org.gradle.api.attributes.Attribute
779 import org.gradle.api.attributes.Attribute
775
780
776 def variantAttr = Attribute.of('test.variant', String)
781 def variantAttr = Attribute.of('test.variant', String)
777 def slotAttr = Attribute.of('test.slot', String)
782 def slotAttr = Attribute.of('test.slot', String)
778
783
779 subprojects {
784 subprojects {
780 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
785 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
781 }
786 }
782
787
783 project(':producer') {
788 project(':producer') {
784 variants.layers.create('main')
789 variants.layers.create('main')
785 variants.roles.create('main')
790 variants.roles.create('main')
786 variants.variant('browser') {
791 variants.variant('browser') {
787 role('main') {
792 role('main') {
788 layers('main')
793 layers('main')
789 }
794 }
790 }
795 }
791
796
792 variantSources.layer('main') {
797 variantSources.layer('main') {
793 sourceSet {
798 sourceSet {
794 declareOutputs('types', 'js')
799 declareOutputs('types', 'js')
795 registerOutput('types', layout.projectDirectory.file('inputs/types.d.ts'))
800 registerOutput('types', layout.projectDirectory.file('inputs/types.d.ts'))
796 registerOutput('js', layout.projectDirectory.file('inputs/index.js'))
801 registerOutput('js', layout.projectDirectory.file('inputs/index.js'))
797 }
802 }
798 }
803 }
799
804
800 variantArtifacts {
805 variantArtifacts {
801 variant('browser') {
806 variant('browser') {
802 primarySlot('typesPackage') {
807 primarySlot('typesPackage') {
803 fromVariant {
808 fromVariant {
804 output('types')
809 output('types')
805 }
810 }
806 }
811 }
807 slot('js') {
812 slot('js') {
808 fromVariant {
813 fromVariant {
809 output('js')
814 output('js')
810 }
815 }
811 }
816 }
812 }
817 }
813
818
814 whenOutgoingConfiguration { publication ->
819 whenOutgoingConfiguration { publication ->
815 publication.configuration {
820 publication.configuration {
816 attributes.attribute(variantAttr, publication.variant.name)
821 attributes.attribute(variantAttr, publication.variant.name)
817 }
822 }
818 }
823 }
819
824
820 whenOutgoingSlot { publication ->
825 whenOutgoingSlot { publication ->
821 publication.artifactAttributes {
826 publication.artifactAttributes {
822 attribute(slotAttr, publication.artifactSlot.slot.name)
827 attribute(slotAttr, publication.artifactSlot.slot.name)
823 }
828 }
824 }
829 }
825 }
830 }
826
831
827 }
832 }
828
833
829 project(':consumer') {
834 project(':consumer') {
830 configurations {
835 configurations {
831 compileView {
836 compileView {
832 canBeResolved = true
837 canBeResolved = true
833 canBeConsumed = false
838 canBeConsumed = false
834 canBeDeclared = true
839 canBeDeclared = true
835 attributes {
840 attributes {
836 attribute(variantAttr, 'browser')
841 attribute(variantAttr, 'browser')
837 attribute(slotAttr, 'typesPackage')
842 attribute(slotAttr, 'typesPackage')
838 }
843 }
839 }
844 }
840 }
845 }
841
846
842 dependencies {
847 dependencies {
843 compileView project(':producer')
848 compileView project(':producer')
844 }
849 }
845
850
846 tasks.register('probe') {
851 tasks.register('probe') {
847 doLast {
852 doLast {
848 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
853 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
849 def jsFiles = configurations.compileView.incoming.artifactView {
854 def jsFiles = configurations.compileView.incoming.artifactView {
850 attributes {
855 attributes {
851 attribute(slotAttr, 'js')
856 attribute(slotAttr, 'js')
852 }
857 }
853 }.files.files.collect { it.name }.sort().join(',')
858 }.files.files.collect { it.name }.sort().join(',')
854
859
855 println('compileFiles=' + compileFiles)
860 println('compileFiles=' + compileFiles)
856 println('jsFiles=' + jsFiles)
861 println('jsFiles=' + jsFiles)
857 }
862 }
858 }
863 }
859 }
864 }
860 """);
865 """);
861
866
862 BuildResult result = runner(":consumer:probe").build();
867 BuildResult result = runner(":consumer:probe").build();
863
868
864 assertTrue(result.getOutput().contains("compileFiles=typesPackage"));
869 assertTrue(result.getOutput().contains("compileFiles=typesPackage"));
865 assertTrue(result.getOutput().contains("jsFiles=js"));
870 assertTrue(result.getOutput().contains("jsFiles=js"));
866 }
871 }
867 }
872 }
General Comments 0
You need to be logged in to leave comments. Login now