diff --git a/pom.xml b/pom.xml
index 8aae6a8c8..13feb76bc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -188,6 +188,11 @@
4.11.0
test
+
+ org.codehaus.plexus
+ plexus-utils
+ 4.0.2
+
diff --git a/src/it/property-expansion/pom.xml b/src/it/property-expansion/pom.xml
new file mode 100644
index 000000000..b6c488fd9
--- /dev/null
+++ b/src/it/property-expansion/pom.xml
@@ -0,0 +1,59 @@
+
+
+ 4.0.0
+ org.vafer
+ jdeb-it
+ 1.0
+ description from pom
+
+ 2.0.0-SNAPSHOT
+ 1
+
+ ${release.version}-${property.package.revision}
+ 1.8
+ 1.8
+
+
+
+
+ org.vafer
+ jdeb
+ @project.version@
+
+
+ package
+
+ jdeb
+
+
+ true
+ ${basedir}/src/deb/control
+
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.6.0
+
+
+ regex-property
+
+ regex-property
+
+
+ release.version
+ ${property.upstream.version}
+ -SNAPSHOT
+
+ false
+
+
+
+
+
+
+
+
diff --git a/src/it/property-expansion/src/deb/control/control b/src/it/property-expansion/src/deb/control/control
new file mode 100644
index 000000000..c3dff5c53
--- /dev/null
+++ b/src/it/property-expansion/src/deb/control/control
@@ -0,0 +1,7 @@
+Package: [[name]]
+Version: [[property.project.version]]
+Section: misc
+Priority: low
+Architecture: all
+Description: [[description]]
+Maintainer: tcurdt@vafer.org
diff --git a/src/it/property-expansion/src/main/java/org/vafer/jdeb/examples/Main.java b/src/it/property-expansion/src/main/java/org/vafer/jdeb/examples/Main.java
new file mode 100644
index 000000000..62a24347d
--- /dev/null
+++ b/src/it/property-expansion/src/main/java/org/vafer/jdeb/examples/Main.java
@@ -0,0 +1,7 @@
+package org.vafer.jdeb.examples;
+
+public class Main {
+ public static void main(String[] args) {
+ System.out.println("jdeb example!");
+ }
+}
diff --git a/src/it/property-expansion/verify.groovy b/src/it/property-expansion/verify.groovy
new file mode 100644
index 000000000..78df0e978
--- /dev/null
+++ b/src/it/property-expansion/verify.groovy
@@ -0,0 +1,54 @@
+import java.nio.file.Files
+import java.util.zip.GZIPInputStream
+import org.apache.commons.compress.archivers.ar.ArArchiveInputStream
+import org.apache.commons.compress.archivers.ar.ArArchiveEntry
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream
+
+File deb = new File(basedir, 'target/jdeb-it_1.0_all.deb')
+assert deb.exists()
+
+InputStream debInput = new FileInputStream(deb)
+ArArchiveInputStream arInput = new ArArchiveInputStream(debInput)
+List controlContent = []
+
+ArArchiveEntry entry
+while ((entry = arInput.getNextEntry()) != null) {
+ if (entry.getName() == 'control.tar.gz') {
+
+ // Save control.tar.gz to a temporary file
+ File tempControlGz = File.createTempFile("control", ".tar.gz")
+ tempControlGz.deleteOnExit()
+ tempControlGz.withOutputStream { out ->
+ byte[] buffer = new byte[4096]
+ int len
+ while ((len = arInput.read(buffer)) != -1) {
+ out.write(buffer, 0, len)
+ }
+ }
+
+ // Read the contents of control.tar.gz
+ GZIPInputStream gzipIn = new GZIPInputStream(new FileInputStream(tempControlGz))
+ TarArchiveInputStream tarIn = new TarArchiveInputStream(gzipIn)
+
+ def tarEntry
+ while ((tarEntry = tarIn.nextEntry) != null) {
+ String name = tarEntry.name
+
+ if (name == './control' || name == 'control') {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(tarIn, 'UTF-8'))
+ while (reader.readLine() != null) {
+ controlContent.add(reader.readLine());
+ }
+ break
+ }
+ }
+
+ tarIn.close()
+ gzipIn.close()
+ }
+}
+
+arInput.close()
+debInput.close()
+
+assert "Version: 2.0.0-1".equals(controlContent.get(0))
diff --git a/src/main/java/org/vafer/jdeb/maven/DebMojo.java b/src/main/java/org/vafer/jdeb/maven/DebMojo.java
index 0bd1daf4d..b6da2103a 100644
--- a/src/main/java/org/vafer/jdeb/maven/DebMojo.java
+++ b/src/main/java/org/vafer/jdeb/maven/DebMojo.java
@@ -414,37 +414,6 @@ protected void setData( Data[] dataSet ) {
}
}
- @SuppressWarnings("unchecked,rawtypes")
- protected VariableResolver initializeVariableResolver( Map variables, Long outputTimestampMs ) {
- variables.putAll((Map) getProject().getProperties());
- variables.putAll((Map) System.getProperties());
- variables.put("name", name != null ? name : getProject().getName());
- variables.put("artifactId", getProject().getArtifactId());
- variables.put("groupId", getProject().getGroupId());
- variables.put("version", getProjectVersion(outputTimestampMs));
- variables.put("description", getProject().getDescription());
- variables.put("extension", "deb");
- variables.put("baseDir", getProject().getBasedir().getAbsolutePath());
- variables.put("buildDir", buildDirectory.getAbsolutePath());
- variables.put("project.version", getProject().getVersion());
-
- if (getProject().getInceptionYear() != null) {
- variables.put("project.inceptionYear", getProject().getInceptionYear());
- }
- if (getProject().getOrganization() != null) {
- if (getProject().getOrganization().getName() != null) {
- variables.put("project.organization.name", getProject().getOrganization().getName());
- }
- if (getProject().getOrganization().getUrl() != null) {
- variables.put("project.organization.url", getProject().getOrganization().getUrl());
- }
- }
-
- variables.put("url", getProject().getUrl());
-
- return new MapVariableResolver(variables);
- }
-
/**
* Doc some cleanup and conversion on the Maven project version.
*
@@ -521,10 +490,16 @@ public void execute() throws MojoExecutionException {
console = new MojoConsole(getLog(), verbose);
initializeSignProperties();
-
+
Long outputTimestampMs = new OutputTimestampResolver(console).resolveOutputTimestamp(outputTimestamp);
- final VariableResolver resolver = initializeVariableResolver(new HashMap(), outputTimestampMs);
+ final VariableResolver resolver = MapVariableResolver.builder()
+ .withName(this.name)
+ .withVersion(getProjectVersion(outputTimestampMs))
+ .withMavenProject(getProject())
+ .withSystemProperties(System.getProperties())
+ .withBuildDirectory(this.buildDirectory.getAbsolutePath())
+ .build();
final File debFile = new File(Utils.replaceVariables(resolver, deb, openReplaceToken, closeReplaceToken));
final File controlDirFile = new File(Utils.replaceVariables(resolver, controlDir, openReplaceToken, closeReplaceToken));
diff --git a/src/main/java/org/vafer/jdeb/utils/MapVariableResolver.java b/src/main/java/org/vafer/jdeb/utils/MapVariableResolver.java
index 5dc2ab11c..303964e18 100644
--- a/src/main/java/org/vafer/jdeb/utils/MapVariableResolver.java
+++ b/src/main/java/org/vafer/jdeb/utils/MapVariableResolver.java
@@ -15,7 +15,13 @@
*/
package org.vafer.jdeb.utils;
+import java.util.HashMap;
import java.util.Map;
+import java.util.Properties;
+
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.interpolation.InterpolationException;
+import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
/**
* Resolve variables based on a Map.
@@ -34,4 +40,92 @@ public String get( String key ) {
return map.get(key);
}
+ public static MapVariableResolverBuilder builder() {
+ return new MapVariableResolverBuilder();
+ }
+
+ public static class MapVariableResolverBuilder {
+ private String name;
+ private MavenProject mavenProject;
+ private Properties systemProperties;
+ private String buildDirectory;
+ private String version;
+
+ public MapVariableResolverBuilder withName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public MapVariableResolverBuilder withMavenProject(MavenProject project) {
+ this.mavenProject = project;
+ return this;
+ }
+
+ public MapVariableResolverBuilder withSystemProperties(Properties properties) {
+ this.systemProperties = properties;
+ return this;
+ }
+
+ public MapVariableResolverBuilder withBuildDirectory(String directory) {
+ this.buildDirectory = directory;
+ return this;
+ }
+
+ public MapVariableResolverBuilder withVersion(String version) {
+ this.version = version;
+ return this;
+ }
+
+ public MapVariableResolver build() {
+ if( this.mavenProject == null || this.systemProperties == null ) {
+ throw new IllegalStateException("MavenProject and system properties must be set");
+ }
+
+ Map variables = new HashMap() ;
+
+ Map combinedProperties = new HashMap<>();
+ combinedProperties.putAll((Map) this.mavenProject.getProperties());
+ combinedProperties.putAll((Map) this.systemProperties);
+
+ // Expand (interpolate) values using RegexBasedInterpolator
+ RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
+ for (Map.Entry entry : combinedProperties.entrySet()) {
+ interpolator.addValueSource(new org.codehaus.plexus.interpolation.MapBasedValueSource(combinedProperties));
+ try {
+ String expandedValue = interpolator.interpolate(entry.getValue(), "");
+ variables.put(entry.getKey(), expandedValue);
+ } catch (InterpolationException e) {
+ // Fallback to original value if interpolation fails
+ variables.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ variables.put("name", this.name != null ? this.name : this.mavenProject.getName());
+ variables.put("artifactId", this.mavenProject.getArtifactId());
+ variables.put("groupId", this.mavenProject.getGroupId());
+ variables.put("version", this.version);
+ variables.put("description", this.mavenProject.getDescription());
+ variables.put("extension", "deb");
+ variables.put("baseDir", this.mavenProject.getBasedir().getAbsolutePath());
+ variables.put("buildDir", this.buildDirectory);
+ variables.put("project.version", this.mavenProject.getVersion());
+
+ if (this.mavenProject.getInceptionYear() != null) {
+ variables.put("project.inceptionYear", this.mavenProject.getInceptionYear());
+ }
+ if (this.mavenProject.getOrganization() != null) {
+ if (this.mavenProject.getOrganization().getName() != null) {
+ variables.put("project.organization.name", this.mavenProject.getOrganization().getName());
+ }
+ if (this.mavenProject.getOrganization().getUrl() != null) {
+ variables.put("project.organization.url", this.mavenProject.getOrganization().getUrl());
+ }
+ }
+
+ variables.put("url", this.mavenProject.getUrl());
+
+ return new MapVariableResolver(variables);
+ }
+ }
+
}
diff --git a/src/test/java/org/vafer/jdeb/utils/MapVariableResolverTestCase.java b/src/test/java/org/vafer/jdeb/utils/MapVariableResolverTestCase.java
new file mode 100644
index 000000000..0261e2f02
--- /dev/null
+++ b/src/test/java/org/vafer/jdeb/utils/MapVariableResolverTestCase.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2025 The jdeb developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.vafer.jdeb.utils;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.maven.model.Organization;
+import org.apache.maven.project.MavenProject;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.vafer.jdeb.utils.MapVariableResolver.MapVariableResolverBuilder;
+
+public final class MapVariableResolverTestCase extends Assert {
+
+ @Test(expected = IllegalStateException.class)
+ public void builderThrowsWithMissingMavenProject() throws Exception {
+ MapVariableResolverBuilder builder = MapVariableResolver.builder();
+
+ // Mock Maven Project
+ MavenProject mockMavenProject = Mockito.mock(MavenProject.class);
+ File expectedBaseDir = FileUtils.getTempDirectory();
+ Mockito.when(mockMavenProject.getBasedir()).thenReturn(expectedBaseDir);
+
+ Properties mockMavenProperties = new Properties();
+ Mockito.when(mockMavenProject.getProperties()).thenReturn(mockMavenProperties);
+
+ // Mock System properties
+ Properties mockSystemProperties = new Properties();
+
+ builder
+ .withName("Name")
+ .withVersion("2.0.0")
+ .withSystemProperties(mockSystemProperties)
+ .withBuildDirectory("aDirectory")
+ .build();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void builderThrowsWithMissingSystemProperties() throws Exception {
+ MapVariableResolverBuilder builder = MapVariableResolver.builder();
+
+ // Mock Maven Project
+ MavenProject mockMavenProject = Mockito.mock(MavenProject.class);
+ File expectedBaseDir = FileUtils.getTempDirectory();
+ Mockito.when(mockMavenProject.getBasedir()).thenReturn(expectedBaseDir);
+
+ Properties mockMavenProperties = new Properties();
+ Mockito.when(mockMavenProject.getProperties()).thenReturn(mockMavenProperties);
+
+ builder
+ .withName("Name")
+ .withVersion("2.0.0")
+ .withMavenProject(mockMavenProject)
+ .withBuildDirectory("aDirectory")
+ .build();
+ }
+
+ @Test
+ public void builderWorksWithNoData() throws Exception {
+ MapVariableResolverBuilder builder = MapVariableResolver.builder();
+
+ // Mock Maven Project
+ MavenProject mockMavenProject = Mockito.mock(MavenProject.class);
+ File expectedBaseDir = FileUtils.getTempDirectory();
+ Mockito.when(mockMavenProject.getBasedir()).thenReturn(expectedBaseDir);
+
+ Properties mockMavenProperties = new Properties();
+ Mockito.when(mockMavenProject.getProperties()).thenReturn(mockMavenProperties);
+
+ // Mock System properties
+ Properties mockSystemProperties = new Properties();
+
+ // Builder
+ MapVariableResolver resolver = builder
+ .withName("test")
+ .withVersion("2.0.0")
+ .withMavenProject(mockMavenProject)
+ .withSystemProperties(mockSystemProperties)
+ .withBuildDirectory("aDirectory")
+ .build();
+
+ assertEquals(null, resolver.get("artifactId"));
+ assertEquals(expectedBaseDir.getAbsolutePath(), resolver.get("baseDir"));
+ assertEquals("aDirectory", resolver.get("buildDir"));
+ assertEquals(null, resolver.get("description"));
+ assertEquals("deb", resolver.get("extension"));
+ assertEquals(null, resolver.get("groupId"));
+ assertEquals("test", resolver.get("name"));
+ assertEquals(null, resolver.get("project.version"));
+ assertEquals(null, resolver.get("url"));
+ assertEquals("2.0.0", resolver.get("version"));
+ }
+
+ @Test
+ public void builderWorksWithAllMandatoryData() throws Exception {
+ MapVariableResolverBuilder builder = MapVariableResolver.builder();
+
+ // Mock Maven Project
+ MavenProject mockMavenProject = Mockito.mock(MavenProject.class);
+ File expectedBaseDir = FileUtils.getTempDirectory();
+ Mockito.when(mockMavenProject.getBasedir()).thenReturn(expectedBaseDir);
+
+ LinkedHashMap mavenMap = new LinkedHashMap();
+ mavenMap.put("maven.compiler.source", "1.8");
+ mavenMap.put("maven.compiler.target", "1.8");
+ mavenMap.put("property.one", "one");
+ mavenMap.put("meta.property", "${property.one}-${property.two}-${property.three}");
+ mavenMap.put("nested.meta.property", "${property.one}+${meta.property}");
+
+ Properties mockMavenProperties = new Properties();
+ mockMavenProperties.putAll(mavenMap);
+ Mockito.when(mockMavenProject.getProperties()).thenReturn(mockMavenProperties);
+
+ Mockito.when(mockMavenProject.getArtifactId()).thenReturn("anAwesomeArtifactId");
+ Mockito.when(mockMavenProject.getGroupId()).thenReturn("anAwesomeGroupId");
+ Mockito.when(mockMavenProject.getDescription()).thenReturn("anAwesomeDescription");
+ Mockito.when(mockMavenProject.getVersion()).thenReturn("2.0.0");
+ Mockito.when(mockMavenProject.getUrl()).thenReturn("https://github.com/tcurdt/jdeb");
+
+ // Mock System Properties
+ LinkedHashMap systemMap = new LinkedHashMap();
+ systemMap.put("meta.property.system", "${property.three}");
+ systemMap.put("property.three", "three");
+ systemMap.put("property.two", "two");
+ systemMap.put("java.specification.version", "17");
+
+ Properties mockSystemProperties = new Properties();
+ mockSystemProperties.putAll(systemMap);
+
+ // Builder
+ MapVariableResolver resolver = builder
+ .withName("test")
+ .withVersion("2.0.0")
+ .withMavenProject(mockMavenProject)
+ .withSystemProperties(mockSystemProperties)
+ .withBuildDirectory("aDirectory")
+ .build();
+
+ // Expected Resolver
+ assertEquals("anAwesomeArtifactId", resolver.get("artifactId"));
+ assertEquals(expectedBaseDir.getAbsolutePath(), resolver.get("baseDir"));
+ assertEquals("aDirectory", resolver.get("buildDir"));
+ assertEquals("anAwesomeDescription", resolver.get("description"));
+ assertEquals("deb", resolver.get("extension"));
+ assertEquals("anAwesomeGroupId", resolver.get("groupId"));
+ assertEquals("17", resolver.get("java.specification.version"));
+ assertEquals("1.8", resolver.get("maven.compiler.source"));
+ assertEquals("1.8", resolver.get("maven.compiler.target"));
+ assertEquals("one-two-three", resolver.get("meta.property"));
+ assertEquals("test", resolver.get("name"));
+ assertEquals("one+one-two-three", resolver.get("nested.meta.property"));
+ assertEquals("2.0.0", resolver.get("project.version"));
+ assertEquals("one", resolver.get("property.one"));
+ assertEquals("three", resolver.get("property.three"));
+ assertEquals("two", resolver.get("property.two"));
+ assertEquals("three", resolver.get("meta.property.system"));
+ assertEquals("https://github.com/tcurdt/jdeb", resolver.get("url"));
+ assertEquals("2.0.0", resolver.get("version"));
+ }
+
+ @Test
+ public void builderWorksWithOptionalData() throws Exception {
+ MapVariableResolverBuilder builder = MapVariableResolver.builder();
+
+ // Mock Maven Project
+ MavenProject mockMavenProject = Mockito.mock(MavenProject.class);
+ File expectedBaseDir = FileUtils.getTempDirectory();
+ Mockito.when(mockMavenProject.getBasedir()).thenReturn(expectedBaseDir);
+
+ Properties mockMavenProperties = new Properties();
+ Mockito.when(mockMavenProject.getProperties()).thenReturn(mockMavenProperties);
+
+ Mockito.when(mockMavenProject.getArtifactId()).thenReturn("anAwesomeArtifactId");
+ Mockito.when(mockMavenProject.getGroupId()).thenReturn("anAwesomeGroupId");
+ Mockito.when(mockMavenProject.getDescription()).thenReturn("anAwesomeDescription");
+ Mockito.when(mockMavenProject.getVersion()).thenReturn("2.0.0");
+ Mockito.when(mockMavenProject.getUrl()).thenReturn("https://github.com/tcurdt/jdeb");
+
+ // Optional info
+ Mockito.when(mockMavenProject.getInceptionYear()).thenReturn("1990");
+
+ Organization mockOrganization = new Organization();
+ mockOrganization.setName("anAwesomeOrganization");
+ mockOrganization.setUrl("https://www.awesome.org");
+ Mockito.when(mockMavenProject.getOrganization()).thenReturn(mockOrganization);
+
+ // Mock System Properties
+ Properties mockSystemProperties = new Properties();
+
+ // Builder
+ MapVariableResolver resolver = builder
+ .withName("test")
+ .withVersion("2.0.0")
+ .withMavenProject(mockMavenProject)
+ .withSystemProperties(mockSystemProperties)
+ .withBuildDirectory("aDirectory")
+ .build();
+
+ // Expected Resolver
+ assertEquals("anAwesomeArtifactId", resolver.get("artifactId"));
+ assertEquals(expectedBaseDir.getAbsolutePath(), resolver.get("baseDir"));
+ assertEquals("aDirectory", resolver.get("buildDir"));
+ assertEquals("anAwesomeDescription", resolver.get("description"));
+ assertEquals("deb", resolver.get("extension"));
+ assertEquals("anAwesomeGroupId", resolver.get("groupId"));
+ assertEquals("test", resolver.get("name"));
+ assertEquals("2.0.0", resolver.get("project.version"));
+ assertEquals("https://github.com/tcurdt/jdeb", resolver.get("url"));
+ assertEquals("2.0.0", resolver.get("version"));
+ assertEquals("1990", resolver.get("project.inceptionYear"));
+ assertEquals("anAwesomeOrganization", resolver.get("project.organization.name"));
+ assertEquals("https://www.awesome.org", resolver.get("project.organization.url"));
+ }
+}