Skip to content

Commit fcf0d94

Browse files
authored
Add Windows OS build and deployment (#5)
* Add Windows packaging support with .msi installer * Update and clarify information in README. * Refactored build.xml then split javadoc target in two: usage api, internal * Update src/Main.java comment about hidden constructor and add author and version tags
1 parent 178bfcc commit fcf0d94

5 files changed

Lines changed: 112 additions & 38 deletions

File tree

.github/workflows/build.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
strategy:
1515
fail-fast: false
1616
matrix:
17-
os: [macos-latest, ubuntu-latest]
17+
os: [macos-latest, ubuntu-latest, windows-latest]
1818

1919
steps:
2020
- name: Checkout Code
@@ -61,6 +61,7 @@ jobs:
6161
files: |
6262
artifacts/*.dmg
6363
artifacts/*.deb
64+
artifacts/*.msi
6465
body: "Automatic build of ${{ github.ref_name }}"
6566
env:
6667
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

README.md

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
# NativeJavaApp
22
Scaffold for creating double-clickable apps using Java
33

4-
This project uses Apache Ant and JDK 25 to compile, bundle, and package a Java application into native installers (.dmg for macOS and .deb for Ubuntu).
4+
This project uses Apache Ant and JDK 25 to compile, bundle, and package a Java application into native installers (.dmg for macOS, .deb for Ubuntu, and .msi for Windows).
55

66
## Common Requirements (All Systems)
77
Before running the build, ensure the following are installed and configured:
8-
- JDK 21 or 25: Must be installed. jpackage was introduced in JDK 14, but JDK 21+ is recommended for modern macOS/Linux support.
8+
- JDK 21 or 25: Must be installed. Although jpackage was introduced in JDK 14, JDK 21+ is recommended for modern macOS/Linux/Windows support.
99
- Apache Ant: Installed and available in your PATH.
10-
- JAVA_HOME: This environment variable must point to your JDK installation directory.
11-
- Check via: ant -version and java -version
10+
- `JAVA_HOME`: This environment variable must point to your JDK installation directory.
11+
- Check via: `ant -version` and `java -version`
1212
## macOS Setup
1313
To build the .dmg installer, your Mac needs the following:
1414
- Xcode Command Line Tools: Required for various build utilities.
15-
- Install via: xcode-select --install
15+
- Install via: `xcode-select --install`
1616
- Icon Asset: A file named icon.icns must be in the project root.
1717
- Note on Security: Since the app is not "Signed" with an Apple Developer Certificate, users may need to Right-Click > Open the app the first time to bypass the "Unidentified Developer" warning.
1818
## Ubuntu / Debian Setup
@@ -27,24 +27,35 @@ sudo apt install ant fakeroot dpkg-dev
2727
- fakeroot: Allows the package to be built with correct file permissions without requiring root access.
2828
- dpkg-dev: Provides the core utilities to create Debian packages.
2929
- Icon Asset: A file named icon.png (512x512 recommended) must be in the project root.
30+
## Windows Setup
31+
To build the .msi installer on Windows, ensure the following:
32+
- JDK 21 or 25: Must be installed with JAVA_HOME configured.
33+
- Apache Ant: Installed and available in your PATH.
34+
- WiX Toolset: Required by jpackage to create MSI installers.
35+
- Download and install from: https://wixtoolset.org/releases/
36+
- Version 3.11 or higher is recommended.
37+
- After installation, verify WiX is in your PATH by running: `candle -?`
38+
- Icon Asset: A file named icon.ico must be in the project root.
39+
- Note on Security: Windows may show a SmartScreen warning for unsigned installers. Users will need to click "More info" > "Run anyway" to install the app.
3040
## Project Directory Structure
3141
Ensure your project looks like this for the build.xml to find all resources:
3242

3343
```Plaintext
34-
MyAntApp/
44+
MyAppName/
3545
├── src/
3646
│ └── Main.java # Your source code
3747
├── icon.icns # Required for macOS DMG
3848
├── icon.png # Required for Ubuntu DEB
49+
├── icon.ico # Required for Windows MSI
3950
└── build.xml # The Ant build script
4051
```
4152
## Usage Commands
42-
Open your terminal in the project root and use the following targets:
53+
Open a terminal in the project root and use the following targets:
4354

4455
| Command | Description |
4556
|-----|-----|
4657
| ant | The default; runs the package target. |
47-
| ant package | Automatically detects OS and builds .dmg (Mac) or .deb (Linux). |
58+
| ant package | Automatically detects OS and builds .dmg (Mac), .deb (Linux), or .msi (Windows). |
4859
| ant run | Compiles and launches the app immediately for testing. |
4960
| ant clean | Deletes the build/ and dist/ folders to start fresh. |
5061
| ant -p | Displays a help menu of all available targets. |
@@ -58,6 +69,10 @@ Open your terminal in the project root and use the following targets:
5869
<td>Linux Icon Error: </td>
5970
<td>If the Linux build fails, ensure icon.png is not just a renamed .icns or .jpg. It must be a valid PNG file.</td>
6071
</tr>
72+
<tr>
73+
<td>Windows "candle.exe not found": </td>
74+
<td>Install WiX Toolset 3.11+ and ensure it's in your PATH. jpackage uses WiX to create MSI installers on Windows.</td>
75+
</tr>
6176
<tr>
6277
<td>Permissions: </td>
6378
<td>If the generated .app or .deb won't execute, ensure you have the necessary write permissions in the dist/ directory.</td>
@@ -69,15 +84,15 @@ This project uses GitHub Actions to automatically build and distribute native in
6984

7085
### The Build Phase (Continuous Integration)
7186
Every time you push code to the main branch or open a Pull Request:
72-
- GitHub starts a macOS runner and an Ubuntu runner.
73-
- Both systems compile the code and create their respective installers (.dmg and .deb).
87+
- GitHub starts a macOS runner, an Ubuntu runner, and a Windows runner.
88+
- All systems compile the code and create their respective installers (.dmg, .deb, and .msi).
7489
- The installers are saved as Artifacts in the GitHub Actions run summary for 90 days.
7590
### The Release Phase (Continuous Deployment)
7691
A formal GitHub Release is only triggered when you push a version tag.
77-
This creates a permanent download page for your users.
92+
This creates a permanent download page for users.
7893

7994
#### How to trigger a new release:
80-
To release a new version (e.g., version 1.0.1), run the following commands in your terminal:
95+
To release a new version (e.g., version 1.0.1), run the following commands in a terminal:
8196

8297
```Bash
8398
# 1. Tag the current commit
@@ -92,8 +107,9 @@ git push origin v1.0.1
92107
- Under Assets, you will find:
93108
- MyAntApp-Installer.dmg (for macOS)
94109
- MyAntApp-Linux.deb (for Ubuntu/Debian)
110+
- MyAntApp-Windows.msi (for Windows)
95111

96-
### Pro-Tip: Changing the Version Number
97-
When you're ready to bump the version, remember to update the version number in two places to keep everything in sync:
112+
### Changing the Version Number
113+
When ready to bump the version, remember to update the version number in **two** places to keep everything in sync:
98114
- The Git Tag (the v1.0.1 above).
99-
- The app.version property at the top of your build.xml. This ensures that when the user installs the app, the OS sees the correct version number in the "About" or "Get Info" screens.
115+
- The **app.version** property at the top of `build.xml`. This ensures that when the user installs the app, the OS sees the correct version number in the "About" or "Get Info" screens.

build.xml

Lines changed: 74 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,31 @@
2121
</not>
2222
</condition>
2323

24+
<macrodef name="generate-javadoc">
25+
<attribute name="access-level" default="protected"/>
26+
<attribute name="output-subdir"/>
27+
<sequential>
28+
<mkdir dir="${docs.dir}/@{output-subdir}"/>
29+
<javadoc destdir="${docs.dir}/@{output-subdir}"
30+
source="1.8"
31+
encoding="UTF-8"
32+
use="true"
33+
author="true"
34+
version="true"
35+
failonerror="true"
36+
access="@{access-level}">
37+
<fileset dir="${src.dir}" includes="**/*.java"/>
38+
<link href="${javadoc.api.url}"/>
39+
<!-- Uncomment to resolve references in Javadoc comments to lib/ jars. -->
40+
<!--
41+
<classpath>
42+
<pathelement path="${build.dir}"/>
43+
<fileset dir="lib" includes="**/*.jar" erroronmissingdir="false"/>
44+
</classpath>
45+
-->
46+
</javadoc>
47+
</sequential>
48+
</macrodef>
2449

2550
<condition property="isMac">
2651
<os family="mac"/>
@@ -33,9 +58,22 @@
3358
</and>
3459
</condition>
3560

36-
<condition property="icon.file" value="icon.icns" else="icon.png">
61+
<condition property="isWindows">
62+
<os family="windows"/>
63+
</condition>
64+
65+
<condition property="icon.file" value="icon.icns">
3766
<isset property="isMac"/>
3867
</condition>
68+
<condition property="icon.file" value="icon.ico">
69+
<and>
70+
<isset property="isWindows"/>
71+
<not><isset property="icon.file"/></not>
72+
</and>
73+
</condition>
74+
<condition property="icon.file" value="icon.png">
75+
<not><isset property="icon.file"/></not>
76+
</condition>
3977

4078
<target name="preflight" description="Print toolchain/OS info">
4179
<echo message="Java: ${java.runtime.name} ${java.runtime.version}"/>
@@ -72,31 +110,20 @@
72110
<java jar="${jar.dir}/${app.name}.jar" fork="true"/>
73111
</target>
74112

75-
<target name="javadoc" depends="preflight" description="Generate Javadoc (works for unnamed or named packages)">
76-
<mkdir dir="${docs.dir}"/>
77-
<javadoc destdir="${docs.dir}"
78-
source="1.8"
79-
encoding="UTF-8"
80-
use="true"
81-
author="true"
82-
version="true"
83-
failonerror="true">
84-
<fileset dir="${src.dir}" includes="**/*.java"/>
85-
86-
<link href="${javadoc.api.url}"/>
87-
<!-- Uncomment to resolve references in Javadoc comments. -->
88-
<!--
89-
<classpath>
90-
<pathelement path="${build.dir}"/>
91-
<fileset dir="lib" includes="**/*.jar"/>
92-
</classpath>
93-
-->
94-
</javadoc>
113+
<target name="javadoc" depends="preflight" description="Generate standard API documentation">
114+
<generate-javadoc access-level="protected" output-subdir="api"/>
95115
</target>
96116

97-
<target name="package" description="Build the native installer for the current OS (DMG or DEB)">
117+
<target name="javadoc-internal" depends="preflight" description="Generate internal documentation (includes private members)">
118+
<generate-javadoc access-level="private" output-subdir="internal"/>
119+
</target>
120+
121+
<target name="javadoc-all" depends="javadoc, javadoc-internal" description="Generate both standard and internal documentation sets"/>
122+
123+
<target name="package" description="Build the native installer for the current OS (DMG, DEB, or MSI)">
98124
<antcall target="dmg"/>
99125
<antcall target="debian"/>
126+
<antcall target="windows"/>
100127
</target>
101128

102129
<target name="dmg" depends="jar" if="isMac" description="[Mac Only] Builds the .dmg installer">
@@ -148,4 +175,29 @@
148175
<echo message="Success! Debian Package: ${dist.dir}/${app.name}-Linux.deb"/>
149176
</target>
150177

178+
<target name="windows" depends="jar" if="isWindows" description="[Windows Only] Builds the .msi installer">
179+
<mkdir dir="${dist.dir}"/>
180+
<exec executable="jpackage" failonerror="true">
181+
<arg value="--input"/><arg value="${jar.dir}"/>
182+
<arg value="--dest"/><arg value="${dist.dir}"/>
183+
<arg value="--name"/><arg value="${app.name}"/>
184+
<arg value="--main-jar"/><arg value="${app.name}.jar"/>
185+
<arg value="--main-class"/><arg value="${main.class}"/>
186+
<arg value="--type"/><arg value="msi"/>
187+
<arg value="--icon"/><arg value="${icon.file}"/>
188+
<arg value="--app-version"/><arg value="${app.version}"/>
189+
<arg value="--win-dir-chooser"/>
190+
<arg value="--win-shortcut"/>
191+
<arg value="--win-menu"/>
192+
</exec>
193+
<move todir="${dist.dir}">
194+
<fileset dir="${dist.dir}">
195+
<include name="${app.name}*.msi"/>
196+
<exclude name="${app.name}-Windows.msi"/>
197+
</fileset>
198+
<mapper type="merge" to="${app.name}-Windows.msi"/>
199+
</move>
200+
<echo message="Success! Windows Installer: ${dist.dir}/${app.name}-Windows.msi"/>
201+
</target>
202+
151203
</project>

icon.ico

151 KB
Binary file not shown.

src/Main.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@
22

33
/**
44
* Main class with main method invoked on app start.
5+
* @version 1.0.0
6+
* @author Dr. Jody Paul
57
*/
68
public class Main {
9+
/** Private constructor to prevent instantiation of entry point class. */
10+
private Main() { }
11+
712
/**
813
* Invoked on start.
914
* @param args ignored

0 commit comments

Comments
 (0)