diff --git a/Manifest.toml b/Manifest.toml index 0423754..bf0f170 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -176,4 +176,4 @@ uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" deps = ["BinDeps", "Compat", "HTTPClient", "LibExpat", "Libdl", "Libz", "URIParser"] git-tree-sha1 = "2a889d320f3b77d17c037f295859fe570133cfbf" uuid = "c17dfb99-b4f7-5aad-8812-456da1ad7187" -version = "0.4.2" +version = "0.4.2" \ No newline at end of file diff --git a/src/ApplicationBuilder.jl b/src/ApplicationBuilder.jl index 73c3296..d3e1d05 100644 --- a/src/ApplicationBuilder.jl +++ b/src/ApplicationBuilder.jl @@ -9,6 +9,11 @@ export build_app_bundle include("mac_commandline_app.jl") end +@static if Sys.iswindows() + include("win_commandline_app.jl") + include("win-installer.jl") +end + """ build_app_bundle(juliaprog_main; appname, builddir, binary_name, resources, libraries, verbose, bundle_identifier, @@ -32,8 +37,9 @@ function build_app_bundle(juliaprog_main; resources = String[], libraries = String[], verbose = false, bundle_identifier = nothing, app_version = "0.1", icns_file = nothing, certificate = nothing, entitlements_file = nothing, - snoopfile = nothing, autosnoop = false, cpu_target="x86-64", + snoopfile = nothing, autosnoop = false, cpu_target=nothing, create_installer = false, commandline_app = false, + installer_compiler="iss" ) # ----------- Input sanity checking -------------- @@ -48,6 +54,10 @@ function build_app_bundle(juliaprog_main; if occursin(r"\s", bundle_identifier) throw(ArgumentError("Bundle identifier must not contain whitespace.")) end if occursin(r"[^A-Za-z0-9-.]", bundle_identifier) throw(ArgumentError("Bundle identifier must contain only alphanumeric characters (A-Z,a-z,0-9), hyphen (-), and period (.).")) end + elseif Sys.iswindows() + if commandline_app + @warn "Will create windows script" + end else if commandline_app @warn "Ignore `commandline_app=true` on non-macOS system." @@ -83,7 +93,7 @@ function build_app_bundle(juliaprog_main; end applet_name = nothing - if commandline_app # MacOS only + if commandline_app # MacOS and Windows only # TODO: What if the user specifies Resources that could overwrite # applet resources? (ie Scripts/ or applet.rsrc) applet_name = build_commandline_app_bundle(builddir, binary_name, appname, verbose) @@ -267,7 +277,7 @@ function build_app_bundle(juliaprog_main; end end elseif Sys.iswindows() - create_installer && win_installer(builddir, name = appname) + create_installer && win_installer(builddir, name=appname, installer_compiler=installer_compiler) end println("~~~~~~ Done building '$appbundle'! ~~~~~~~") diff --git a/src/win-installer.jl b/src/win-installer.jl index 1cbe11d..8bfbf6f 100644 --- a/src/win-installer.jl +++ b/src/win-installer.jl @@ -1,39 +1,152 @@ +JULIA_HOME = get(ENV, "JULIA_HOME", "") +LICENSE_PATH = joinpath(abspath(JULIA_HOME, ".."), "License.md") + +baremodule SetupCompilers + iss="iss" + nsis="nsis" +end + +function _hasfilesin(path::String)::Bool + for (root, dir, files) in walkdir(path) + if length(files) > 0 + return true + end + end + return false +end + function win_installer(builddir; name = "nothing", - license = "$JULIA_HOME/../License.md") + license = LICENSE_PATH, installer_compiler=SetupCompilers.iss) # check = success(`makensis`) # !check && throw(ErrorException("NSIS not found in path. Exiting.")) - nsis_commands = """ - # set the name of the installer - Outfile "$(name)_Installer.exe" + commands = if installer_compiler == SetupCompilers.nsis + """ + # set the name of the installer + Name "$(name)" + Outfile "$(name)_Installer.exe" + + # Default install directory + InstallDir "\$LOCALAPPDATA" - # Default install directory - InstallDir "\$LOCALAPPDATA" + Page license + Page directory + Page instfiles - Page license - Page directory - Page instfiles + LicenseData "$license" - LicenseData "$license" + # create a default section. + Section "Install" - # create a default section. - Section "Install" + SetOutPath "$(joinpath("\$INSTDIR", name))" + File /nonfatal /a /r "$builddir" - SetOutPath "$(joinpath("\$INSTDIR", name))" - File /nonfatal /a /r "$builddir" + CreateShortcut "$(joinpath("\$INSTDIR", name, "$name.lnk"))" "$(joinpath(builddir, "core", "blink.exe"))" - CreateShortcut "$(joinpath("\$INSTDIR", name, "$name.lnk"))" "$(joinpath(builddir, "core", "blink.exe"))" + SectionEnd + """ + elseif installer_compiler == SetupCompilers.iss + res_path = joinpath(builddir, name, "res") + lib_path = joinpath(builddir, name, "lib") + files = + """ + [Files] + Source: "$(joinpath(builddir, name, "bin") * "\\*")"; DestDir: "{app}\\bin"; Flags: ignoreversion + """ - SectionEnd - """ + if isdir(res_path) && _hasfilesin(res_path) + files = "$files\nSource: \"$(res_path * "\\*")\"; DestDir: \"{app}\\res\"; Flags: ignoreversion" + end + + if isdir(lib_path) && _hasfilesin(lib_path) + files = "$files\nSource: \"$(lib_path * "\\*")\"; DestDir: \"{app}\\lib\"; Flags: ignoreversion" + end + + """ + ; $name InnoSetup Compiler + ; This software is property of Gabriel Freire. All Rights reserved. + ; Copyright 2019 + ; Requires InnoSetup Latest (5.5 tested) + ; This script compiles the setup file for $name in the SETUP folder + + #define MyAppName "$name" + #define MyAppVersion "1.0" + #define ApplicationVersion GetStringFileInfo("$(joinpath(builddir, name, "bin", "$(name).exe"))", "FileVersion") + #define MyAppExeName "$name.exe" + + + [Setup] + AppId={{802D0907-22CE-4E43-8FAB-017F687159C4} + AppName={#MyAppName} + AppVersion={#ApplicationVersion} + AppVerName={#MyAppName} + VersionInfoVersion={#ApplicationVersion} + DefaultDirName={pf}\\{#MyAppName} + DisableDirPage=yes + DisableProgramGroupPage=yes + OutputDir=.\\ + OutputBaseFilename=$(name * "Setup") + UninstallDisplayIcon={app}\\{#MyAppExeName} + Compression=lzma + SolidCompression=yes + ; Tell Windows Explorer to reload the environment + ChangesEnvironment=yes + UsePreviousAppDir=False + + [CustomMessages] + AppAddPath=Add application directory to your environmental path (required) + + [Languages] + Name: "english"; MessagesFile: "compiler:Default.isl" + + [Tasks] + Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + Name: modifypath; Description:{cm:AppAddPath}; Flags: unchecked + + [Registry] + Root: HKCU; Subkey: "Environment"; ValueType:expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\\bin"; Flags: preservestringtype + + $(files) + + [Code] + + var CancelWithoutPrompt: boolean; + + function InitializeSetup(): Boolean; + begin + CancelWithoutPrompt := false; + result := true; + Log('{#ApplicationVersion}'); + end; + + procedure CancelButtonClick(CurPageID: Integer; var Cancel, Confirm: Boolean); + begin + if CurPageID=wpInstalling then + Confirm := not CancelWithoutPrompt; + end; + + [Icons] + Name: "{commonprograms}\\{#MyAppName}"; Filename: "{app}\\{#MyAppExeName}" + Name: "{commondesktop}\\{#MyAppName}"; Filename: "{app}\\{#MyAppExeName}"; Tasks: desktopicon + Name: "{commonstartup}\\{#MyAppName}"; Filename: "{app}\\{#MyAppExeName}"; + """ + else + throw(ArgumentError("Unknown compiler: $installer_compiler")) + end + + ext = if installer_compiler == SetupCompilers.nsis + "nsi" + elseif installer_compiler == SetupCompilers.iss + "iss" + end @info "Creating installer at $builddir" - nsis_file = joinpath(builddir, "..", "$name.nsi") - open(nsis_file, "w") do f - write(f, nsis_commands) + compiler_file = joinpath(abspath(builddir, ".."), "$name.$ext") + open(compiler_file, "w") do f + write(f, commands) end - run(`makensis $nsis_file`) + # run(`makensis $nsis_file`) @info "Created installer successfully." diff --git a/src/win_commandline_app.jl b/src/win_commandline_app.jl new file mode 100644 index 0000000..cb1e274 --- /dev/null +++ b/src/win_commandline_app.jl @@ -0,0 +1,30 @@ +function get_commandline_sh_script(appname) + """ + @echo off + set source_dir=%~dp0 + call "%source_dir%$(appname).exe" %* + """ +end + +# Build a wrapper app that opens a terminal and runs the provided binary. +# Returns a new script name only if binary_name is already "appname" (to prevent collision). +function build_commandline_app_bundle(builddir, binary_name, appname, verbose) + println("~~~~~~ Creating commandline-app wrapper script. ~~~~~~~") + + mkpath(builddir) # Create builddir if it doesn't already exist. + + app_path = joinpath(builddir, appname) + exe_dir = "bin" # Put the binaries next to the applet in MacOS. + script_name = "$(binary_name).bat" + if binary_name == appname # Prevent collisions. + script_name = "$(binary_name)_.bat" + end + script_path = joinpath(app_path, exe_dir, script_name) + mkpath(dirname(script_path)) + + verbose && println(" Creating wrapper script: $script_path") + write(script_path, get_commandline_sh_script(appname)) + run(`chmod u+x $script_path`) + + return script_name +end diff --git a/test/build_app-cli.jl b/test/build_app-cli.jl index 8169551..9576316 100644 --- a/test/build_app-cli.jl +++ b/test/build_app-cli.jl @@ -1,28 +1,48 @@ + using Test +# prevent shell_split on removing all slashes +path_to_cmd(path::String) = replace(path, "\\" => "\\\\") + julia = Base.julia_cmd().exec[1] -build_app_jl = joinpath(@__DIR__, "..", "build_app.jl") -examples_hello = joinpath(@__DIR__, "..", "examples", "hello.jl") +build_app_jl = joinpath(abspath(@__DIR__, ".."), "build_app.jl") +examples_hello = joinpath(abspath(@__DIR__, ".."), "examples", "hello.jl") builddir = mktempdir() @assert isdir(builddir) @testset "Basic file resource args" begin # Test the build_app.jl script CLI args. -res1 = @__FILE__ # haha copy this file itself as a "resource"! -res2 = joinpath(@__DIR__, "runtests.jl") # lol sure this is a resource, why not. -NEWARGS = Base.shell_split("""--verbose - -R $res1 --resource $res2 -L $res1 --lib $res2 - $examples_hello "HelloWorld" $builddir""") -eval(:(ARGS = $NEWARGS)) -@test 0 == include("$build_app_jl") -@test isdir("$builddir/HelloWorld.app") -@test isfile("$builddir/HelloWorld.app/Contents/MacOS/hello") - -# Make sure the specified resources and libs were copied: -@test isfile("$builddir/HelloWorld.app/Contents/Resources/$(basename(res1))") -@test isfile("$builddir/HelloWorld.app/Contents/Resources/$(basename(res2))") -@test isfile("$builddir/HelloWorld.app/Contents/Libraries/$(basename(res1))") + res1 = @__FILE__ # haha copy this file itself as a "resource"! + res2 = joinpath(@__DIR__, "runtests.jl") # lol sure this is a resource, why not. + + NEWARGS = Base.shell_split("""--verbose + -R $(path_to_cmd(res1)) --resource $(path_to_cmd(res2)) -L $(path_to_cmd(res1)) --lib $(path_to_cmd(res2)) + $(path_to_cmd(examples_hello)) "HelloWorld" $(path_to_cmd(builddir))""") + eval(:(ARGS = $NEWARGS)) + + @test 0 == include(build_app_jl) + + if Sys.isapple() + @test isdir("$builddir/HelloWorld.app") + @test isfile("$builddir/HelloWorld.app/Contents/MacOS/hello") + + # Make sure the specified resources and libs were copied: + @test isfile("$builddir/HelloWorld.app/Contents/Resources/$(basename(res1))") + @test isfile("$builddir/HelloWorld.app/Contents/Resources/$(basename(res2))") + @test isfile("$builddir/HelloWorld.app/Contents/Libraries/$(basename(res1))") + else + @test isdir("$builddir/HelloWorld") + @test isdir("$builddir/HelloWorld/bin") + @test isfile("$builddir/HelloWorld/bin/hello.exe") + @test isfile("$builddir/HelloWorld/bin/hello.dll") + @test isfile("$builddir/HelloWorld/bin/hello.a") + + # Make sure the specified resources and libs were copied: + @test isfile("$builddir/HelloWorld/res/$(basename(res1))") + @test isfile("$builddir/HelloWorld/res/$(basename(res2))") + @test isfile("$builddir/HelloWorld/lib/$(basename(res1))") + end end @testset "Exits without juliaprog_main" begin diff --git a/test/build_examples/blink.jl b/test/build_examples/blink.jl index c81f1a6..15f0a37 100644 --- a/test/build_examples/blink.jl +++ b/test/build_examples/blink.jl @@ -2,7 +2,7 @@ using ApplicationBuilder using Pkg -examples_blink = joinpath(@__DIR__, "..", "..", "examples", "blink.jl") +examples_blink = joinpath(abspath(@__DIR__, "..", ".."), "examples", "blink.jl") # Allow this file to be called either as a standalone file to build the above # example, or from runtests.jl using a provided builddir. diff --git a/test/build_examples/commandline_hello.jl b/test/build_examples/commandline_hello.jl index 1150a8c..62b5fbd 100644 --- a/test/build_examples/commandline_hello.jl +++ b/test/build_examples/commandline_hello.jl @@ -4,6 +4,6 @@ using ApplicationBuilder # example, or from runtests.jl using a provided builddir. @isdefined(builddir) || (builddir="builddir") -build_app_bundle(joinpath(@__DIR__,"..","..","examples","commandline_hello.jl"), +build_app_bundle(joinpath(abspath(@__DIR__,"..",".."),"examples","commandline_hello.jl"), appname="hello", binary_name="hello", commandline_app=true, builddir=builddir) diff --git a/test/build_examples/hello.jl b/test/build_examples/hello.jl index d104640..3eae4c1 100644 --- a/test/build_examples/hello.jl +++ b/test/build_examples/hello.jl @@ -1,6 +1,6 @@ using ApplicationBuilder -examples_hello = joinpath(@__DIR__, "..", "..", "examples", "hello.jl") +examples_hello = joinpath(abspath(@__DIR__, "..", ".."), "examples", "hello.jl") # Allow this file to be called either as a standalone file to build the above # example, or from runtests.jl using a provided builddir. diff --git a/test/build_examples/libui.jl b/test/build_examples/libui.jl index 1e1f165..7398fc3 100644 --- a/test/build_examples/libui.jl +++ b/test/build_examples/libui.jl @@ -12,7 +12,7 @@ libUIPkg = Pkg.dir("Libui") using Libui -ApplicationBuilder.build_app_bundle(joinpath(@__DIR__, "..", "..", "examples", "libui.jl"); +ApplicationBuilder.build_app_bundle(joinpath(abspath(@__DIR__, "..", ".."), "examples", "libui.jl"); verbose = true, resources = [], libraries = [ Libui.libui ], diff --git a/test/build_examples/sdl.jl b/test/build_examples/sdl.jl index 5cd12a4..d110cea 100644 --- a/test/build_examples/sdl.jl +++ b/test/build_examples/sdl.jl @@ -1,7 +1,7 @@ using ApplicationBuilder using Pkg -examples_blink = joinpath(@__DIR__, "..", "..", "examples", "sdl.jl") +examples_blink = joinpath(abspath(@__DIR__, "..", ".."), "examples", "sdl.jl") # Allow this file to be called either as a standalone file to build the above # example, or from runtests.jl using a globally-defined builddir. diff --git a/test/bundle.jl b/test/bundle.jl index 46c63f4..480f23d 100644 --- a/test/bundle.jl +++ b/test/bundle.jl @@ -10,6 +10,7 @@ builddir = mktempdir() @testset "HelloWorld.app" begin @test 0 == include("build_examples/commandline_hello.jl") @test isdir(joinpath(builddir, "hello")) - @test success(`$builddir/hello/bin/hello`) + p = joinpath(builddir,"hello", "bin", "hello") + @test success(`$p`) #@test success(`open $builddir/hello.app`) end diff --git a/test/runtests.jl b/test/runtests.jl index 5cf3b7e..27e29d3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,22 +1,25 @@ using Test - @testset "ApplicationBuilder.jl" begin -# TODO: Make the tests work on Windows and Linux!!! :'( -@static if Sys.isapple() + # TODO: Make the tests work on Windows and Linux!!! :'( + @static if Sys.isapple() -@testset "Test ApplicationBuilder (by compiling examples/*.jl)" begin - include("ApplicationBuilder.jl") -end -@testset "Command-line interface (compiling examples/*.jl)" begin - include("build_app-cli.jl") -end + @testset "Test ApplicationBuilder (by compiling examples/*.jl)" begin + include("ApplicationBuilder.jl") + end + @testset "Command-line interface (compiling examples/*.jl)" begin + include("build_app-cli.jl") + end -else # Windows and Linux + end + @static if Sys.iswindows() # Windows -@testset "bundle.jl" begin - include("bundle.jl") -end + @testset "bundle.jl" begin + include("bundle.jl") + end -end + @testset "Command-line interface (compiling examples/*.jl)" begin + include("build_app-cli.jl") + end + end end