PeaceFounder / AppBundler.jl

Bundle your Julia application
MIT License
53 stars 3 forks source link

Bundling App on Windows Fails #14

Open Arryk opened 1 month ago

Arryk commented 1 month ago

Hi,

I am trying to build a very simple application to test the bundler on Windows, but the application returns an error.

I have a very simple main.jl that contains

println("main.jl has been called!")

My Project.toml file

name = "TestBundle"
authors = ["AH"]
version = "0.0.1"

[bundle]
APP_NAME = "TestBundle"
APP_DESCRIPTION = "An app to TestBundle"
APP_DISPLAY_NAME = "TestBundle"
APP_SUMMARY = "An app to rTestBundle"
BUNDLE_IDENTIFIER = "none"
PUBLISHER = "CN=AH"
PUBLISHER_DISPLAY_NAME = "AH"
WITH_SPLASH_SCREEN = true

After that I run in the REPL AppBundler.bundle_app(Windows(:x86_64), "TestBundle", "build/TestBundle.zip")

And I get a lots of errors

image

Every single filename has an "issue" with invalid characters.

I am running Julia 1.10.4 on Windows 11 Pro 22H2 (build 22621.1413)

Any idea how to get this to work ?

JanisErdmanis commented 1 month ago

Hi,

I have actually not tested running AppBundler on Windows, so issues are expected. The issue appears to be in checking that the path does not contain invalid characters, which include \, but I should have seen that in MacOS as well.

For the time being, you can comment out ensure_windows_compatability in bundle_app for the Windows platform, which is simply a safety check. I will try to fix that someday next week when I plan to do some Windows stuff.

Arryk commented 1 month ago

Hi,

Thanks, after commenting the line out, it works fine now.

I had a look at the function and the problem lies with the RegEx, it is normally used to validate file / folder names and you are inputting a file path or folder path. A path in windows will includes \ characters and : characters in a specific pattern and the RegEx to validate those is much more complicated.

image

If the files you are checking exists on disk, then using the function ispath will yield the same result and will take care of all the corner cases for you. If you need to check path before creating the files, then you'll probably needs to update the RegEx to something like this:

^(?<ParentPath>(?:[a-zA-Z]\:|\\\\[\w\s\.]+\\[\w\s\.$]+)\\(?:[\w\s\.]+\\)*)(?<BaseName>[\w\s\.]*?)$

Taken from: https://regexr.com/52is4

Arryk commented 1 month ago

For your reference, I ran some test on the installation and everything works fine until .msix package installation.

App installation failed with error message: error 0x8007007E: Failed to load the extension DLL due to the following error: The specified module could not be found.
. (0x8007007e)

I get this error during installation on the package. Running the precompile script didn't create a compiled folder, maybe this is related

JanisErdmanis commented 1 month ago

I will have to check whether I can use ispath. The issue the regex solves is that on Linux and MacOS path variables can contain more characters like : and \ so it just ensures that the resulting bundles are functional made on Windows. I guess on Windows this check shouldn't be present as file written on disk already indicates that it is a valid path.

Regarding the installation, I can't remember seeing such error before. There is a powershell script launcher in the bundled directory, does it work for you?

Arryk commented 1 month ago

It doesn't really work either, the script doesn't return any errors but when I try to run the main.jl in the Julia session I get an error specifying precompiled folder doesn't exists

JanisErdmanis commented 1 month ago

I have no idea what the issue is, so I will only try to reproduce the issue on my Windows VM. I may try to allocate a few hours for the windows next week if I feel that I can change focus from the cryptography stuff I am doing now.

Arryk commented 1 month ago

If you have a simple test application to run on Windows, I can also try it on my setup and test for you, if this helps

JanisErdmanis commented 1 month ago

I just pushed a new version, v0.1.5, with which I managed to bundle examples/qmlapp on Windows. The build script that should be used for Windows is:

using AppBundler

import Pkg.BinaryPlatforms: Linux, MacOS, Windows

APP_DIR = dirname(@__DIR__)

BUILD_DIR = joinpath(APP_DIR, "build")
mkpath(BUILD_DIR)

AppBundler.bundle_app(Windows(:x86_64), APP_DIR, "$BUILD_DIR/qmlapp-win64")

where it is reasonable to omit zip archive creation.

Running the script will produce build/qmlapp-win64, where one needs to run the precompile.ps1 and then qmlapp.ps1. Let me know if you can launch the app that way.

Note that creating a bundle in deeply nested subdirectories would fail if the path exceeded the 260-character limit. Also, there is buggy behaviour when one uses the Parallels Desktop bridged folder, seemingly indicating that a network drive is affected the same way.

Arryk commented 1 month ago

Hi Janis,

Thanks for spending some time on this. I tried to replicate the same build but got some issues:

Julia bundle succeeded

julia> AppBundler.bundle_app(Windows(:x86_64), APP_DIR, "$BUILD_DIR/qmlapp-win64")
[ Info: Rule with origin windows/assets is skipped as not found in default or override path.
  Activating project at `C:\Users\user\AppData\Local\Temp\temp_env`
   Resolving package versions...
    Updating `C:\Users\user\AppData\Local\Temp\temp_env\Project.toml`
  [ca6e7d0a] + GLAbstraction v0.7.0
  [f7f18e0c] + GLFW v3.4.3
    Updating `C:\Users\user\AppData\Local\Temp\temp_env\Manifest.toml`
  [ca6e7d0a] + GLAbstraction v0.7.0
  [f7f18e0c] + GLFW v3.4.3
  [66fc600b] + ModernGL v1.1.7
  [aea7be01] + PrecompileTools v1.2.1
  [3cdcf5f2] + RecipesBase v1.3.4
  [90137ffa] + StaticArrays v1.9.7
  [1e83bf80] + StaticArraysCore v1.4.3
  [b189fb0b] + ThreadPools v2.1.1
  [83423d85] + Cairo_jll v1.18.0+2
  [ee1fde0b] + Dbus_jll v1.14.10+0
  [559328eb] + FriBidi_jll v1.0.14+0
  [0656b61e] + GLFW_jll v3.4.0+1
  [3b182d85] + Graphite2_jll v1.3.14+0
  [2e76f6c2] + HarfBuzz_jll v2.8.1+1
  [1d63c593] + LLVMOpenMP_jll v15.0.7+0
  [dd4b983a] + LZO_jll v2.10.2+0
  [36c8627f] + Pango_jll v1.52.2+0
  [30392449] + Pixman_jll v0.43.4+0
  [935fb764] + Xorg_libXcursor_jll v1.2.0+4
  [d091e8ba] + Xorg_libXfixes_jll v5.0.3+4
  [a51aa0fd] + Xorg_libXi_jll v1.7.10+4
  [d1454406] + Xorg_libXinerama_jll v1.1.4+4
  [1183f4f0] + libdecor_jll v0.2.2+0
  [b53b4c65] + libpng_jll v1.6.43+1
  Activating project at `scripts`
[ Info: include_dependency updated C:\scripts\qmlapp\build/qmlapp-win64/packages\CxxWrap\src\CxxWrap.jl
[ Info: include_dependency skipped C:\scripts\qmlapp\build/qmlapp-win64/packages\Requires\src\Requires.jl

However the precompilation failed

PS C:\scripts\qmlapp\build\qmlapp-win64> .\precompile.ps1
ERROR: LoadError: MethodError: no method matching include_dependency(::String; track_content::Bool)

Closest candidates are:
  include_dependency(::AbstractString) got unsupported keyword argument "track_content"
   @ Base loading.jl:1689

Stacktrace:
 [1] readmodule(so_path_cb::typeof(CxxWrap.StdLib.get_libcxxwrap_julia_stl_path), funcname::Symbol, m::Module, flags::UInt32)
   @ CxxWrap.CxxWrapCore C:\scripts\qmlapp\build\qmlapp-win64\packages\CxxWrap\src\CxxWrap.jl:816
 [2] wrapmodule(so_path_cb::Function, funcname::Symbol, m::Module, flags::UInt32)
   @ CxxWrap.CxxWrapCore C:\scripts\qmlapp\build\qmlapp-win64\packages\CxxWrap\src\CxxWrap.jl:825
 [3] top-level scope
   @ C:\scripts\qmlapp\build\qmlapp-win64\packages\CxxWrap\src\StdLib.jl:22
 [4] include(mod::Module, _path::String)
   @ Base .\Base.jl:495
 [5] include(x::String)
   @ CxxWrap C:\scripts\qmlapp\build\qmlapp-win64\packages\CxxWrap\src\CxxWrap.jl:1
 [6] top-level scope
   @ C:\scripts\qmlapp\build\qmlapp-win64\packages\CxxWrap\src\CxxWrap.jl:933
 [7] include
   @ .\Base.jl:495 [inlined]
 [8] include_package_for_output(pkg::Base.PkgId, input::String, depot_path::Vector{String}, dl_load_path::Vector{String}, load_path::Vector{String}, concrete_deps::Vector{Pair{Base.PkgId, UInt128}}, source::String)
   @ Base .\loading.jl:2222
 [9] top-level scope
   @ stdin:3
in expression starting at C:\scripts\qmlapp\build\qmlapp-win64\packages\CxxWrap\src\StdLib.jl:1
in expression starting at C:\scripts\qmlapp\build\qmlapp-win64\packages\CxxWrap\src\CxxWrap.jl:1
in expression starting at stdin:3
ERROR: LoadError: Failed to precompile CxxWrap [1f15a43c-97ca-5a2a-ae31-89f07a497df4] to "C:\\scripts\\qmlapp\\build\\qmlapp-win64\\compiled\\v1.10\\CxxWrap\\jl_FAC7.tmp".
Stacktrace:
  [1] error(s::String)
    @ Base .\error.jl:35
  [2] compilecache(pkg::Base.PkgId, path::String, internal_stderr::IO, internal_stdout::IO, keep_loaded_modules::Bool)
    @ Base .\loading.jl:2468
  [3] compilecache
    @ .\loading.jl:2340 [inlined]
  [4] (::Base.var"#968#969"{Base.PkgId})()
    @ Base .\loading.jl:1974
  [5] mkpidlock(f::Base.var"#968#969"{Base.PkgId}, at::String, pid::Int32; kwopts::@Kwargs{stale_age::Int64, wait::Bool})
    @ FileWatching.Pidfile C:\scripts\qmlapp\build\qmlapp-win64\julia\share\julia\stdlib\v1.10\FileWatching\src\pidfile.jl:93
  [6] #mkpidlock#6
    @ C:\scripts\qmlapp\build\qmlapp-win64\julia\share\julia\stdlib\v1.10\FileWatching\src\pidfile.jl:88 [inlined]
  [7] trymkpidlock(::Function, ::Vararg{Any}; kwargs::@Kwargs{stale_age::Int64})
    @ FileWatching.Pidfile C:\scripts\qmlapp\build\qmlapp-win64\julia\share\julia\stdlib\v1.10\FileWatching\src\pidfile.jl:111
  [8] #invokelatest#2
    @ .\essentials.jl:894 [inlined]
  [9] invokelatest
    @ .\essentials.jl:889 [inlined]
 [10] maybe_cachefile_lock(f::Base.var"#968#969"{Base.PkgId}, pkg::Base.PkgId, srcpath::String; stale_age::Int64)
    @ Base .\loading.jl:2983
 [11] maybe_cachefile_lock
    @ .\loading.jl:2980 [inlined]
 [12] _require(pkg::Base.PkgId, env::String)
    @ Base .\loading.jl:1970
 [13] __require_prelocked(uuidkey::Base.PkgId, env::String)
    @ Base .\loading.jl:1812
 [14] #invoke_in_world#3
    @ .\essentials.jl:926 [inlined]
 [15] invoke_in_world
    @ .\essentials.jl:923 [inlined]
 [16] _require_prelocked(uuidkey::Base.PkgId, env::String)
    @ Base .\loading.jl:1803
 [17] macro expansion
    @ .\loading.jl:1790 [inlined]
 [18] macro expansion
    @ .\lock.jl:267 [inlined]
 [19] __require(into::Module, mod::Symbol)
    @ Base .\loading.jl:1753
 [20] #invoke_in_world#3
    @ .\essentials.jl:926 [inlined]
 [21] invoke_in_world
    @ .\essentials.jl:923 [inlined]
 [22] require(into::Module, mod::Symbol)
    @ Base .\loading.jl:1746
 [23] include
    @ .\Base.jl:495 [inlined]
 [24] include_package_for_output(pkg::Base.PkgId, input::String, depot_path::Vector{String}, dl_load_path::Vector{String}, load_path::Vector{String}, concrete_deps::Vector{Pair{Base.PkgId, UInt128}}, source::String)
    @ Base .\loading.jl:2222
 [25] top-level scope
    @ stdin:3
in expression starting at C:\scripts\qmlapp\build\qmlapp-win64\packages\QML\src\QML.jl:1
in expression starting at stdin:3
ERROR: LoadError: Failed to precompile QML [2db162a6-7e43-52c3-8d84-290c1c42d82a] to "C:\\scripts\\qmlapp\\build\\qmlapp-win64\\compiled\\v1.10\\QML\\jl_8B9F.tmp".
Stacktrace:
  [1] error(s::String)
    @ Base .\error.jl:35
  [2] compilecache(pkg::Base.PkgId, path::String, internal_stderr::IO, internal_stdout::IO, keep_loaded_modules::Bool)
    @ Base .\loading.jl:2468
  [3] compilecache
    @ .\loading.jl:2340 [inlined]
  [4] (::Base.var"#968#969"{Base.PkgId})()
    @ Base .\loading.jl:1974
  [5] mkpidlock(f::Base.var"#968#969"{Base.PkgId}, at::String, pid::Int32; kwopts::@Kwargs{stale_age::Int64, wait::Bool})
    @ FileWatching.Pidfile C:\scripts\qmlapp\build\qmlapp-win64\julia\share\julia\stdlib\v1.10\FileWatching\src\pidfile.jl:93
  [6] #mkpidlock#6
    @ C:\scripts\qmlapp\build\qmlapp-win64\julia\share\julia\stdlib\v1.10\FileWatching\src\pidfile.jl:88 [inlined]
  [7] trymkpidlock(::Function, ::Vararg{Any}; kwargs::@Kwargs{stale_age::Int64})
    @ FileWatching.Pidfile C:\scripts\qmlapp\build\qmlapp-win64\julia\share\julia\stdlib\v1.10\FileWatching\src\pidfile.jl:111
  [8] #invokelatest#2
    @ .\essentials.jl:894 [inlined]
  [9] invokelatest
    @ .\essentials.jl:889 [inlined]
 [10] maybe_cachefile_lock(f::Base.var"#968#969"{Base.PkgId}, pkg::Base.PkgId, srcpath::String; stale_age::Int64)
    @ Base .\loading.jl:2983
 [11] maybe_cachefile_lock
    @ .\loading.jl:2980 [inlined]
 [12] _require(pkg::Base.PkgId, env::String)
    @ Base .\loading.jl:1970
 [13] __require_prelocked(uuidkey::Base.PkgId, env::String)
    @ Base .\loading.jl:1812
 [14] #invoke_in_world#3
    @ .\essentials.jl:926 [inlined]
 [15] invoke_in_world
    @ .\essentials.jl:923 [inlined]
 [16] _require_prelocked(uuidkey::Base.PkgId, env::String)
    @ Base .\loading.jl:1803
 [17] macro expansion
    @ .\loading.jl:1790 [inlined]
 [18] macro expansion
    @ .\lock.jl:267 [inlined]
 [19] __require(into::Module, mod::Symbol)
    @ Base .\loading.jl:1753
 [20] #invoke_in_world#3
    @ .\essentials.jl:926 [inlined]
 [21] invoke_in_world
    @ .\essentials.jl:923 [inlined]
 [22] require(into::Module, mod::Symbol)
    @ Base .\loading.jl:1746

Any idea why this is the case ?

Thanks!

JanisErdmanis commented 4 weeks ago

It seems that track content argument got introduced only with Julia 1.11 which is now in release candidate stage. You should use that as it also introduces cache relocability fixes.

Arryk commented 4 weeks ago

Thanks, I will try again on 1.11 when it releases and update the post then