Skip to content

Commit 59f3f3e

Browse files
authored
Codesign on Windows (#20)
Co-authored-by: U-DESKTOP-QVUEOL6\tanin <@tanin>
1 parent 5341ae9 commit 59f3f3e

File tree

5 files changed

+162
-74
lines changed

5 files changed

+162
-74
lines changed

build.gradle.kts

Lines changed: 153 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -256,12 +256,12 @@ tasks.register("copyJar", Copy::class) {
256256
from(tasks.jar).into(layout.buildDirectory.dir("jmods"))
257257
}
258258

259-
private fun runCmd(vararg args: String): String {
259+
private fun runCmd(currentDir: File, vararg args: String): String {
260260
println("Executing command: ${args.joinToString(" ")}")
261261

262262
val output = StringBuilder()
263263
val process = ProcessBuilder(*args)
264-
.directory(layout.projectDirectory.asFile)
264+
.directory(currentDir)
265265
.start()
266266

267267
// Print stdout
@@ -286,49 +286,72 @@ private fun runCmd(vararg args: String): String {
286286
return output.toString()
287287
}
288288

289+
private fun runCmd(vararg args: String): String {
290+
return runCmd(layout.projectDirectory.asFile, *args)
291+
}
292+
289293
private fun codesign(file: File, useRuntimeEntitlement: Boolean = false) {
290-
runCmd(
291-
"codesign",
292-
"-vvvv",
293-
"--options",
294-
"runtime",
295-
"--entitlements",
296-
if (useRuntimeEntitlement) {
297-
"runtime-entitlements.plist"
298-
} else {
299-
"entitlements.plist"
300-
},
301-
"--timestamp",
302-
"--force",
303-
"--sign",
304-
macDeveloperApplicationCertName,
305-
file.absolutePath,
306-
)
294+
if (currentOS == OS.MAC) {
295+
runCmd(
296+
"codesign",
297+
"-vvvv",
298+
"--options",
299+
"runtime",
300+
"--entitlements",
301+
if (useRuntimeEntitlement) {
302+
"runtime-entitlements.plist"
303+
} else {
304+
"entitlements.plist"
305+
},
306+
"--timestamp",
307+
"--force",
308+
"--sign",
309+
macDeveloperApplicationCertName,
310+
file.absolutePath,
311+
)
312+
}
307313
}
308314

309315
private fun isCodesignable(file: File): Boolean {
310-
val excludedExtensions = setOf("so", "a", "xml")
311-
return file.extension == "dylib" ||
312-
file.extension == "jnilib" ||
313-
(!excludedExtensions.contains(file.extension) && file.toPath().isExecutable())
316+
if (currentOS == OS.MAC) {
317+
val excludedExtensions = setOf("so", "a", "xml")
318+
return file.extension == "dylib" ||
319+
file.extension == "jnilib" ||
320+
(!excludedExtensions.contains(file.extension) && file.toPath().isExecutable())
321+
} else if (currentOS == OS.WINDOWS) {
322+
return file.extension == "dll"
323+
} else {
324+
throw Exception("Unsupported OS: $currentOS")
325+
}
326+
}
327+
328+
private fun isValidLibFile(file: File): Boolean {
329+
if (currentOS == OS.MAC) {
330+
return !(
331+
file.absolutePath.contains("darwin-x86-64") || // for libjnidispatch.jnilib
332+
file.absolutePath.contains("x86_64") // for liblz4-java.dylib
333+
)
334+
} else if (currentOS == OS.WINDOWS) {
335+
return !(
336+
file.absolutePath.endsWith("win32-aarch64\\jnidispatch.dll") ||
337+
file.absolutePath.endsWith("win32-x86\\jnidispatch.dll")
338+
)
339+
} else {
340+
throw Exception("Unsupported OS: $currentOS")
341+
}
314342
}
315343

316344
private fun codesignInJar(jarFile: File, nativeLibPath: File) {
317-
val tmpDir = createTempDirectory("MacosCodesignLibsInJarsTask").toFile()
345+
val tmpDir = createTempDirectory("codesignLibsInJarsTask").toFile()
318346
runCmd("unzip", "-q", jarFile.absolutePath, "-d", tmpDir.absolutePath)
319347

320348
tmpDir.walk()
321349
.filter { it.isFile && isCodesignable(it) }
322350
.forEach { libFile ->
323-
println("")
324-
codesign(libFile)
325-
326-
if (
327-
libFile.absolutePath.contains("darwin-x86-64") || // for libjnidispatch.jnilib
328-
libFile.absolutePath.contains("x86_64") // for liblz4-java.dylib
329-
) {
330-
// Skip the jna's x86-64 lib
331-
} else {
351+
352+
if (isValidLibFile(libFile)) {
353+
println("")
354+
codesign(libFile)
332355
runCmd("cp", libFile.absolutePath, nativeLibPath.absolutePath)
333356
}
334357

@@ -344,7 +367,7 @@ private fun codesignInJar(jarFile: File, nativeLibPath: File) {
344367
tmpDir.deleteRecursively()
345368
}
346369

347-
tasks.register("macosCodesignLibsInJars") {
370+
tasks.register("codesignLibsInJars") {
348371
dependsOn("copyDependencies", "copyJar")
349372
inputs.files(tasks.named("copyJar").get().outputs.files)
350373

@@ -383,6 +406,7 @@ private fun removeQuarantine(file: File) {
383406
}
384407

385408
tasks.register("macosCodesignProvisionprofile") {
409+
onlyIf { currentOS == OS.MAC }
386410
doLast {
387411
removeQuarantine(provisionprofileDir.file("embedded.provisionprofile").asFile)
388412
codesign(provisionprofileDir.file("embedded.provisionprofile").asFile)
@@ -393,7 +417,7 @@ tasks.register("macosCodesignProvisionprofile") {
393417
}
394418

395419
tasks.register<Exec>("jlink") {
396-
dependsOn("assemble", "macosCodesignLibsInJars", "macosCodesignProvisionprofile")
420+
dependsOn("assemble", "codesignLibsInJars", "macosCodesignProvisionprofile")
397421
val jlinkBin = Paths.get(System.getProperty("java.home"), "bin", "jlink")
398422

399423
inputs.files(tasks.named("copyJar").get().outputs.files)
@@ -412,6 +436,7 @@ tasks.register<Exec>("jlink") {
412436
}
413437

414438
tasks.register("prepareInfoPlist") {
439+
onlyIf { currentOS == OS.MAC }
415440
doLast {
416441
val template = layout.projectDirectory.file("mac-resources/Info.plist.template").asFile.readText()
417442
val content = template
@@ -435,45 +460,78 @@ tasks.register("bareJpackage") {
435460
inputs.files(runtimeImage, modulePath)
436461

437462
val outputDir = layout.buildDirectory.dir("bare-jpackage")
438-
val outputFile = outputDir.get().asFile.resolve("${appName}-$version.dmg")
463+
val outputFile = if (currentOS == OS.MAC) {
464+
outputDir.get().asFile.resolve("${appName}-$version.dmg")
465+
} else if (currentOS == OS.WINDOWS) {
466+
outputDir.get().asFile.resolve("${appName}-$version.msi")
467+
} else {
468+
throw Exception("Unsupported OS: $currentOS")
469+
}
439470

440471
outputs.file(outputFile)
441472
outputDir.get().asFile.deleteRecursively()
442473

443-
// -XstartOnFirstThread is required for MacOS
444-
val maybeStartOnFirstThread = if (currentOS == OS.MAC) {
445-
"-XstartOnFirstThread"
446-
} else {
447-
""
448-
}
449-
450474
doLast {
451-
runCmd(
452-
jpackageBin.absolutePathString(),
475+
// -XstartOnFirstThread is required for MacOS
476+
val maybeStartOnFirstThread = if (currentOS == OS.MAC) {
477+
"-XstartOnFirstThread"
478+
} else {
479+
""
480+
}
481+
482+
val javaOptionsArg = listOf(
483+
"--java-options",
484+
// -Djava.library.path=$APPDIR/resources is needed because we put the needed dylibs, jnilibs, and dlls there.
485+
"$maybeStartOnFirstThread -Djava.electron.packaged=true -Djava.library.path=\$APPDIR/resources --add-exports java.base/sun.security.x509=ALL-UNNAMED --add-exports java.base/sun.security.tools.keytool=ALL-UNNAMED"
486+
)
487+
488+
val baseArgs = listOf(
453489
"--name", appName,
454490
"--app-version", version.toString(),
455491
"--main-jar", modulePath.resolve("${project.name}-$version.jar").absolutePath,
456492
"--main-class", mainClassName,
457493
"--runtime-image", runtimeImage.absolutePath,
458494
"--input", modulePath.absolutePath,
459495
"--dest", outputDir.get().asFile.absolutePath,
460-
"--mac-package-identifier", packageIdentifier,
461-
"--mac-package-name", appName,
462-
"--mac-sign",
463-
"--mac-app-store",
464-
"--mac-signing-key-user-name", macDeveloperApplicationCertName,
465-
"--mac-entitlements", "entitlements.plist",
466-
"--resource-dir", layout.projectDirectory.dir("mac-resources").asFile.absolutePath,
467-
"--app-content", provisionprofileDir.file("embedded.provisionprofile").asFile.absolutePath,
468496
"--app-content", layout.buildDirectory.file("resources-native").get().asFile.resolve("app").absolutePath,
469-
"--java-options",
470-
// -Djava.library.path=$APPDIR/resources is needed because we put all dylibs there.
471-
"$maybeStartOnFirstThread -Djava.library.path=\$APPDIR/resources --add-exports java.base/sun.security.x509=ALL-UNNAMED --add-exports java.base/sun.security.tools.keytool=ALL-UNNAMED"
497+
) + javaOptionsArg
498+
499+
val platformSpecificArgs = if (currentOS == OS.MAC) {
500+
listOf(
501+
"--mac-package-identifier",
502+
packageIdentifier,
503+
"--mac-package-name",
504+
appName,
505+
"--mac-sign",
506+
"--mac-app-store",
507+
"--mac-signing-key-user-name",
508+
macDeveloperApplicationCertName,
509+
"--mac-entitlements",
510+
"entitlements.plist",
511+
"--resource-dir",
512+
layout.projectDirectory.dir("mac-resources").asFile.absolutePath,
513+
"--app-content",
514+
provisionprofileDir.file("embedded.provisionprofile").asFile.absolutePath,
515+
)
516+
} else if (currentOS == OS.WINDOWS) {
517+
listOf(
518+
"--type", "msi",
519+
"--win-menu",
520+
"--win-shortcut"
521+
)
522+
} else {
523+
throw Exception("Unsupported OS: $currentOS")
524+
}
525+
526+
runCmd(
527+
*((listOf(jpackageBin.absolutePathString())
528+
+ baseArgs + platformSpecificArgs).toTypedArray())
472529
)
473530
}
474531
}
475532

476-
tasks.register("jpackage") {
533+
tasks.register("jpackageForMac") {
534+
onlyIf { currentOS == OS.MAC }
477535
dependsOn("bareJpackage")
478536

479537
inputs.file(tasks.named("bareJpackage").get().outputs.files.singleFile)
@@ -533,10 +591,43 @@ tasks.register("jpackage") {
533591
}
534592
}
535593

594+
595+
tasks.register("jpackageForWindows") {
596+
onlyIf { currentOS == OS.WINDOWS }
597+
dependsOn("bareJpackage")
598+
599+
inputs.file(tasks.named("bareJpackage").get().outputs.files.singleFile)
600+
601+
val outputAppDir = layout.buildDirectory.dir("msi").get().asFile
602+
603+
outputs.dir(outputAppDir)
604+
605+
doLast {
606+
outputAppDir.deleteRecursively()
607+
outputAppDir.mkdirs()
608+
609+
runCmd(
610+
File("c:\\Users\\tanin\\projects\\CodeSignTool-v1.3.2-windows"),
611+
"CodeSignTool.bat",
612+
"sign",
613+
"-input_file_path=${inputs.files.singleFile.absolutePath}",
614+
"-output_dir_path=${outputAppDir.absolutePath}",
615+
"-program_name=JavaElectron",
616+
"-username=${System.getenv("SSL_COM_USERNAME")}",
617+
"-password=${System.getenv("SSL_COM_PASSWORD")}",
618+
"-totp_secret=${System.getenv("SSL_COM_TOTP_SECRET")}"
619+
)
620+
}
621+
}
622+
623+
tasks.register("jpackage") {
624+
dependsOn("bareJpackage", "jpackageForMac", "jpackageForWindows")
625+
}
626+
536627
tasks.register<Exec>("notarize") {
537-
dependsOn("jpackage")
628+
dependsOn("jpackageForMac")
538629

539-
inputs.file(tasks.named("jpackage").get().outputs.files.filter { it.extension == "dmg" }.first())
630+
inputs.file(tasks.named("jpackageForMac").get().outputs.files.filter { it.extension == "dmg" }.first())
540631

541632
commandLine(
542633
"/usr/bin/xcrun",
@@ -554,7 +645,7 @@ tasks.register<Exec>("notarize") {
554645
tasks.register<Exec>("staple") {
555646
dependsOn("notarize")
556647

557-
inputs.file(tasks.named("jpackage").get().outputs.files.filter { it.extension == "dmg" }.first())
648+
inputs.file(tasks.named("jpackageForMac").get().outputs.files.filter { it.extension == "dmg" }.first())
558649

559650
commandLine(
560651
"/usr/bin/xcrun",
@@ -566,8 +657,8 @@ tasks.register<Exec>("staple") {
566657
}
567658

568659
tasks.register<Exec>("convertToPkg") {
569-
dependsOn("jpackage")
570-
val app = tasks.named("jpackage").get().outputs.files.filter { it.extension == "app" }.first()
660+
dependsOn("jpackageForMac")
661+
val app = tasks.named("jpackageForMac").get().outputs.files.filter { it.extension == "app" }.first()
571662
inputs.dir(app)
572663
outputs.file(layout.buildDirectory.dir("pkg").get().file("Backdoor.pkg"))
573664
commandLine(

src/main/java/tanin/javaelectron/Browser.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,13 @@
1717
import static tanin.javaelectron.nativeinterface.WebviewNative.N;
1818

1919
public class Browser {
20-
public static interface OnFileSelected {
21-
void invoke(String path);
22-
}
23-
2420
private static final Logger logger = Logger.getLogger(Browser.class.getName());
2521

2622
String url;
2723
boolean isDebug;
2824
private long pointer;
2925
SelfSignedCertificate cert;
3026

31-
private static final String OS_NAME = System.getProperty("os.name").toLowerCase();
32-
3327
private MacOsApi.OnFileSelected onFileSelected = null;
3428

3529
public Browser(String url, boolean isDebug) {
@@ -78,6 +72,10 @@ private void terminate() {
7872
}
7973
}
8074

75+
public static interface OnFileSelected {
76+
void invoke(String path);
77+
}
78+
8179
void openFileDialog(boolean isSaved, OnFileSelected fileSelected) {
8280
if (Base.CURRENT_OS == Base.OperatingSystem.WINDOWS) {
8381
var thread = new Thread(() -> {
@@ -114,7 +112,7 @@ void openFileDialog(boolean isSaved, OnFileSelected fileSelected) {
114112
MacOsApi.N.openFile(onFileSelected);
115113
}
116114
} else {
117-
throw new RuntimeException("Unsupported OS: " + OS_NAME);
115+
throw new RuntimeException("Unsupported OS: " + Base.CURRENT_OS);
118116
}
119117
}
120118
}

src/main/java/tanin/javaelectron/Main.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ public static void main(String[] args) throws Exception {
3535
main.start();
3636

3737
var port = main.minum.getSslServer().getPort();
38-
// var port = main.minum.getServer().getPort();
3938

4039
var browser = new Browser(
4140
"https://localhost:" + port + "?authKey=" + authKey,

src/main/java/tanin/javaelectron/nativeinterface/Base.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ private static OperatingSystem determineOS() {
2424
public static final File nativeDir;
2525

2626
static {
27-
boolean sandboxed = System.getenv("APP_SANDBOX_CONTAINER_ID") != null;
27+
boolean packaged = System.getProperty("java.electron.packaged") != null;
2828

2929
try {
30-
if (sandboxed) {
30+
if (packaged) {
3131
nativeDir = new File(System.getProperty("java.library.path"));
3232
} else {
3333
nativeDir = new File("src/main/resources/native");
@@ -38,8 +38,8 @@ private static OperatingSystem determineOS() {
3838
System.setProperty("jna.library.path", nativeDir.getAbsolutePath());
3939
System.setProperty("java.library.path", nativeDir.getAbsolutePath());
4040

41-
if (sandboxed) {
42-
logger.info("Run in sandbox.");
41+
if (packaged) {
42+
logger.info("Run in a packaged environment.");
4343
System.setProperty("jna.nounpack", "true");
4444
System.setProperty("jna.noclasspath", "true");
4545
System.setProperty("jna.boot.library.path", nativeDir.getAbsolutePath());
0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)