Skip to content

Commit e46bd20

Browse files
committed
Introduce CleanProperties
PropertiesFileTransformer uses `java.utils.Properties` internally as a storage. `java.utils.Properties` `store0()` contains `bw.write("#" + new Date().toString());` that prepends current timestamp before any content (after comments). This effectively breaks reproducible builds that use PropertiesFileTransformer because every new build has different timestamp in transformed files. CleanProperties implementation is introduced in order to remove prepended timestamp when creating output stream.
1 parent d8117e9 commit e46bd20

File tree

3 files changed

+115
-4
lines changed

3 files changed

+115
-4
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Source https://stackoverflow.com/a/39043903/519333
3+
*/
4+
package com.github.jengelman.gradle.plugins.shadow.internal
5+
6+
class CleanProperties extends Properties {
7+
private static class StripFirstLineStream extends FilterOutputStream {
8+
9+
private boolean firstLineSeen = false
10+
11+
StripFirstLineStream(final OutputStream out) {
12+
super(out)
13+
}
14+
15+
@Override
16+
void write(final int b) throws IOException {
17+
if (firstLineSeen) {
18+
super.write(b);
19+
} else if (b == '\n') {
20+
super.write(b);
21+
22+
firstLineSeen = true;
23+
}
24+
}
25+
26+
}
27+
28+
@Override
29+
void store(final OutputStream out, final String comments) throws IOException {
30+
super.store(new StripFirstLineStream(out), null)
31+
}
32+
}

src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package com.github.jengelman.gradle.plugins.shadow.transformers
2121

22+
import com.github.jengelman.gradle.plugins.shadow.internal.CleanProperties
2223
import org.apache.tools.zip.ZipEntry
2324
import org.apache.tools.zip.ZipOutputStream
2425
import org.codehaus.plexus.util.IOUtil
@@ -117,7 +118,7 @@ import static groovy.lang.Closure.IDENTITY
117118
class PropertiesFileTransformer implements Transformer {
118119
private static final String PROPERTIES_SUFFIX = '.properties'
119120

120-
private Map<String, Properties> propertiesEntries = [:]
121+
private Map<String, CleanProperties> propertiesEntries = [:]
121122

122123
@Input
123124
List<String> paths = []
@@ -179,15 +180,17 @@ class PropertiesFileTransformer implements Transformer {
179180
}
180181

181182
private Properties loadAndTransformKeys(InputStream is) {
182-
Properties props = new Properties()
183-
props.load(is)
183+
Properties props = new CleanProperties()
184+
if (is != null) {
185+
props.load(is)
186+
}
184187
return transformKeys(props)
185188
}
186189

187190
private Properties transformKeys(Properties properties) {
188191
if (keyTransformer == IDENTITY)
189192
return properties
190-
def result = new Properties()
193+
def result = new CleanProperties()
191194
properties.each { key, value ->
192195
result.put(keyTransformer.call(key), value)
193196
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.github.jengelman.gradle.plugins.shadow.transformers
2+
3+
import com.github.jengelman.gradle.plugins.shadow.ShadowStats
4+
import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator
5+
import org.apache.tools.zip.ZipOutputStream
6+
import org.junit.Before
7+
import org.junit.Test
8+
9+
import java.util.zip.ZipFile
10+
11+
import static java.util.Arrays.asList
12+
import static org.junit.Assert.*
13+
14+
/**
15+
* Test for {@link PropertiesFileTransformer}.
16+
*/
17+
final class PropertiesFileTransformerTest extends TransformerTestSupport {
18+
static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"
19+
20+
private PropertiesFileTransformer transformer
21+
22+
@Before
23+
void setUp() {
24+
transformer = new PropertiesFileTransformer()
25+
}
26+
27+
@Test
28+
void testHasTransformedResource() {
29+
transformer.transform(new TransformerContext(MANIFEST_NAME))
30+
31+
assertTrue(transformer.hasTransformedResource())
32+
}
33+
34+
@Test
35+
void testHasNotTransformedResource() {
36+
assertFalse(transformer.hasTransformedResource())
37+
}
38+
39+
@Test
40+
void testTransformation() {
41+
transformer.transform(new TransformerContext(MANIFEST_NAME, getResourceStream(MANIFEST_NAME), Collections.<Relocator>emptyList(), new ShadowStats()))
42+
43+
def testableZipFile = File.createTempFile("testable-zip-file-", ".jar")
44+
def fileOutputStream = new FileOutputStream(testableZipFile)
45+
def bufferedOutputStream = new BufferedOutputStream(fileOutputStream)
46+
def zipOutputStream = new ZipOutputStream(bufferedOutputStream)
47+
48+
try {
49+
transformer.modifyOutputStream(zipOutputStream, false)
50+
} finally {
51+
zipOutputStream.close()
52+
}
53+
def targetLines = readFrom(testableZipFile, MANIFEST_NAME)
54+
55+
assertFalse(targetLines.isEmpty())
56+
57+
assertTrue(targetLines.contains("Manifest-Version=1.0"))
58+
}
59+
60+
static List<String> readFrom(File jarFile, String resourceName) {
61+
def zip = new ZipFile(jarFile)
62+
try {
63+
def entry = zip.getEntry(resourceName)
64+
if (!entry) {
65+
return Collections.emptyList()
66+
}
67+
return zip.getInputStream(entry).readLines()
68+
} finally {
69+
zip.close()
70+
}
71+
}
72+
73+
InputStream getResourceStream(String resource) {
74+
this.class.classLoader.getResourceAsStream(resource)
75+
}
76+
}

0 commit comments

Comments
 (0)