##// END OF EJS Templates
Upgrade Gradle to 9.4.1 and fix functional tests
cin -
r62:c7861b56e1d8 default
parent child
Show More
@@ -1,57 +1,57
1 1 plugins {
2 2 id "java-library"
3 3 id "ivy-publish"
4 4 }
5 5
6 6 description = "Shared Gradle build utilities used by Implab plugins"
7 7
8 8 java {
9 9 withJavadocJar()
10 10 withSourcesJar()
11 11 toolchain {
12 12 languageVersion = JavaLanguageVersion.of(21)
13 13 }
14 14 }
15 15
16 16 dependencies {
17 17 compileOnly libs.jdt.annotations
18 18
19 19 api gradleApi(),
20 20 libs.bundles.jackson
21 21
22 22 testImplementation gradleTestKit()
23 23 testImplementation "org.junit.jupiter:junit-jupiter-api:5.11.4"
24 24 testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.11.4"
25 25 testRuntimeOnly "org.junit.platform:junit-platform-launcher:1.11.4"
26 26 }
27 27
28 28 task printVersion{
29 29 doLast {
30 30 println "project: $project.group:$project.name:$project.version"
31 31 println "jar: ${->jar.archiveFileName.get()}"
32 32 }
33 33 }
34 34
35 35 test {
36 36 useJUnitPlatform()
37 37 }
38 38
39 39 publishing {
40 40 repositories {
41 41 ivy {
42 url "${System.properties["user.home"]}/ivy-repo"
42 url = "${System.properties["user.home"]}/ivy-repo"
43 43 }
44 44 }
45 45 publications {
46 46 ivy(IvyPublication) {
47 47 from components.java
48 48 descriptor.description {
49 49 text = providers.provider({ description })
50 50 }
51 51 descriptor.license {
52 52 name = "BSD-2-Clause"
53 53 url = "https://spdx.org/licenses/BSD-2-Clause.html"
54 54 }
55 55 }
56 56 }
57 57 }
1 NO CONTENT: modified file, binary diff hidden
@@ -1,7 +1,7
1 1 distributionBase=GRADLE_USER_HOME
2 2 distributionPath=wrapper/dists
3 distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
3 distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
4 4 networkTimeout=10000
5 5 validateDistributionUrl=true
6 6 zipStoreBase=GRADLE_USER_HOME
7 7 zipStorePath=wrapper/dists
@@ -1,249 +1,252
1 1 #!/bin/sh
2 2
3 3 #
4 4 # Copyright Β© 2015-2021 the original authors.
5 5 #
6 6 # Licensed under the Apache License, Version 2.0 (the "License");
7 7 # you may not use this file except in compliance with the License.
8 8 # You may obtain a copy of the License at
9 9 #
10 10 # https://www.apache.org/licenses/LICENSE-2.0
11 11 #
12 12 # Unless required by applicable law or agreed to in writing, software
13 13 # distributed under the License is distributed on an "AS IS" BASIS,
14 14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 15 # See the License for the specific language governing permissions and
16 16 # limitations under the License.
17 17 #
18 # SPDX-License-Identifier: Apache-2.0
19 #
18 20
19 21 ##############################################################################
20 22 #
21 23 # Gradle start up script for POSIX generated by Gradle.
22 24 #
23 25 # Important for running:
24 26 #
25 27 # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 28 # noncompliant, but you have some other compliant shell such as ksh or
27 29 # bash, then to run this script, type that shell name before the whole
28 30 # command line, like:
29 31 #
30 32 # ksh Gradle
31 33 #
32 34 # Busybox and similar reduced shells will NOT work, because this script
33 35 # requires all of these POSIX shell features:
34 36 # * functions;
35 37 # * expansions Β«$varΒ», Β«${var}Β», Β«${var:-default}Β», Β«${var+SET}Β»,
36 38 # Β«${var#prefix}Β», Β«${var%suffix}Β», and Β«$( cmd )Β»;
37 39 # * compound commands having a testable exit status, especially Β«caseΒ»;
38 40 # * various built-in commands including Β«commandΒ», Β«setΒ», and Β«ulimitΒ».
39 41 #
40 42 # Important for patching:
41 43 #
42 44 # (2) This script targets any POSIX shell, so it avoids extensions provided
43 45 # by Bash, Ksh, etc; in particular arrays are avoided.
44 46 #
45 47 # The "traditional" practice of packing multiple parameters into a
46 48 # space-separated string is a well documented source of bugs and security
47 49 # problems, so this is (mostly) avoided, by progressively accumulating
48 50 # options in "$@", and eventually passing that to Java.
49 51 #
50 52 # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 53 # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 54 # see the in-line comments for details.
53 55 #
54 56 # There are tweaks for specific operating systems such as AIX, CygWin,
55 57 # Darwin, MinGW, and NonStop.
56 58 #
57 59 # (3) This script is generated from the Groovy template
58 # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
60 # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 61 # within the Gradle project.
60 62 #
61 63 # You can find Gradle at https://github.com/gradle/gradle/.
62 64 #
63 65 ##############################################################################
64 66
65 67 # Attempt to set APP_HOME
66 68
67 69 # Resolve links: $0 may be a link
68 70 app_path=$0
69 71
70 72 # Need this for daisy-chained symlinks.
71 73 while
72 74 APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 75 [ -h "$app_path" ]
74 76 do
75 77 ls=$( ls -ld "$app_path" )
76 78 link=${ls#*' -> '}
77 79 case $link in #(
78 80 /*) app_path=$link ;; #(
79 81 *) app_path=$APP_HOME$link ;;
80 82 esac
81 83 done
82 84
83 85 # This is normally unused
84 86 # shellcheck disable=SC2034
85 87 APP_BASE_NAME=${0##*/}
86 88 # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
87 APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
89 APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
90 ' "$PWD" ) || exit
88 91
89 92 # Use the maximum available, or set MAX_FD != -1 to use that value.
90 93 MAX_FD=maximum
91 94
92 95 warn () {
93 96 echo "$*"
94 97 } >&2
95 98
96 99 die () {
97 100 echo
98 101 echo "$*"
99 102 echo
100 103 exit 1
101 104 } >&2
102 105
103 106 # OS specific support (must be 'true' or 'false').
104 107 cygwin=false
105 108 msys=false
106 109 darwin=false
107 110 nonstop=false
108 111 case "$( uname )" in #(
109 112 CYGWIN* ) cygwin=true ;; #(
110 113 Darwin* ) darwin=true ;; #(
111 114 MSYS* | MINGW* ) msys=true ;; #(
112 115 NONSTOP* ) nonstop=true ;;
113 116 esac
114 117
115 118 CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
116 119
117 120
118 121 # Determine the Java command to use to start the JVM.
119 122 if [ -n "$JAVA_HOME" ] ; then
120 123 if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
121 124 # IBM's JDK on AIX uses strange locations for the executables
122 125 JAVACMD=$JAVA_HOME/jre/sh/java
123 126 else
124 127 JAVACMD=$JAVA_HOME/bin/java
125 128 fi
126 129 if [ ! -x "$JAVACMD" ] ; then
127 130 die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
128 131
129 132 Please set the JAVA_HOME variable in your environment to match the
130 133 location of your Java installation."
131 134 fi
132 135 else
133 136 JAVACMD=java
134 137 if ! command -v java >/dev/null 2>&1
135 138 then
136 139 die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 140
138 141 Please set the JAVA_HOME variable in your environment to match the
139 142 location of your Java installation."
140 143 fi
141 144 fi
142 145
143 146 # Increase the maximum file descriptors if we can.
144 147 if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
145 148 case $MAX_FD in #(
146 149 max*)
147 150 # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
148 151 # shellcheck disable=SC2039,SC3045
149 152 MAX_FD=$( ulimit -H -n ) ||
150 153 warn "Could not query maximum file descriptor limit"
151 154 esac
152 155 case $MAX_FD in #(
153 156 '' | soft) :;; #(
154 157 *)
155 158 # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
156 159 # shellcheck disable=SC2039,SC3045
157 160 ulimit -n "$MAX_FD" ||
158 161 warn "Could not set maximum file descriptor limit to $MAX_FD"
159 162 esac
160 163 fi
161 164
162 165 # Collect all arguments for the java command, stacking in reverse order:
163 166 # * args from the command line
164 167 # * the main class name
165 168 # * -classpath
166 169 # * -D...appname settings
167 170 # * --module-path (only if needed)
168 171 # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
169 172
170 173 # For Cygwin or MSYS, switch paths to Windows format before running java
171 174 if "$cygwin" || "$msys" ; then
172 175 APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
173 176 CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
174 177
175 178 JAVACMD=$( cygpath --unix "$JAVACMD" )
176 179
177 180 # Now convert the arguments - kludge to limit ourselves to /bin/sh
178 181 for arg do
179 182 if
180 183 case $arg in #(
181 184 -*) false ;; # don't mess with options #(
182 185 /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183 186 [ -e "$t" ] ;; #(
184 187 *) false ;;
185 188 esac
186 189 then
187 190 arg=$( cygpath --path --ignore --mixed "$arg" )
188 191 fi
189 192 # Roll the args list around exactly as many times as the number of
190 193 # args, so each arg winds up back in the position where it started, but
191 194 # possibly modified.
192 195 #
193 196 # NB: a `for` loop captures its iteration list before it begins, so
194 197 # changing the positional parameters here affects neither the number of
195 198 # iterations, nor the values presented in `arg`.
196 199 shift # remove old arg
197 200 set -- "$@" "$arg" # push replacement arg
198 201 done
199 202 fi
200 203
201 204
202 205 # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203 206 DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204 207
205 208 # Collect all arguments for the java command:
206 209 # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207 210 # and any embedded shellness will be escaped.
208 211 # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209 212 # treated as '${Hostname}' itself on the command line.
210 213
211 214 set -- \
212 215 "-Dorg.gradle.appname=$APP_BASE_NAME" \
213 216 -classpath "$CLASSPATH" \
214 217 org.gradle.wrapper.GradleWrapperMain \
215 218 "$@"
216 219
217 220 # Stop when "xargs" is not available.
218 221 if ! command -v xargs >/dev/null 2>&1
219 222 then
220 223 die "xargs is not available"
221 224 fi
222 225
223 226 # Use "xargs" to parse quoted args.
224 227 #
225 228 # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
226 229 #
227 230 # In Bash we could simply go:
228 231 #
229 232 # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
230 233 # set -- "${ARGS[@]}" "$@"
231 234 #
232 235 # but POSIX shell has neither arrays nor command substitution, so instead we
233 236 # post-process each arg (as a line of input to sed) to backslash-escape any
234 237 # character that might be a shell metacharacter, then use eval to reverse
235 238 # that process (while maintaining the separation between arguments), and wrap
236 239 # the whole thing up as a single "set" statement.
237 240 #
238 241 # This will of course break if any of these variables contains a newline or
239 242 # an unmatched quote.
240 243 #
241 244
242 245 eval "set -- $(
243 246 printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
244 247 xargs -n1 |
245 248 sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
246 249 tr '\n' ' '
247 250 )" '"$@"'
248 251
249 252 exec "$JAVACMD" "$@"
@@ -1,92 +1,94
1 1 @rem
2 2 @rem Copyright 2015 the original author or authors.
3 3 @rem
4 4 @rem Licensed under the Apache License, Version 2.0 (the "License");
5 5 @rem you may not use this file except in compliance with the License.
6 6 @rem You may obtain a copy of the License at
7 7 @rem
8 8 @rem https://www.apache.org/licenses/LICENSE-2.0
9 9 @rem
10 10 @rem Unless required by applicable law or agreed to in writing, software
11 11 @rem distributed under the License is distributed on an "AS IS" BASIS,
12 12 @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 13 @rem See the License for the specific language governing permissions and
14 14 @rem limitations under the License.
15 15 @rem
16 @rem SPDX-License-Identifier: Apache-2.0
17 @rem
16 18
17 19 @if "%DEBUG%"=="" @echo off
18 20 @rem ##########################################################################
19 21 @rem
20 22 @rem Gradle startup script for Windows
21 23 @rem
22 24 @rem ##########################################################################
23 25
24 26 @rem Set local scope for the variables with windows NT shell
25 27 if "%OS%"=="Windows_NT" setlocal
26 28
27 29 set DIRNAME=%~dp0
28 30 if "%DIRNAME%"=="" set DIRNAME=.
29 31 @rem This is normally unused
30 32 set APP_BASE_NAME=%~n0
31 33 set APP_HOME=%DIRNAME%
32 34
33 35 @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 36 for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 37
36 38 @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 39 set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 40
39 41 @rem Find java.exe
40 42 if defined JAVA_HOME goto findJavaFromJavaHome
41 43
42 44 set JAVA_EXE=java.exe
43 45 %JAVA_EXE% -version >NUL 2>&1
44 46 if %ERRORLEVEL% equ 0 goto execute
45 47
46 48 echo. 1>&2
47 49 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
48 50 echo. 1>&2
49 51 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
50 52 echo location of your Java installation. 1>&2
51 53
52 54 goto fail
53 55
54 56 :findJavaFromJavaHome
55 57 set JAVA_HOME=%JAVA_HOME:"=%
56 58 set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 59
58 60 if exist "%JAVA_EXE%" goto execute
59 61
60 62 echo. 1>&2
61 63 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
62 64 echo. 1>&2
63 65 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
64 66 echo location of your Java installation. 1>&2
65 67
66 68 goto fail
67 69
68 70 :execute
69 71 @rem Setup the command line
70 72
71 73 set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 74
73 75
74 76 @rem Execute Gradle
75 77 "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 78
77 79 :end
78 80 @rem End local scope for the variables with windows NT shell
79 81 if %ERRORLEVEL% equ 0 goto mainEnd
80 82
81 83 :fail
82 84 rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 85 rem the _cmd.exe /c_ return code!
84 86 set EXIT_CODE=%ERRORLEVEL%
85 87 if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 88 if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 89 exit /b %EXIT_CODE%
88 90
89 91 :mainEnd
90 92 if "%OS%"=="Windows_NT" endlocal
91 93
92 94 :omega
@@ -1,24 +1,24
1 1 /*
2 2 * This settings file was generated by the Gradle 'init' task.
3 3 *
4 4 * The settings file is used to specify which projects to include in your build.
5 5 * In a single project build this file can be empty or even removed.
6 6 *
7 7 * Detailed information about configuring a multi-project build in Gradle can be found
8 8 * in the user guide at https://docs.gradle.org/3.5/userguide/multi_project_builds.html
9 9 */
10 10
11 11 dependencyResolutionManagement {
12 12 repositories {
13 13 mavenCentral()
14 14 mavenLocal()
15 15 ivy {
16 url "${System.properties["user.home"]}/ivy-repo"
16 url = "${System.properties["user.home"]}/ivy-repo"
17 17 }
18 18 }
19 19 }
20 20
21 21 rootProject.name = 'gradle-common'
22 22
23 23 include 'common'
24 24 include 'variants'
@@ -1,61 +1,61
1 1 plugins {
2 2 id "java-library"
3 3 id "ivy-publish"
4 4 }
5 5
6 6 description = "Variant, source-set, and outgoing artifact model plugins for Gradle builds"
7 7
8 8 java {
9 9 withJavadocJar()
10 10 withSourcesJar()
11 11 toolchain {
12 12 languageVersion = JavaLanguageVersion.of(21)
13 13 }
14 14 }
15 15
16 16 dependencies {
17 17 compileOnly libs.jdt.annotations
18 18
19 19 api gradleApi(),
20 20 project(":common")
21 21
22 22 testImplementation gradleTestKit()
23 23 testImplementation "org.junit.jupiter:junit-jupiter-api:5.11.4"
24 24 testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.11.4"
25 25 testRuntimeOnly "org.junit.platform:junit-platform-launcher:1.11.4"
26 26 }
27 27
28 28 task printVersion{
29 29 doLast {
30 30 println "project: $project.group:$project.name:$project.version"
31 31 println "jar: ${->jar.archiveFileName.get()}"
32 32 }
33 33 }
34 34
35 35 test {
36 36 useJUnitPlatform()
37 37 }
38 38
39 39 javadoc {
40 40 exclude "**/internal/**"
41 41 }
42 42
43 43 publishing {
44 44 repositories {
45 45 ivy {
46 url "${System.properties["user.home"]}/ivy-repo"
46 url = "${System.properties["user.home"]}/ivy-repo"
47 47 }
48 48 }
49 49 publications {
50 50 ivy(IvyPublication) {
51 51 from components.java
52 52 descriptor.description {
53 53 text = providers.provider({ description })
54 54 }
55 55 descriptor.license {
56 56 name = "BSD-2-Clause"
57 57 url = "https://spdx.org/licenses/BSD-2-Clause.html"
58 58 }
59 59 }
60 60 }
61 61 }
@@ -1,98 +1,123
1 1 package org.implab.gradle.variants;
2 2
3 3 import static org.junit.jupiter.api.Assertions.assertTrue;
4 4
5 5 import java.io.File;
6 6 import java.io.IOException;
7 7 import java.nio.file.Files;
8 8 import java.nio.file.Path;
9 9 import java.util.LinkedHashSet;
10 10 import java.util.List;
11 11 import java.util.stream.Collectors;
12 import java.util.regex.Pattern;
12 13
13 14 import org.gradle.testkit.runner.GradleRunner;
14 15 import org.gradle.testkit.runner.UnexpectedBuildFailure;
15 16 import org.implab.gradle.common.core.lang.Deferred;
16 17 import org.implab.gradle.variants.sources.GenericSourceSet;
17 18 import org.junit.jupiter.api.io.TempDir;
18 19
19 20 abstract class AbstractFunctionalTest {
20 21 private static final String SETTINGS_FILE = "settings.gradle";
21 22 private static final String BUILD_FILE = "build.gradle";
23 private static final Pattern INCLUDE_DECLARATION = Pattern.compile("^include(?:\\s|\\().*");
24 private static final Pattern QUOTED_LITERAL = Pattern.compile("['\"]([^'\"]+)['\"]");
22 25
23 26 @TempDir
24 27 Path testProjectDir;
25 28
26 29 protected void writeSettings(String rootProjectName) throws IOException {
27 30 writeFile(SETTINGS_FILE, "rootProject.name = '" + rootProjectName + "'\n");
28 31 }
29 32
30 33 protected void writeBuildFile(String body) throws IOException {
31 34 writeFile(BUILD_FILE, buildscriptClasspathBlock() + "\n" + body);
32 35 }
33 36
34 37 protected GradleRunner runner(String... arguments) {
35 38 return GradleRunner.create()
36 39 .withProjectDir(testProjectDir.toFile())
37 40 .withArguments(arguments)
38 41 .forwardOutput();
39 42 }
40 43
41 44 protected void assertBuildFails(String expectedError, String... arguments) {
42 45 var ex = org.junit.jupiter.api.Assertions.assertThrows(
43 46 UnexpectedBuildFailure.class,
44 47 () -> runner(arguments).build());
45 48
46 49 var output = ex.getBuildResult().getOutput();
47 50 assertTrue(output.contains(expectedError), () -> "Expected [" + expectedError + "] in output:\n" + output);
48 51 }
49 52
50 53 private static String buildscriptClasspathBlock() {
51 54 var classpath = pluginClasspath().stream()
52 55 .map(file -> "'" + file.getAbsolutePath().replace("\\", "\\\\") + "'")
53 56 .collect(Collectors.joining(", "));
54 57
55 58 return """
56 59 buildscript {
57 60 dependencies {
58 61 classpath files(%s)
59 62 }
60 63 }
61 64 """.formatted(classpath);
62 65 }
63 66
64 67 private static List<File> pluginClasspath() {
65 68 try {
66 69 var files = new LinkedHashSet<File>();
67 70 files.add(codeSource(VariantSourcesPlugin.class));
68 71 files.add(resourceRoot("META-INF/gradle-plugins/org.implab.gradle-variants.properties"));
69 72 files.add(codeSource(GenericSourceSet.class));
70 73 files.add(codeSource(Deferred.class));
71 74 return List.copyOf(files);
72 75 } catch (Exception e) {
73 76 throw new RuntimeException("Unable to build plugin classpath for test", e);
74 77 }
75 78 }
76 79
77 80 private static File codeSource(Class<?> type) throws Exception {
78 81 return Path.of(type.getProtectionDomain().getCodeSource().getLocation().toURI()).toFile();
79 82 }
80 83
81 84 private static File resourceRoot(String resourceName) throws Exception {
82 85 var url = VariantSourcesPlugin.class.getClassLoader().getResource(resourceName);
83 86 if (url == null)
84 87 throw new IllegalStateException("Classpath resource isn't found: " + resourceName);
85 88
86 89 var path = Path.of(url.toURI());
87 90 var segments = resourceName.split("/").length;
88 91 for (var i = 0; i < segments; i++)
89 92 path = path.getParent();
90 93 return path.toFile();
91 94 }
92 95
93 96 protected void writeFile(String relativePath, String content) throws IOException {
94 97 Path path = testProjectDir.resolve(relativePath);
95 98 Files.createDirectories(path.getParent());
96 99 Files.writeString(path, content);
100
101 if (SETTINGS_FILE.equals(relativePath))
102 ensureIncludedProjectDirectories(content);
103 }
104
105 private void ensureIncludedProjectDirectories(String settingsContent) throws IOException {
106 for (var line : settingsContent.split("\\R")) {
107 var trimmed = line.strip();
108 if (!INCLUDE_DECLARATION.matcher(trimmed).matches())
109 continue;
110
111 var matcher = QUOTED_LITERAL.matcher(trimmed);
112 while (matcher.find()) {
113 var projectPath = matcher.group(1);
114 if (projectPath.startsWith(":"))
115 projectPath = projectPath.substring(1);
116 if (projectPath.isBlank())
117 continue;
118
119 Files.createDirectories(testProjectDir.resolve(projectPath.replace(':', File.separatorChar)));
97 120 }
98 121 }
122 }
123 }
@@ -1,867 +1,867
1 1 package org.implab.gradle.variants;
2 2
3 3 import static org.junit.jupiter.api.Assertions.assertTrue;
4 4
5 5 import org.gradle.testkit.runner.BuildResult;
6 6 import org.gradle.testkit.runner.TaskOutcome;
7 7 import org.junit.jupiter.api.Test;
8 8
9 9 class VariantArtifactsPluginFunctionalTest extends AbstractFunctionalTest {
10 10
11 11 @Test
12 12 void gradleReferenceLazyOutgoingConfigurationAllowsSecondaryArtifactSelection() throws Exception {
13 13 writeFile("settings.gradle", """
14 14 rootProject.name = 'gradle-reference-outgoing-resolution'
15 15 include 'producer', 'consumer'
16 16 """);
17 17 writeFile("producer/inputs/typesPackage", "types\n");
18 18 writeFile("producer/inputs/js", "js\n");
19 19 writeBuildFile("""
20 20 import org.gradle.api.attributes.Attribute
21 21
22 22 def variantAttr = Attribute.of('test.variant', String)
23 23 def slotAttr = Attribute.of('test.slot', String)
24 24
25 25 project(':producer') {
26 26 def browserElements = configurations.consumable('browserElements')
27 27
28 28 println('reference: registered browserElements provider')
29 29
30 30 browserElements.configure { configuration ->
31 31 println('reference: configuring browserElements')
32 32
33 33 configuration.attributes.attribute(variantAttr, 'browser')
34 34 configuration.outgoing.attributes.attribute(slotAttr, 'typesPackage')
35 35 configuration.outgoing.artifact(layout.projectDirectory.file('inputs/typesPackage'))
36 36
37 37 configuration.outgoing.variants.create('js') { secondary ->
38 38 println('reference: creating js outgoing variant')
39 39
40 40 secondary.attributes.attribute(slotAttr, 'js')
41 41 secondary.artifact(layout.projectDirectory.file('inputs/js'))
42 42 }
43 43 }
44 44 }
45 45
46 46 project(':consumer') {
47 47 configurations {
48 48 compileView {
49 49 canBeResolved = true
50 50 canBeConsumed = false
51 51 canBeDeclared = true
52 52 attributes {
53 53 attribute(variantAttr, 'browser')
54 54 attribute(slotAttr, 'typesPackage')
55 55 }
56 56 }
57 57 }
58 58
59 59 dependencies {
60 60 compileView project(':producer')
61 61 }
62 62
63 63 tasks.register('probe') {
64 64 doLast {
65 65 println('reference: resolving primary files')
66 66
67 67 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
68 68
69 69 println('reference: resolving secondary files')
70 70
71 71 def jsFiles = configurations.compileView.incoming.artifactView {
72 72 attributes {
73 73 attribute(slotAttr, 'js')
74 74 }
75 75 }.files.files.collect { it.name }.sort().join(',')
76 76
77 77 println('compileFiles=' + compileFiles)
78 78 println('jsFiles=' + jsFiles)
79 79 }
80 80 }
81 81 }
82 82 """);
83 83
84 84 BuildResult result = runner(":consumer:probe").build();
85 85 var output = result.getOutput();
86 86 var registered = output.indexOf("reference: registered browserElements provider");
87 87 var resolvingPrimary = output.indexOf("reference: resolving primary files");
88 88 var configuring = output.indexOf("reference: configuring browserElements");
89 89 var creatingSecondary = output.indexOf("reference: creating js outgoing variant");
90 90 var resolvingSecondary = output.indexOf("reference: resolving secondary files");
91 91
92 92 assertTrue(registered >= 0);
93 93 assertTrue(resolvingPrimary >= 0);
94 94 assertTrue(configuring >= 0);
95 95 assertTrue(creatingSecondary >= 0);
96 96 assertTrue(resolvingSecondary >= 0);
97 97 assertTrue(registered < resolvingPrimary);
98 98 assertTrue(resolvingPrimary < configuring);
99 99 assertTrue(configuring < creatingSecondary);
100 100 assertTrue(creatingSecondary < resolvingSecondary);
101 101 assertTrue(output.contains("compileFiles=typesPackage"));
102 102 assertTrue(output.contains("jsFiles=js"));
103 103 }
104 104
105 105 @Test
106 void gradleReferenceRegisteredSecondaryArtifactVariantIsNotRealizedBeforeResolution() throws Exception {
107 // Gradle issue: https://github.com/gradle/gradle/issues/27441
108 // Registered outgoing artifact variants are not realized before dependency resolution.
106 void gradleReferenceRegisteredSecondaryArtifactVariantAllowsSecondaryArtifactSelection() throws Exception {
109 107 writeFile("settings.gradle", """
110 108 rootProject.name = 'gradle-reference-registered-secondary-variant'
111 109 include 'producer', 'consumer'
112 110 """);
113 111 writeFile("producer/inputs/typesPackage", "types\n");
114 112 writeFile("producer/inputs/js", "js\n");
115 113 writeFile("build.gradle", """
116 114 import org.gradle.api.attributes.Attribute
117 115
118 116 def variantAttr = Attribute.of('test.variant', String)
119 117 def slotAttr = Attribute.of('test.slot', String)
120 118
121 119 project(':producer') {
122 120 def browserElements = configurations.consumable('browserElements')
123 121
124 122 browserElements.configure { configuration ->
125 123 configuration.attributes.attribute(variantAttr, 'browser')
126 124 configuration.outgoing.attributes.attribute(slotAttr, 'typesPackage')
127 125 configuration.outgoing.artifact(layout.projectDirectory.file('inputs/typesPackage'))
128 126
129 127 configuration.outgoing.variants.register('js') { secondary ->
130 128 secondary.attributes.attribute(slotAttr, 'js')
131 129 secondary.artifact(layout.projectDirectory.file('inputs/js'))
132 130 }
133 131 }
134 132 }
135 133
136 134 project(':consumer') {
137 135 configurations {
138 136 compileView {
139 137 canBeResolved = true
140 138 canBeConsumed = false
141 139 canBeDeclared = true
142 140 attributes {
143 141 attribute(variantAttr, 'browser')
144 142 attribute(slotAttr, 'typesPackage')
145 143 }
146 144 }
147 145 }
148 146
149 147 dependencies {
150 148 compileView project(':producer')
151 149 }
152 150
153 151 tasks.register('probe') {
154 152 doLast {
155 153 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
156 154 def jsFiles = configurations.compileView.incoming.artifactView {
157 155 attributes {
158 156 attribute(slotAttr, 'js')
159 157 }
160 158 }.files.files.collect { it.name }.sort().join(',')
161 159
162 160 println('compileFiles=' + compileFiles)
163 161 println('jsFiles=' + jsFiles)
164 162 }
165 163 }
166 164 }
167 165 """);
168 166
169 assertBuildFails("Cannot create variant 'js' after dependency configuration ':producer:browserElements' has been resolved",
170 ":consumer:probe");
167 BuildResult result = runner(":consumer:probe").build();
168
169 assertTrue(result.getOutput().contains("compileFiles=typesPackage"));
170 assertTrue(result.getOutput().contains("jsFiles=js"));
171 171 }
172 172
173 173 @Test
174 174 void materializesPrimaryAndSecondarySlotsAndInvokesOutgoingHooks() throws Exception {
175 175 writeSettings("variant-artifacts-slots");
176 176 writeFile("inputs/base.js", "console.log('base')\n");
177 177 writeFile("inputs/amd.js", "console.log('amd')\n");
178 178 writeFile("inputs/mainJs.txt", "mainJs marker\n");
179 179 writeFile("inputs/amdJs.txt", "amdJs marker\n");
180 180 writeBuildFile("""
181 181 import org.gradle.api.attributes.Attribute
182 182
183 183 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
184 184
185 185 def variantAttr = Attribute.of('test.variant', String)
186 186 def slotAttr = Attribute.of('test.slot', String)
187 187
188 188 variants.layers.create('mainBase')
189 189 variants.layers.create('mainAmd')
190 190 variants.roles.create('main')
191 191 variants.roles.create('test')
192 192 variants.variant('browser') {
193 193 role('main') {
194 194 layers('mainBase', 'mainAmd')
195 195 }
196 196 }
197 197
198 198 variantSources {
199 199 layer('mainBase') {
200 200 sourceSet {
201 201 declareOutputs('js')
202 202 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
203 203 }
204 204 }
205 205 layer('mainAmd') {
206 206 sourceSet {
207 207 declareOutputs('js')
208 208 registerOutput('js', layout.projectDirectory.file('inputs/amd.js'))
209 209 }
210 210 }
211 211 }
212 212
213 213 variantArtifacts {
214 214 variant('browser') {
215 215 primarySlot('mainJs') {
216 216 fromRole('main') {
217 217 output('js')
218 218 }
219 219 from(layout.projectDirectory.file('inputs/mainJs.txt'))
220 220 }
221 221 slot('amdJs') {
222 222 fromLayer('mainAmd') {
223 223 output('js')
224 224 }
225 225 from(layout.projectDirectory.file('inputs/amdJs.txt'))
226 226 }
227 227 }
228 228
229 229 whenOutgoingConfiguration { publication ->
230 230 publication.configuration {
231 231 attributes.attribute(variantAttr, publication.variant.name)
232 232 }
233 233 }
234 234
235 235 whenOutgoingSlot { publication ->
236 236 def slotName = publication.artifactSlot.slot.name
237 237 publication.artifactAttributes {
238 238 attribute(slotAttr, slotName)
239 239 }
240 240 }
241 241 }
242 242
243 243 tasks.register('probe') {
244 244 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_mainJs'
245 245 dependsOn 'assembleVariantArtifactSlot_v7_browser_s5_amdJs'
246 246
247 247 doLast {
248 248 def mainDir = layout.buildDirectory.dir('variant-assemblies/browser/mainJs').get().asFile
249 249 def amdDir = layout.buildDirectory.dir('variant-assemblies/browser/amdJs').get().asFile
250 250
251 251 assert new File(mainDir, 'base.js').exists()
252 252 assert new File(mainDir, 'amd.js').exists()
253 253 assert new File(mainDir, 'mainJs.txt').exists()
254 254
255 255 assert !new File(amdDir, 'base.js').exists()
256 256 assert new File(amdDir, 'amd.js').exists()
257 257 assert new File(amdDir, 'amdJs.txt').exists()
258 258
259 259 def elements = configurations.getByName('browserElements')
260 260 def amdVariant = elements.outgoing.variants.getByName('amdJs')
261 261
262 262 println('variantAttr=' + elements.attributes.getAttribute(variantAttr))
263 263 println('primarySlotAttr=' + elements.outgoing.attributes.getAttribute(slotAttr))
264 264 println('amdSlotAttr=' + amdVariant.attributes.getAttribute(slotAttr))
265 265 println('configurations=' + configurations.matching { it.name == 'browserElements' }.collect { it.name }.join(','))
266 266 println('secondaryVariants=' + elements.outgoing.variants.collect { it.name }.sort().join(','))
267 267 }
268 268 }
269 269 """);
270 270
271 271 BuildResult result = runner("probe").build();
272 272
273 273 assertTrue(result.getOutput().contains("variantAttr=browser"));
274 274 assertTrue(result.getOutput().contains("primarySlotAttr=mainJs"));
275 275 assertTrue(result.getOutput().contains("amdSlotAttr=amdJs"));
276 276 assertTrue(result.getOutput().contains("configurations=browserElements"));
277 277 assertTrue(result.getOutput().contains("secondaryVariants=amdJs"));
278 278 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
279 279 }
280 280
281 281 @Test
282 282 void outgoingSlotHookFollowsMaterializedGradleArtifactVariants() throws Exception {
283 283 writeSettings("variant-artifacts-materialized-gradle-variant");
284 284 writeFile("inputs/typesPackage", "types\n");
285 285 writeFile("inputs/js", "js\n");
286 286 writeBuildFile("""
287 287 import org.gradle.api.attributes.Attribute
288 288
289 289 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
290 290
291 291 def slotAttr = Attribute.of('test.slot', String)
292 292
293 293 variants.layers.create('main')
294 294 variants.roles.create('main')
295 295 variants.variant('browser') {
296 296 role('main') {
297 297 layers('main')
298 298 }
299 299 }
300 300
301 301 variantArtifacts {
302 302 variant('browser') {
303 303 primarySlot('typesPackage') {
304 304 from(layout.projectDirectory.file('inputs/typesPackage'))
305 305 }
306 306 }
307 307
308 308 whenOutgoingConfiguration { publication ->
309 309 publication.configuration {
310 310 outgoing.variants.create('js') { secondary ->
311 311 secondary.artifact(layout.projectDirectory.file('inputs/js'))
312 312 }
313 313 }
314 314 }
315 315
316 316 whenOutgoingSlot { publication ->
317 317 publication.artifactAttributes {
318 318 attribute(slotAttr, publication.artifactSlot.slot.name)
319 319 }
320 320 }
321 321 }
322 322
323 323 tasks.register('probe') {
324 324 doLast {
325 325 def elements = configurations.getByName('browserElements')
326 326 def jsVariant = elements.outgoing.variants.getByName('js')
327 327
328 328 println('primarySlotAttr=' + elements.outgoing.attributes.getAttribute(slotAttr))
329 329 println('jsSlotAttr=' + jsVariant.attributes.getAttribute(slotAttr))
330 330 }
331 331 }
332 332 """);
333 333
334 334 BuildResult result = runner("probe").build();
335 335
336 336 assertTrue(result.getOutput().contains("primarySlotAttr=typesPackage"));
337 337 assertTrue(result.getOutput().contains("jsSlotAttr=js"));
338 338 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
339 339 }
340 340
341 341 @Test
342 342 void allowsSingleSlotVariantWithoutExplicitPrimarySlot() throws Exception {
343 343 writeSettings("variant-artifacts-single-slot");
344 344 writeBuildFile("""
345 345 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
346 346
347 347 variants.layers.create('main')
348 348 variants.roles.create('main')
349 349 variants.variant('browser') {
350 350 role('main') {
351 351 layers('main')
352 352 }
353 353 }
354 354
355 355 variantSources.layer('main') {
356 356 sourceSet {
357 357 declareOutputs('types')
358 358 }
359 359 }
360 360
361 361 variantArtifacts {
362 362 variant('browser') {
363 363 slot('typesPackage') {
364 364 fromVariant {
365 365 output('types')
366 366 }
367 367 }
368 368 }
369 369 }
370 370
371 371 tasks.register('probe') {
372 372 doLast {
373 373 variantArtifacts.whenAvailable { ctx ->
374 374 def browser = objects.named(org.implab.gradle.variants.core.Variant, 'browser')
375 375 println('primary=' + ctx.findOutgoing(browser).get().primarySlot.get().name)
376 376 }
377 377 }
378 378 }
379 379 """);
380 380
381 381 BuildResult result = runner("probe").build();
382 382
383 383 assertTrue(result.getOutput().contains("primary=typesPackage"));
384 384 }
385 385
386 386 @Test
387 387 void materializesDirectSlotInputsWithoutVariantSourceBindings() throws Exception {
388 388 writeSettings("variant-artifacts-direct-input");
389 389 writeFile("inputs/bundle.js", "console.log('bundle')\n");
390 390 writeBuildFile("""
391 391 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
392 392
393 393 variants.layers.create('main')
394 394 variants.roles.create('main')
395 395 variants.variant('browser') {
396 396 role('main') {
397 397 layers('main')
398 398 }
399 399 }
400 400
401 401 variantArtifacts {
402 402 variant('browser') {
403 403 primarySlot('bundle') {
404 404 from(layout.projectDirectory.file('inputs/bundle.js'))
405 405 }
406 406 }
407 407 }
408 408
409 409 tasks.register('probe') {
410 410 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_bundle'
411 411
412 412 doLast {
413 413 def bundleDir = layout.buildDirectory.dir('variant-assemblies/browser/bundle').get().asFile
414 414 assert new File(bundleDir, 'bundle.js').exists()
415 415 }
416 416 }
417 417 """);
418 418
419 419 BuildResult result = runner("probe").build();
420 420
421 421 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
422 422 }
423 423
424 424 @Test
425 425 void publishesTaskProducedFileArtifactDirectly() throws Exception {
426 426 writeFile("settings.gradle", """
427 427 rootProject.name = 'variant-artifacts-task-produced-file'
428 428 include 'producer', 'consumer'
429 429 """);
430 430 writeBuildFile("""
431 431 import org.gradle.api.DefaultTask
432 432 import org.gradle.api.attributes.Attribute
433 433 import org.gradle.api.file.RegularFileProperty
434 434 import org.gradle.api.tasks.OutputFile
435 435 import org.gradle.api.tasks.TaskAction
436 436
437 437 def variantAttr = Attribute.of('test.variant', String)
438 438 def slotAttr = Attribute.of('test.slot', String)
439 439
440 440 abstract class WritePackageMetadata extends DefaultTask {
441 441 @OutputFile
442 442 abstract RegularFileProperty getOutputFile()
443 443
444 444 @TaskAction
445 445 void write() {
446 446 def file = outputFile.get().asFile
447 447 file.parentFile.mkdirs()
448 448 file.text = '{"name":"demo"}\\n'
449 449 }
450 450 }
451 451
452 452 project(':producer') {
453 453 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
454 454
455 455 variants.layers.create('main')
456 456 variants.roles.create('main')
457 457 variants.variant('browser') {
458 458 role('main') {
459 459 layers('main')
460 460 }
461 461 }
462 462
463 463 def writePackageMetadata = tasks.register('writePackageMetadata', WritePackageMetadata) {
464 464 outputFile.set(layout.buildDirectory.file('generated/package.json'))
465 465 }
466 466
467 467 variantArtifacts {
468 468 variant('browser') {
469 469 primarySlot('packageMetadata') {
470 470 producedBy(writePackageMetadata) {
471 471 outputFile
472 472 }
473 473 }
474 474 }
475 475
476 476 whenOutgoingConfiguration { publication ->
477 477 publication.configuration {
478 478 attributes.attribute(variantAttr, publication.variant.name)
479 479 }
480 480 }
481 481
482 482 whenOutgoingSlot { publication ->
483 483 publication.artifactAttributes {
484 484 attribute(slotAttr, publication.artifactSlot.slot.name)
485 485 }
486 486 }
487 487 }
488 488
489 489 tasks.register('checkNoManagedAssembly') {
490 490 doLast {
491 491 def assemblyTasks = tasks.names
492 492 .findAll { it.startsWith('assembleVariantArtifactSlot') }
493 493 .sort()
494 494 println('producerAssemblyTasks=' + assemblyTasks.join(','))
495 495 assert assemblyTasks.empty
496 496 }
497 497 }
498 498 }
499 499
500 500 project(':consumer') {
501 501 configurations {
502 502 compileView {
503 503 canBeResolved = true
504 504 canBeConsumed = false
505 505 canBeDeclared = true
506 506 attributes {
507 507 attribute(variantAttr, 'browser')
508 508 attribute(slotAttr, 'packageMetadata')
509 509 }
510 510 }
511 511 }
512 512
513 513 dependencies {
514 514 compileView project(':producer')
515 515 }
516 516
517 517 tasks.register('probe') {
518 518 dependsOn configurations.compileView
519 519 dependsOn ':producer:checkNoManagedAssembly'
520 520
521 521 doLast {
522 522 def files = configurations.compileView.files
523 523 println('resolvedFiles=' + files.collect { it.name }.sort().join(','))
524 524 println('metadata=' + files.iterator().next().text.trim())
525 525 }
526 526 }
527 527 }
528 528 """);
529 529
530 530 BuildResult result = runner(":consumer:probe").build();
531 531
532 532 assertTrue(result.getOutput().contains("producerAssemblyTasks="));
533 533 assertTrue(result.getOutput().contains("resolvedFiles=package.json"));
534 534 assertTrue(result.getOutput().contains("metadata={\"name\":\"demo\"}"));
535 535 assertTrue(result.task(":producer:writePackageMetadata").getOutcome() == TaskOutcome.SUCCESS);
536 536 assertTrue(result.task(":consumer:probe").getOutcome() == TaskOutcome.SUCCESS);
537 537 }
538 538
539 539 @Test
540 540 void failsWhenTaskProducedArtifactIsMixedWithContributionAssembly() throws Exception {
541 541 writeSettings("variant-artifacts-mixed-assembly-mode");
542 542 writeFile("inputs/marker.txt", "marker\n");
543 543 writeBuildFile("""
544 544 import org.gradle.api.DefaultTask
545 545 import org.gradle.api.file.RegularFileProperty
546 546 import org.gradle.api.tasks.OutputFile
547 547 import org.gradle.api.tasks.TaskAction
548 548
549 549 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
550 550
551 551 abstract class WritePackageMetadata extends DefaultTask {
552 552 @OutputFile
553 553 abstract RegularFileProperty getOutputFile()
554 554
555 555 @TaskAction
556 556 void write() {
557 557 outputFile.get().asFile.text = '{}\\n'
558 558 }
559 559 }
560 560
561 561 variants.layers.create('main')
562 562 variants.roles.create('main')
563 563 variants.variant('browser') {
564 564 role('main') {
565 565 layers('main')
566 566 }
567 567 }
568 568
569 569 def writePackageMetadata = tasks.register('writePackageMetadata', WritePackageMetadata) {
570 570 outputFile.set(layout.buildDirectory.file('generated/package.json'))
571 571 }
572 572
573 573 variantArtifacts {
574 574 variant('browser') {
575 575 primarySlot('packageMetadata') {
576 576 producedBy(writePackageMetadata) {
577 577 outputFile
578 578 }
579 579 from(layout.projectDirectory.file('inputs/marker.txt'))
580 580 }
581 581 }
582 582 }
583 583 """);
584 584
585 585 assertBuildFails("cannot mix task-produced artifact and contribution-based assembly", "help");
586 586 }
587 587
588 588 @Test
589 589 void combinesDirectAndTopologyAwareSlotInputs() throws Exception {
590 590 writeSettings("variant-artifacts-combined-inputs");
591 591 writeFile("inputs/base.js", "console.log('base')\n");
592 592 writeFile("inputs/marker.txt", "marker\n");
593 593 writeBuildFile("""
594 594 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
595 595
596 596 variants.layers.create('main')
597 597 variants.roles.create('main')
598 598 variants.variant('browser') {
599 599 role('main') {
600 600 layers('main')
601 601 }
602 602 }
603 603
604 604 variantSources.layer('main') {
605 605 sourceSet {
606 606 declareOutputs('js')
607 607 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
608 608 }
609 609 }
610 610
611 611 variantArtifacts {
612 612 variant('browser') {
613 613 primarySlot('bundle') {
614 614 fromVariant {
615 615 output('js')
616 616 }
617 617 from(layout.projectDirectory.file('inputs/marker.txt'))
618 618 }
619 619 }
620 620 }
621 621
622 622 tasks.register('probe') {
623 623 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_bundle'
624 624
625 625 doLast {
626 626 def bundleDir = layout.buildDirectory.dir('variant-assemblies/browser/bundle').get().asFile
627 627 assert new File(bundleDir, 'base.js').exists()
628 628 assert new File(bundleDir, 'marker.txt').exists()
629 629 }
630 630 }
631 631 """);
632 632
633 633 BuildResult result = runner("probe").build();
634 634
635 635 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
636 636 }
637 637
638 638 @Test
639 639 void failsOnUnknownVariantReference() throws Exception {
640 640 writeSettings("variant-artifacts-missing-variant");
641 641 writeBuildFile("""
642 642 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
643 643
644 644 variants.layers.create('main')
645 645
646 646 variantArtifacts {
647 647 variant('browser') {
648 648 slot('mainJs') {
649 649 fromVariant {
650 650 output('js')
651 651 }
652 652 }
653 653 }
654 654 }
655 655 """);
656 656
657 657 assertBuildFails("isn't declared", "help");
658 658 }
659 659
660 660 @Test
661 661 void failsOnUnknownRoleReference() throws Exception {
662 662 writeSettings("variant-artifacts-missing-role");
663 663 writeBuildFile("""
664 664 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
665 665
666 666 variants.layers.create('main')
667 667 variants.roles.create('main')
668 668 variants.variant('browser') {
669 669 role('main') {
670 670 layers('main')
671 671 }
672 672 }
673 673
674 674 variantArtifacts {
675 675 variant('browser') {
676 676 slot('mainJs') {
677 677 fromRole('test') {
678 678 output('js')
679 679 }
680 680 }
681 681 }
682 682 }
683 683 """);
684 684
685 685 assertBuildFails("Role projection for variant 'browser' and role 'test' not found", "help");
686 686 }
687 687
688 688 @Test
689 689 void failsWhenPrimarySlotIsMissingForMultipleSlots() throws Exception {
690 690 writeSettings("variant-artifacts-missing-primary");
691 691 writeBuildFile("""
692 692 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
693 693
694 694 variants.layers.create('main')
695 695 variants.roles.create('main')
696 696 variants.variant('browser') {
697 697 role('main') {
698 698 layers('main')
699 699 }
700 700 }
701 701
702 702 variantSources.layer('main') {
703 703 sourceSet {
704 704 declareOutputs('types', 'js')
705 705 }
706 706 }
707 707
708 708 variantArtifacts {
709 709 variant('browser') {
710 710 slot('typesPackage') {
711 711 fromVariant {
712 712 output('types')
713 713 }
714 714 }
715 715 slot('js') {
716 716 fromVariant {
717 717 output('js')
718 718 }
719 719 }
720 720 }
721 721 }
722 722
723 723 tasks.register('probe') {
724 724 doLast {
725 725 variantArtifacts.whenAvailable { ctx ->
726 726 def browser = objects.named(org.implab.gradle.variants.core.Variant, 'browser')
727 727 ctx.findOutgoing(browser).get().primarySlot.get()
728 728 }
729 729 }
730 730 }
731 731 """);
732 732
733 733 assertBuildFails("Multiple slots declared for browser, please specify primary slot explicitly", "probe");
734 734 }
735 735
736 736 @Test
737 737 void failsOnLayerReferenceOutsideVariantTopology() throws Exception {
738 738 writeSettings("variant-artifacts-layer-outside-topology");
739 739 writeBuildFile("""
740 740 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
741 741
742 742 variants.layers.create('mainBase')
743 743 variants.layers.create('extra')
744 744 variants.roles.create('main')
745 745 variants.variant('browser') {
746 746 role('main') {
747 747 layers('mainBase')
748 748 }
749 749 }
750 750
751 751 variantArtifacts {
752 752 variant('browser') {
753 753 slot('extraJs') {
754 754 fromLayer('extra') {
755 755 output('js')
756 756 }
757 757 }
758 758 }
759 759 }
760 760 """);
761 761
762 762 assertBuildFails("Compile unit for variant 'browser' and layer 'extra' not found", "help");
763 763 }
764 764
765 765 @Test
766 766 void preservesPrimaryResolutionAndAllowsSecondaryArtifactSelection() throws Exception {
767 767 writeFile("settings.gradle", """
768 768 rootProject.name = 'variant-artifacts-resolution'
769 769 include 'producer', 'consumer'
770 770 """);
771 771 writeFile("producer/inputs/types.d.ts", "export type Foo = string\n");
772 772 writeFile("producer/inputs/index.js", "export const foo = 'bar'\n");
773 773 writeBuildFile("""
774 774 import org.gradle.api.attributes.Attribute
775 775
776 776 def variantAttr = Attribute.of('test.variant', String)
777 777 def slotAttr = Attribute.of('test.slot', String)
778 778
779 779 subprojects {
780 780 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
781 781 }
782 782
783 783 project(':producer') {
784 784 variants.layers.create('main')
785 785 variants.roles.create('main')
786 786 variants.variant('browser') {
787 787 role('main') {
788 788 layers('main')
789 789 }
790 790 }
791 791
792 792 variantSources.layer('main') {
793 793 sourceSet {
794 794 declareOutputs('types', 'js')
795 795 registerOutput('types', layout.projectDirectory.file('inputs/types.d.ts'))
796 796 registerOutput('js', layout.projectDirectory.file('inputs/index.js'))
797 797 }
798 798 }
799 799
800 800 variantArtifacts {
801 801 variant('browser') {
802 802 primarySlot('typesPackage') {
803 803 fromVariant {
804 804 output('types')
805 805 }
806 806 }
807 807 slot('js') {
808 808 fromVariant {
809 809 output('js')
810 810 }
811 811 }
812 812 }
813 813
814 814 whenOutgoingConfiguration { publication ->
815 815 publication.configuration {
816 816 attributes.attribute(variantAttr, publication.variant.name)
817 817 }
818 818 }
819 819
820 820 whenOutgoingSlot { publication ->
821 821 publication.artifactAttributes {
822 822 attribute(slotAttr, publication.artifactSlot.slot.name)
823 823 }
824 824 }
825 825 }
826 826
827 827 }
828 828
829 829 project(':consumer') {
830 830 configurations {
831 831 compileView {
832 832 canBeResolved = true
833 833 canBeConsumed = false
834 834 canBeDeclared = true
835 835 attributes {
836 836 attribute(variantAttr, 'browser')
837 837 attribute(slotAttr, 'typesPackage')
838 838 }
839 839 }
840 840 }
841 841
842 842 dependencies {
843 843 compileView project(':producer')
844 844 }
845 845
846 846 tasks.register('probe') {
847 847 doLast {
848 848 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
849 849 def jsFiles = configurations.compileView.incoming.artifactView {
850 850 attributes {
851 851 attribute(slotAttr, 'js')
852 852 }
853 853 }.files.files.collect { it.name }.sort().join(',')
854 854
855 855 println('compileFiles=' + compileFiles)
856 856 println('jsFiles=' + jsFiles)
857 857 }
858 858 }
859 859 }
860 860 """);
861 861
862 862 BuildResult result = runner(":consumer:probe").build();
863 863
864 864 assertTrue(result.getOutput().contains("compileFiles=typesPackage"));
865 865 assertTrue(result.getOutput().contains("jsFiles=js"));
866 866 }
867 867 }
General Comments 0
You need to be logged in to leave comments. Login now