mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 20:55:44 +00:00
Add WebView GUI to Windows installer (#5532)
* Add WebView GUI to Windows installer Adds a native GUI window to the Windows installer using the webview library which embeds Microsoft Edge WebView2. The installer now shows a modern HTML/CSS/JS interface with progress bar and status updates. Key changes: - Add llvm-mingw toolchain for macOS → Windows CGO cross-compilation - Add webview_go dependency for WebView2 integration - Create UIInterface abstraction with ConsoleUI and WebViewUI impls - Build tags select stub (non-Windows/non-CGO) vs real WebView impl - Genrule handles CGO cross-compilation within Bazel sandbox Build targets: - eagle_installer_windows_amd64: Pure Go console version (5.8 MB) - eagle_installer_windows_amd64_webview: CGO WebView GUI (13.7 MB) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix WebView genrule to use Bazel Go SDK The CI doesn't have Go in PATH, so the genrule needs to use Bazel's Go SDK explicitly. This change: - Exposes @go_default_sdk via use_repo in MODULE.bazel - Adds @go_default_sdk//:files as a srcs dependency to the genrule - Uses absolute paths for GO and GOROOT so they work after cd - Filters out Go SDK files when copying source files - Updates go.mod version to 1.23 to match the Bazel SDK Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -107,6 +107,7 @@ bazel_dep(name = "gazelle", version = "0.47.0", repo_name = "bazel_gazelle")
|
||||
|
||||
go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk")
|
||||
go_sdk.download(version = "1.23.3")
|
||||
use_repo(go_sdk, "go_default_sdk")
|
||||
|
||||
go_deps = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps")
|
||||
go_deps.from_file(go_mod = "//:go.mod")
|
||||
@@ -118,6 +119,7 @@ use_repo(
|
||||
"com_github_aws_aws_sdk_go_v2_service_s3",
|
||||
"com_github_golang_jwt_jwt_v5",
|
||||
"com_github_google_uuid",
|
||||
"com_github_webview_webview_go",
|
||||
"org_golang_google_grpc",
|
||||
"org_golang_google_protobuf",
|
||||
)
|
||||
@@ -336,6 +338,19 @@ http_file(
|
||||
executable = True,
|
||||
)
|
||||
|
||||
# LLVM MinGW toolchain for Windows cross-compilation from macOS
|
||||
# This provides a complete toolchain for building Windows executables including
|
||||
# the MinGW-w64 libraries needed for CGO cross-compilation
|
||||
LLVM_MINGW_VERSION = "20250305"
|
||||
|
||||
http_archive(
|
||||
name = "llvm_mingw",
|
||||
build_file = "@//external:BUILD.llvm_mingw",
|
||||
sha256 = "32c24fc62fc8b9f8a900bf2c730b78b36767688f816f9d21e97a168289ff44e0",
|
||||
strip_prefix = "llvm-mingw-%s-ucrt-macos-14.4.1-universal" % LLVM_MINGW_VERSION,
|
||||
urls = ["https://github.com/fathonix/llvm-mingw-arm64ec-macos/releases/download/%s/llvm-mingw-%s-ucrt-macos-14.4.1-universal.tar.xz" % (LLVM_MINGW_VERSION, LLVM_MINGW_VERSION)],
|
||||
)
|
||||
|
||||
#
|
||||
# Toolchain Registration
|
||||
#
|
||||
|
||||
Generated
+561
@@ -611,6 +611,445 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@aspect_rules_js~//npm:extensions.bzl%pnpm": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "T22SdPzhLxF1CM+j9RD/Rq03yJ0NDfH2eE6hAbUcOII=",
|
||||
"usagesDigest": "6rWte4KDbiluq1s7w98bc4+2NjA8w67DKHDj4+DNw/Y=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"pnpm": {
|
||||
"bzlFile": "@@aspect_rules_js~//npm/private:npm_import.bzl",
|
||||
"ruleClassName": "npm_import_rule",
|
||||
"attributes": {
|
||||
"package": "pnpm",
|
||||
"version": "8.6.7",
|
||||
"root_package": "",
|
||||
"link_workspace": "",
|
||||
"link_packages": {},
|
||||
"integrity": "sha512-vRIWpD/L4phf9Bk2o/O2TDR8fFoJnpYrp2TKqTIZF/qZ2/rgL3qKXzHofHgbXsinwMoSEigz28sqk3pQ+yMEQQ==",
|
||||
"url": "",
|
||||
"commit": "",
|
||||
"patch_args": [
|
||||
"-p0"
|
||||
],
|
||||
"patches": [],
|
||||
"custom_postinstall": "",
|
||||
"npm_auth": "",
|
||||
"npm_auth_basic": "",
|
||||
"npm_auth_username": "",
|
||||
"npm_auth_password": "",
|
||||
"lifecycle_hooks": [],
|
||||
"extra_build_content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_binary\")\njs_binary(name = \"pnpm\", data = glob([\"package/**\"]), entry_point = \"package/dist/pnpm.cjs\", visibility = [\"//visibility:public\"])",
|
||||
"generate_bzl_library_targets": false,
|
||||
"extract_full_archive": true,
|
||||
"exclude_package_contents": [],
|
||||
"system_tar": "auto"
|
||||
}
|
||||
},
|
||||
"pnpm__links": {
|
||||
"bzlFile": "@@aspect_rules_js~//npm/private:npm_import.bzl",
|
||||
"ruleClassName": "npm_import_links",
|
||||
"attributes": {
|
||||
"package": "pnpm",
|
||||
"version": "8.6.7",
|
||||
"dev": false,
|
||||
"root_package": "",
|
||||
"link_packages": {},
|
||||
"deps": {},
|
||||
"transitive_closure": {},
|
||||
"lifecycle_build_target": false,
|
||||
"lifecycle_hooks_env": [],
|
||||
"lifecycle_hooks_execution_requirements": [
|
||||
"no-sandbox"
|
||||
],
|
||||
"lifecycle_hooks_use_default_shell_env": false,
|
||||
"bins": {},
|
||||
"package_visibility": [
|
||||
"//visibility:public"
|
||||
],
|
||||
"replace_package": "",
|
||||
"exclude_package_contents": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"aspect_bazel_lib~",
|
||||
"bazel_lib",
|
||||
"bazel_lib~"
|
||||
],
|
||||
[
|
||||
"aspect_bazel_lib~",
|
||||
"bazel_skylib",
|
||||
"bazel_skylib~"
|
||||
],
|
||||
[
|
||||
"aspect_bazel_lib~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
],
|
||||
[
|
||||
"aspect_bazel_lib~",
|
||||
"tar.bzl",
|
||||
"tar.bzl~"
|
||||
],
|
||||
[
|
||||
"aspect_rules_js~",
|
||||
"aspect_bazel_lib",
|
||||
"aspect_bazel_lib~"
|
||||
],
|
||||
[
|
||||
"aspect_rules_js~",
|
||||
"aspect_rules_js",
|
||||
"aspect_rules_js~"
|
||||
],
|
||||
[
|
||||
"aspect_rules_js~",
|
||||
"bazel_features",
|
||||
"bazel_features~"
|
||||
],
|
||||
[
|
||||
"aspect_rules_js~",
|
||||
"bazel_skylib",
|
||||
"bazel_skylib~"
|
||||
],
|
||||
[
|
||||
"aspect_rules_js~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
],
|
||||
[
|
||||
"bazel_features~",
|
||||
"bazel_features_globals",
|
||||
"bazel_features~~version_extension~bazel_features_globals"
|
||||
],
|
||||
[
|
||||
"bazel_features~",
|
||||
"bazel_features_version",
|
||||
"bazel_features~~version_extension~bazel_features_version"
|
||||
],
|
||||
[
|
||||
"bazel_lib~",
|
||||
"bazel_skylib",
|
||||
"bazel_skylib~"
|
||||
],
|
||||
[
|
||||
"bazel_lib~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
],
|
||||
[
|
||||
"tar.bzl~",
|
||||
"aspect_bazel_lib",
|
||||
"aspect_bazel_lib~"
|
||||
],
|
||||
[
|
||||
"tar.bzl~",
|
||||
"bazel_skylib",
|
||||
"bazel_skylib~"
|
||||
],
|
||||
[
|
||||
"tar.bzl~",
|
||||
"tar.bzl",
|
||||
"tar.bzl~"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@aspect_rules_ts~//ts:extensions.bzl%ext": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "h1hftyFCdJgiHD9blfFcMAiEk5ltEoUdNkuHPIkg/hM=",
|
||||
"usagesDigest": "v0aTa4/gasWF2bvssXYr1bqcYWc3kjV48hcj0z2QVT0=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"npm_typescript": {
|
||||
"bzlFile": "@@aspect_rules_ts~//ts/private:npm_repositories.bzl",
|
||||
"ruleClassName": "http_archive_version",
|
||||
"attributes": {
|
||||
"bzlmod": true,
|
||||
"version": "5.8.3",
|
||||
"integrity": "",
|
||||
"build_file": "@@aspect_rules_ts~//ts:BUILD.typescript",
|
||||
"build_file_substitutions": {
|
||||
"bazel_worker_version": "5.4.2",
|
||||
"google_protobuf_version": "3.20.1"
|
||||
},
|
||||
"urls": [
|
||||
"https://registry.npmjs.org/typescript/-/typescript-{}.tgz"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"aspect_rules_ts~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@cel-spec~//:extensions.bzl%non_module_dependencies": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "/2uyuQa5purSharolRXYOGYMGSGjDeByo6JfQDWsraA=",
|
||||
"usagesDigest": "2f6juplOpWu+UdD1kVgi773xavnFQ+OcH0PRuQduDxY=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"com_google_googleapis": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"sha256": "bd8e735d881fb829751ecb1a77038dda4a8d274c45490cb9fcf004583ee10571",
|
||||
"strip_prefix": "googleapis-07c27163ac591955d736f3057b1619ece66f5b99",
|
||||
"urls": [
|
||||
"https://github.com/googleapis/googleapis/archive/07c27163ac591955d736f3057b1619ece66f5b99.tar.gz"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"cel-spec~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@cel-spec~//:googleapis_ext.bzl%googleapis_ext": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "yun2jmsomFi3bs5bjQWXApBzqQf66zBJ39JEBYigzdc=",
|
||||
"usagesDigest": "OK8FsLndSl2AbwGM1Npe5NdHR1kDAebcw7Ee+KkekE0=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"com_google_googleapis_imports": {
|
||||
"bzlFile": "@@cel-spec~~non_module_dependencies~com_google_googleapis//:repository_rules.bzl",
|
||||
"ruleClassName": "switched_rules",
|
||||
"attributes": {
|
||||
"rules": {
|
||||
"proto_library_with_info": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"moved_proto_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"java_proto_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"java_grpc_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"java_gapic_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"java_gapic_test": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"java_gapic_assembly_gradle_pkg": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"py_proto_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"py_grpc_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"py_gapic_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"py_test": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"py_gapic_assembly_pkg": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"py_import": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"go_proto_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"go_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"go_test": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"go_gapic_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"go_gapic_assembly_pkg": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"cc_proto_library": [
|
||||
"native.cc_proto_library",
|
||||
""
|
||||
],
|
||||
"cc_grpc_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"cc_gapic_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"php_proto_library": [
|
||||
"",
|
||||
"php_proto_library"
|
||||
],
|
||||
"php_grpc_library": [
|
||||
"",
|
||||
"php_grpc_library"
|
||||
],
|
||||
"php_gapic_library": [
|
||||
"",
|
||||
"php_gapic_library"
|
||||
],
|
||||
"php_gapic_assembly_pkg": [
|
||||
"",
|
||||
"php_gapic_assembly_pkg"
|
||||
],
|
||||
"nodejs_gapic_library": [
|
||||
"",
|
||||
"typescript_gapic_library"
|
||||
],
|
||||
"nodejs_gapic_assembly_pkg": [
|
||||
"",
|
||||
"typescript_gapic_assembly_pkg"
|
||||
],
|
||||
"ruby_proto_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"ruby_grpc_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"ruby_ads_gapic_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"ruby_cloud_gapic_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"ruby_gapic_assembly_pkg": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"csharp_proto_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"csharp_grpc_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"csharp_gapic_library": [
|
||||
"",
|
||||
""
|
||||
],
|
||||
"csharp_gapic_assembly_pkg": [
|
||||
"",
|
||||
""
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"cel-spec~",
|
||||
"com_google_googleapis",
|
||||
"cel-spec~~non_module_dependencies~com_google_googleapis"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@envoy_api~//bazel:repositories.bzl%non_module_deps": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "6TqmRfVELxZJRPQYuJpC4JBX4QvdrHqTDOUJGOGODSo=",
|
||||
"usagesDigest": "IivjlawPvqhHPUJ3c6dLiPsH22mn/g/dJtlmv3zdimM=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"prometheus_metrics_model": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/prometheus/client_model/archive/v0.6.1.tar.gz"
|
||||
],
|
||||
"sha256": "b9b690bc35d80061f255faa7df7621eae39fe157179ccd78ff6409c3b004f05e",
|
||||
"strip_prefix": "client_model-0.6.1",
|
||||
"build_file_content": "\nload(\"@envoy_api//bazel:api_build_system.bzl\", \"api_cc_py_proto_library\")\nload(\"@io_bazel_rules_go//proto:def.bzl\", \"go_proto_library\")\n\napi_cc_py_proto_library(\n name = \"client_model\",\n srcs = [\n \"io/prometheus/client/metrics.proto\",\n ],\n visibility = [\"//visibility:public\"],\n)\n\ngo_proto_library(\n name = \"client_model_go_proto\",\n importpath = \"github.com/prometheus/client_model/go\",\n proto = \":client_model\",\n visibility = [\"//visibility:public\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"com_github_bufbuild_buf": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/bufbuild/buf/releases/download/v1.49.0/buf-Linux-x86_64.tar.gz"
|
||||
],
|
||||
"sha256": "ee8da9748249f7946d79191e36469ce7bc3b8ba80019bff1fa4289a44cbc23bf",
|
||||
"strip_prefix": "buf",
|
||||
"build_file_content": "\npackage(\n default_visibility = [\"//visibility:public\"],\n)\n\nfilegroup(\n name = \"buf\",\n srcs = [\n \"@com_github_bufbuild_buf//:bin/buf\",\n ],\n tags = [\"manual\"], # buf is downloaded as a linux binary; tagged manual to prevent build for non-linux users\n)\n"
|
||||
}
|
||||
},
|
||||
"envoy_toolshed": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/envoyproxy/toolshed/archive/bazel-v0.2.2.tar.gz"
|
||||
],
|
||||
"sha256": "443fe177aba0cef8c17b7a48905c925c67b09005b10dd70ff12cd9f729a72d51",
|
||||
"strip_prefix": "toolshed-bazel-v0.2.2/bazel"
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"envoy_api~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
],
|
||||
[
|
||||
"envoy_api~",
|
||||
"envoy_api",
|
||||
"envoy_api~"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@googleapis~//:extensions.bzl%switched_rules": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "vG6fuTzXD8MMvHWZEQud0MMH7eoC4GXY0va7VrFFh04=",
|
||||
@@ -779,6 +1218,37 @@
|
||||
"recordedRepoMappingEntries": []
|
||||
}
|
||||
},
|
||||
"@@pybind11_bazel~//:internal_configure.bzl%internal_configure_extension": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "CyAKLVVonohnkTSqg9II/HA7M49sOlnMkgMHL3CmDuc=",
|
||||
"usagesDigest": "mFrTHX5eCiNU/OIIGVHH3cOILY9Zmjqk8RQYv8o6Thk=",
|
||||
"recordedFileInputs": {
|
||||
"@@pybind11_bazel~//MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34"
|
||||
},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"pybind11": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file": "@@pybind11_bazel~//:pybind11-BUILD.bazel",
|
||||
"strip_prefix": "pybind11-2.12.0",
|
||||
"urls": [
|
||||
"https://github.com/pybind/pybind11/archive/v2.12.0.zip"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"pybind11_bazel~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_foreign_cc~//foreign_cc:extensions.bzl%tools": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "a7qnESofmIRYId6wwGNPJ9kvExU80KrkxL281P3+lBE=",
|
||||
@@ -1119,6 +1589,97 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_fuzzing~//fuzzing/private:extensions.bzl%non_module_dependencies": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "hVgJRQ3Er45/UUAgNn1Yp2Khcp/Y8WyafA2kXIYmQ5M=",
|
||||
"usagesDigest": "YnIrdgwnf3iCLfChsltBdZ7yOJh706lpa2vww/i2pDI=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"platforms": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz",
|
||||
"https://github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz"
|
||||
],
|
||||
"sha256": "8150406605389ececb6da07cbcb509d5637a3ab9a24bc69b1101531367d89d74"
|
||||
}
|
||||
},
|
||||
"rules_python": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"sha256": "d70cd72a7a4880f0000a6346253414825c19cdd40a28289bdf67b8e6480edff8",
|
||||
"strip_prefix": "rules_python-0.28.0",
|
||||
"url": "https://github.com/bazelbuild/rules_python/releases/download/0.28.0/rules_python-0.28.0.tar.gz"
|
||||
}
|
||||
},
|
||||
"bazel_skylib": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"sha256": "cd55a062e763b9349921f0f5db8c3933288dc8ba4f76dd9416aac68acee3cb94",
|
||||
"urls": [
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz",
|
||||
"https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz"
|
||||
]
|
||||
}
|
||||
},
|
||||
"com_google_absl": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/abseil/abseil-cpp/archive/refs/tags/20240116.1.zip"
|
||||
],
|
||||
"strip_prefix": "abseil-cpp-20240116.1",
|
||||
"integrity": "sha256-7capMWOvWyoYbUaHF/b+I2U6XLMaHmky8KugWvfXYuk="
|
||||
}
|
||||
},
|
||||
"rules_fuzzing_oss_fuzz": {
|
||||
"bzlFile": "@@rules_fuzzing~//fuzzing/private/oss_fuzz:repository.bzl",
|
||||
"ruleClassName": "oss_fuzz_repository",
|
||||
"attributes": {}
|
||||
},
|
||||
"honggfuzz": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file": "@@rules_fuzzing~//:honggfuzz.BUILD",
|
||||
"sha256": "6b18ba13bc1f36b7b950c72d80f19ea67fbadc0ac0bb297ec89ad91f2eaa423e",
|
||||
"url": "https://github.com/google/honggfuzz/archive/2.5.zip",
|
||||
"strip_prefix": "honggfuzz-2.5"
|
||||
}
|
||||
},
|
||||
"rules_fuzzing_jazzer": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_jar",
|
||||
"attributes": {
|
||||
"sha256": "ee6feb569d88962d59cb59e8a31eb9d007c82683f3ebc64955fd5b96f277eec2",
|
||||
"url": "https://repo1.maven.org/maven2/com/code-intelligence/jazzer/0.20.1/jazzer-0.20.1.jar"
|
||||
}
|
||||
},
|
||||
"rules_fuzzing_jazzer_api": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_jar",
|
||||
"attributes": {
|
||||
"sha256": "f5a60242bc408f7fa20fccf10d6c5c5ea1fcb3c6f44642fec5af88373ae7aa1b",
|
||||
"url": "https://repo1.maven.org/maven2/com/code-intelligence/jazzer-api/0.20.1/jazzer-api-0.20.1.jar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"rules_fuzzing~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_java~//java:rules_java_deps.bzl%compatibility_proxy": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "KIX40nDfygEWbU+rq3nYpt3tVgTK/iO8PKh5VMBlN7M=",
|
||||
|
||||
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
# LLVM MinGW toolchain for Windows cross-compilation
|
||||
# Provides x86_64-w64-mingw32 target compiler and libraries
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
filegroup(
|
||||
name = "all_files",
|
||||
srcs = glob(["**/*"]),
|
||||
)
|
||||
|
||||
# Compiler binaries
|
||||
filegroup(
|
||||
name = "compiler_files",
|
||||
srcs = glob([
|
||||
"bin/x86_64-w64-mingw32-*",
|
||||
"bin/clang*",
|
||||
"bin/llvm-*",
|
||||
"bin/lld*",
|
||||
]),
|
||||
)
|
||||
|
||||
# Windows x86_64 sysroot (headers and libraries)
|
||||
filegroup(
|
||||
name = "windows_x86_64_sysroot",
|
||||
srcs = glob([
|
||||
"x86_64-w64-mingw32/**/*",
|
||||
"generic-w64-mingw32/include/**/*",
|
||||
]),
|
||||
)
|
||||
|
||||
# All library files needed for linking
|
||||
filegroup(
|
||||
name = "linker_files",
|
||||
srcs = glob([
|
||||
"bin/x86_64-w64-mingw32-*",
|
||||
"bin/lld*",
|
||||
"bin/ld.lld*",
|
||||
"lib/**/*",
|
||||
"x86_64-w64-mingw32/lib/**/*",
|
||||
]),
|
||||
)
|
||||
|
||||
# The main C compiler wrapper script path for CGO
|
||||
# CGO needs CC to point to the cross-compiler
|
||||
exports_files([
|
||||
"bin/x86_64-w64-mingw32-clang",
|
||||
"bin/x86_64-w64-mingw32-clang++",
|
||||
])
|
||||
@@ -11,6 +11,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.72.2
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6
|
||||
google.golang.org/grpc v1.68.0
|
||||
google.golang.org/protobuf v1.36.3
|
||||
)
|
||||
|
||||
@@ -41,6 +41,8 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 h1:VQpB2SpK88C6B5lPHTuSZKb2Qee1QWwiFlC5CKY4AW0=
|
||||
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6/go.mod h1:yE65LFCeWf4kyWD5re+h4XNvOHJEXOCOuJZ4v8l5sgk=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||
|
||||
# Pure Go library (uses stub for WebView)
|
||||
go_library(
|
||||
name = "installer_lib",
|
||||
srcs = [
|
||||
"config.go",
|
||||
"main.go",
|
||||
"ui.go",
|
||||
"ui_interface.go",
|
||||
"ui_webview_stub.go",
|
||||
"ui_webview_windows.go",
|
||||
"updater.go",
|
||||
],
|
||||
importpath = "github.com/nolen777/eagle0/src/main/go/net/eagle0/clients/win/installer",
|
||||
visibility = ["//visibility:private"],
|
||||
deps = select({
|
||||
"@io_bazel_rules_go//go/platform:windows": [
|
||||
"@com_github_webview_webview_go//:webview_go",
|
||||
],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
)
|
||||
|
||||
# Windows x86_64 installer binary
|
||||
# Windows x86_64 installer binary (pure Go, console-only)
|
||||
# Build with: bazel build //src/main/go/net/eagle0/clients/win/installer:eagle_installer_windows_amd64
|
||||
# The manifest public key is injected at build time via x_defs
|
||||
# The resource_windows.syso file embeds the application icon and version info
|
||||
go_binary(
|
||||
name = "eagle_installer_windows_amd64",
|
||||
srcs = ["resource_windows.syso"],
|
||||
@@ -30,7 +38,143 @@ go_binary(
|
||||
},
|
||||
)
|
||||
|
||||
# Native binary for testing on current platform
|
||||
# Windows x86_64 installer with WebView GUI
|
||||
# Build with: bazel build //src/main/go/net/eagle0/clients/win/installer:eagle_installer_windows_amd64_webview
|
||||
# This uses CGO cross-compilation with llvm-mingw toolchain
|
||||
genrule(
|
||||
name = "eagle_installer_windows_amd64_webview",
|
||||
srcs = [
|
||||
"config.go",
|
||||
"main.go",
|
||||
"ui.go",
|
||||
"ui_interface.go",
|
||||
"ui_webview_windows.go",
|
||||
"updater.go",
|
||||
"resource_windows.syso",
|
||||
"@llvm_mingw//:all_files",
|
||||
"@go_default_sdk//:files",
|
||||
],
|
||||
outs = ["EagleInstallerWebView.exe"],
|
||||
cmd = """
|
||||
set -e
|
||||
|
||||
# Find llvm-mingw root
|
||||
LLVM_MINGW=""
|
||||
for f in $(locations @llvm_mingw//:all_files); do
|
||||
if [[ "$$f" == *"/bin/x86_64-w64-mingw32-clang" ]]; then
|
||||
LLVM_MINGW=$$(cd $$(dirname $$(dirname $$f)) && pwd)
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$$LLVM_MINGW" ]]; then
|
||||
echo "ERROR: Could not find llvm-mingw toolchain"
|
||||
exit 1
|
||||
fi
|
||||
echo "Using llvm-mingw at: $$LLVM_MINGW"
|
||||
|
||||
# Find Go SDK from Bazel dependency - use absolute paths
|
||||
GO=""
|
||||
GO_SDK=""
|
||||
SANDBOX_ROOT="$$PWD"
|
||||
for f in $(locations @go_default_sdk//:files); do
|
||||
if [[ "$$f" == */bin/go && ! "$$f" == *.* ]]; then
|
||||
GO="$$SANDBOX_ROOT/$$f"
|
||||
GO_SDK=$$(cd $$(dirname $$(dirname "$$SANDBOX_ROOT/$$f")) && pwd)
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$$GO" ]]; then
|
||||
echo "ERROR: Could not find Go binary in @go_default_sdk//:files"
|
||||
echo "Available files:"
|
||||
for f in $(locations @go_default_sdk//:files); do
|
||||
echo " $$f"
|
||||
done | head -20
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export GOROOT="$$GO_SDK"
|
||||
echo "Using Go SDK at: $$GO_SDK"
|
||||
$$GO version
|
||||
|
||||
# Set up cross-compilation environment
|
||||
export CGO_ENABLED=1
|
||||
export GOOS=windows
|
||||
export GOARCH=amd64
|
||||
export CC="$$LLVM_MINGW/bin/x86_64-w64-mingw32-clang"
|
||||
export CXX="$$LLVM_MINGW/bin/x86_64-w64-mingw32-clang++"
|
||||
export AR="$$LLVM_MINGW/bin/x86_64-w64-mingw32-ar"
|
||||
|
||||
# Verify cross-compiler works
|
||||
echo "Testing cross-compiler..."
|
||||
$$CC --version | head -1
|
||||
|
||||
# Create temp build directory
|
||||
BUILD_DIR=$$(mktemp -d)
|
||||
trap "chmod -R u+w $$BUILD_DIR 2>/dev/null; rm -rf $$BUILD_DIR" EXIT
|
||||
|
||||
# Set up Go environment variables for sandbox
|
||||
export HOME=$$BUILD_DIR
|
||||
export GOPATH=$$BUILD_DIR/go
|
||||
export GOCACHE=$$BUILD_DIR/cache
|
||||
mkdir -p $$GOPATH $$GOCACHE
|
||||
|
||||
# Create Go module structure
|
||||
mkdir -p $$BUILD_DIR/installer
|
||||
cd $$BUILD_DIR/installer
|
||||
|
||||
# Initialize go module
|
||||
cat > go.mod << 'GOMOD'
|
||||
module github.com/nolen777/eagle0/src/main/go/net/eagle0/clients/win/installer
|
||||
|
||||
go 1.23
|
||||
|
||||
require github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6
|
||||
GOMOD
|
||||
|
||||
cat > go.sum << 'GOSUM'
|
||||
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 h1:VQpB2SpK88C6B5lPHTuSZKb2Qee1QWwiFlC5CKY4AW0=
|
||||
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6/go.mod h1:yE65LFCeWf4kyWD5re+h4XNvOHJEXOCOuJZ4v8l5sgk=
|
||||
GOSUM
|
||||
|
||||
# Copy source files from Bazel sandbox (exclude Go SDK and llvm files)
|
||||
echo "Copying source files from $$SANDBOX_ROOT..."
|
||||
for src in $(SRCS); do
|
||||
# Skip Go SDK and toolchain files
|
||||
if [[ "$$src" == *"go_default_sdk"* || "$$src" == *"llvm_mingw"* || "$$src" == *"rules_go"* ]]; then
|
||||
continue
|
||||
fi
|
||||
case "$$src" in
|
||||
*.go)
|
||||
echo " Copying: $$src"
|
||||
cp "$$SANDBOX_ROOT/$$src" . || { echo "Failed to copy $$src"; exit 1; }
|
||||
;;
|
||||
# Skip .syso for now - the existing one is 32-bit and causes link errors
|
||||
# TODO: Generate 64-bit .syso resource file
|
||||
esac
|
||||
done
|
||||
echo "Files copied:"
|
||||
ls -la *.go 2>/dev/null || echo "No files copied!"
|
||||
|
||||
# Download webview dependency
|
||||
$$GO mod download
|
||||
|
||||
# Build with GUI subsystem flag
|
||||
echo "Building Windows installer with WebView..."
|
||||
OUTPUT_EXE="$$SANDBOX_ROOT/$@"
|
||||
$$GO build -v \\
|
||||
-ldflags="-H windowsgui -X main.manifestPublicKey={MANIFEST_PUBLIC_KEY}" \\
|
||||
-o "$$OUTPUT_EXE" \\
|
||||
.
|
||||
|
||||
echo "Build complete!"
|
||||
ls -la "$$OUTPUT_EXE"
|
||||
""",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
# Native binary for testing on current platform (console only)
|
||||
go_binary(
|
||||
name = "eagle_installer",
|
||||
embed = [":installer_lib"],
|
||||
|
||||
@@ -11,10 +11,12 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
fileLocation string
|
||||
exeLocation string
|
||||
invitationFile string
|
||||
ui *UI
|
||||
fileLocation string
|
||||
exeLocation string
|
||||
invitationFile string
|
||||
ui UIInterface
|
||||
invitationCode string
|
||||
replaceInstaller string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -32,7 +34,7 @@ func main() {
|
||||
helpFlag := flag.Bool("help", false, "Show help message")
|
||||
hFlag := flag.Bool("h", false, "Show help message")
|
||||
codeFlag := flag.String("code", "", "Invitation code for new account registration")
|
||||
replaceInstaller := flag.String("replace-installer", "", "Path to old installer to replace (internal use)")
|
||||
replaceFlag := flag.String("replace-installer", "", "Path to old installer to replace (internal use)")
|
||||
flag.Parse()
|
||||
|
||||
if *helpFlag || *hFlag {
|
||||
@@ -40,21 +42,41 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize UI
|
||||
ui = NewUI()
|
||||
// Store flags for use in runInstallerWithUI
|
||||
invitationCode = *codeFlag
|
||||
replaceInstaller = *replaceFlag
|
||||
|
||||
// Initialize UI - try WebView first, fall back to console
|
||||
if webviewUI := NewWebViewUI(); webviewUI != nil {
|
||||
ui = webviewUI
|
||||
// Run installer logic in a goroutine since WebView needs the main thread
|
||||
go runInstallerWithUI()
|
||||
// WebView event loop (blocks until window closes)
|
||||
ui.Run()
|
||||
return
|
||||
}
|
||||
|
||||
// Fall back to console UI
|
||||
ui = NewConsoleUI()
|
||||
runInstallerWithUI()
|
||||
}
|
||||
|
||||
// runInstallerWithUI runs the installer logic with the initialized UI
|
||||
func runInstallerWithUI() {
|
||||
defer ui.Close()
|
||||
|
||||
// Clean up temp installer files from previous updates
|
||||
cleanupTempInstallerFiles()
|
||||
|
||||
// Handle installer replacement if launched with --replace-installer flag
|
||||
if *replaceInstaller != "" {
|
||||
handleInstallerReplacement(*replaceInstaller)
|
||||
if replaceInstaller != "" {
|
||||
handleInstallerReplacement(replaceInstaller)
|
||||
return
|
||||
}
|
||||
|
||||
// Run the main installer logic
|
||||
ui.ShowBanner()
|
||||
if err := runInstallerLogic(*codeFlag); err != nil {
|
||||
if err := runInstallerLogic(invitationCode); err != nil {
|
||||
ui.ShowError(fmt.Sprintf("%v", err))
|
||||
ui.WaitForKeypress("Press Enter to exit...")
|
||||
os.Exit(1)
|
||||
|
||||
@@ -6,21 +6,25 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// UI provides a simple console-based progress display
|
||||
type UI struct {
|
||||
// ConsoleUI provides a simple console-based progress display.
|
||||
// It implements UIInterface and serves as the fallback UI when WebView is not available.
|
||||
type ConsoleUI struct {
|
||||
lastStatus string
|
||||
lastProgress int
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
func NewUI() *UI {
|
||||
return &UI{
|
||||
// Ensure ConsoleUI implements UIInterface
|
||||
var _ UIInterface = (*ConsoleUI)(nil)
|
||||
|
||||
func NewConsoleUI() *ConsoleUI {
|
||||
return &ConsoleUI{
|
||||
startTime: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// ShowBanner displays the application header
|
||||
func (ui *UI) ShowBanner() {
|
||||
func (ui *ConsoleUI) ShowBanner() {
|
||||
fmt.Println()
|
||||
fmt.Println("╔══════════════════════════════════════════════════════════╗")
|
||||
fmt.Println("║ Eagle0 Installer ║")
|
||||
@@ -29,7 +33,7 @@ func (ui *UI) ShowBanner() {
|
||||
}
|
||||
|
||||
// UpdateStatus shows the current status message
|
||||
func (ui *UI) UpdateStatus(status string) {
|
||||
func (ui *ConsoleUI) UpdateStatus(status string) {
|
||||
if status != ui.lastStatus {
|
||||
ui.lastStatus = status
|
||||
fmt.Printf("► %s\n", status)
|
||||
@@ -37,7 +41,7 @@ func (ui *UI) UpdateStatus(status string) {
|
||||
}
|
||||
|
||||
// UpdateProgress shows a progress bar (0-100)
|
||||
func (ui *UI) UpdateProgress(percent int, description string) {
|
||||
func (ui *ConsoleUI) UpdateProgress(percent int, description string) {
|
||||
if percent < 0 {
|
||||
percent = 0
|
||||
}
|
||||
@@ -64,33 +68,43 @@ func (ui *UI) UpdateProgress(percent int, description string) {
|
||||
}
|
||||
|
||||
// ShowSuccess displays a success message
|
||||
func (ui *UI) ShowSuccess(message string) {
|
||||
func (ui *ConsoleUI) ShowSuccess(message string) {
|
||||
fmt.Printf("\n✓ %s\n", message)
|
||||
}
|
||||
|
||||
// ShowError displays an error message
|
||||
func (ui *UI) ShowError(message string) {
|
||||
func (ui *ConsoleUI) ShowError(message string) {
|
||||
fmt.Printf("\n✗ ERROR: %s\n", message)
|
||||
}
|
||||
|
||||
// ShowWarning displays a warning message
|
||||
func (ui *UI) ShowWarning(message string) {
|
||||
func (ui *ConsoleUI) ShowWarning(message string) {
|
||||
fmt.Printf("\n⚠ WARNING: %s\n", message)
|
||||
}
|
||||
|
||||
// ShowInfo displays an info message
|
||||
func (ui *UI) ShowInfo(message string) {
|
||||
func (ui *ConsoleUI) ShowInfo(message string) {
|
||||
fmt.Printf(" %s\n", message)
|
||||
}
|
||||
|
||||
// WaitForKeypress waits for user input before continuing
|
||||
func (ui *UI) WaitForKeypress(prompt string) {
|
||||
func (ui *ConsoleUI) WaitForKeypress(prompt string) {
|
||||
fmt.Printf("\n%s", prompt)
|
||||
fmt.Scanln()
|
||||
}
|
||||
|
||||
// ShowElapsedTime shows how long the operation took
|
||||
func (ui *UI) ShowElapsedTime() {
|
||||
func (ui *ConsoleUI) ShowElapsedTime() {
|
||||
elapsed := time.Since(ui.startTime)
|
||||
fmt.Printf("\nCompleted in %.1f seconds\n", elapsed.Seconds())
|
||||
}
|
||||
|
||||
// Run is a no-op for console UI (no event loop needed)
|
||||
func (ui *ConsoleUI) Run() {
|
||||
// Console UI doesn't need an event loop
|
||||
}
|
||||
|
||||
// Close is a no-op for console UI (no cleanup needed)
|
||||
func (ui *ConsoleUI) Close() {
|
||||
// Console UI doesn't need cleanup
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
// UIInterface defines the interface for installer UI implementations.
|
||||
// This allows switching between console UI (fallback) and WebView UI (Windows with WebView2).
|
||||
type UIInterface interface {
|
||||
// ShowBanner displays the application header/title
|
||||
ShowBanner()
|
||||
|
||||
// UpdateStatus shows the current status message
|
||||
UpdateStatus(status string)
|
||||
|
||||
// UpdateProgress shows a progress bar (0-100) with description
|
||||
UpdateProgress(percent int, description string)
|
||||
|
||||
// ShowSuccess displays a success message
|
||||
ShowSuccess(message string)
|
||||
|
||||
// ShowError displays an error message
|
||||
ShowError(message string)
|
||||
|
||||
// ShowWarning displays a warning message
|
||||
ShowWarning(message string)
|
||||
|
||||
// ShowInfo displays an info message
|
||||
ShowInfo(message string)
|
||||
|
||||
// WaitForKeypress waits for user input before continuing
|
||||
WaitForKeypress(prompt string)
|
||||
|
||||
// ShowElapsedTime shows how long the operation took
|
||||
ShowElapsedTime()
|
||||
|
||||
// Run starts the UI event loop (blocking for WebView, no-op for console)
|
||||
Run()
|
||||
|
||||
// Close cleans up UI resources
|
||||
Close()
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//go:build !cgo || !windows
|
||||
|
||||
package main
|
||||
|
||||
// WebViewUI stub for pure Go builds (no CGO).
|
||||
// This stub allows the code to compile without CGO/WebView dependencies.
|
||||
// NewWebViewUI returns nil, causing main.go to fall back to ConsoleUI.
|
||||
type WebViewUI struct{}
|
||||
|
||||
// Ensure WebViewUI implements UIInterface (for compile-time checking)
|
||||
var _ UIInterface = (*WebViewUI)(nil)
|
||||
|
||||
// NewWebViewUI returns nil in pure Go builds (no WebView available).
|
||||
func NewWebViewUI() *WebViewUI {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The following methods implement UIInterface but will never be called
|
||||
// since NewWebViewUI returns nil and main.go checks for nil.
|
||||
|
||||
func (ui *WebViewUI) ShowBanner() {}
|
||||
func (ui *WebViewUI) UpdateStatus(status string) {}
|
||||
func (ui *WebViewUI) UpdateProgress(percent int, desc string) {}
|
||||
func (ui *WebViewUI) ShowSuccess(message string) {}
|
||||
func (ui *WebViewUI) ShowError(message string) {}
|
||||
func (ui *WebViewUI) ShowWarning(message string) {}
|
||||
func (ui *WebViewUI) ShowInfo(message string) {}
|
||||
func (ui *WebViewUI) WaitForKeypress(prompt string) {}
|
||||
func (ui *WebViewUI) ShowElapsedTime() {}
|
||||
func (ui *WebViewUI) Run() {}
|
||||
func (ui *WebViewUI) Close() {}
|
||||
@@ -0,0 +1,268 @@
|
||||
//go:build cgo && windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/webview/webview_go"
|
||||
)
|
||||
|
||||
// WebViewUI provides a WebView2-based GUI for the installer on Windows.
|
||||
// It implements UIInterface and provides a modern HTML/CSS/JS interface.
|
||||
type WebViewUI struct {
|
||||
w webview.WebView
|
||||
startTime time.Time
|
||||
ready chan struct{}
|
||||
mu sync.Mutex
|
||||
lastStatus string
|
||||
}
|
||||
|
||||
// Ensure WebViewUI implements UIInterface
|
||||
var _ UIInterface = (*WebViewUI)(nil)
|
||||
|
||||
const htmlTemplate = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
||||
color: #e8e8e8;
|
||||
height: 100vh;
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.logo {
|
||||
font-size: 42px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 4px;
|
||||
background: linear-gradient(90deg, #00d4ff, #7b2cbf);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.status {
|
||||
font-size: 16px;
|
||||
margin: 20px 0;
|
||||
min-height: 24px;
|
||||
color: #b8b8b8;
|
||||
}
|
||||
.progress-container {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
height: 28px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.progress-bar {
|
||||
background: linear-gradient(90deg, #00d4ff 0%, #7b2cbf 100%);
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
transition: width 0.3s ease-out;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.percentage {
|
||||
text-align: center;
|
||||
margin-top: 12px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #00d4ff;
|
||||
}
|
||||
.message {
|
||||
margin-top: 20px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
display: none;
|
||||
}
|
||||
.message.success {
|
||||
display: block;
|
||||
background: rgba(0, 255, 136, 0.15);
|
||||
border-left: 4px solid #00ff88;
|
||||
color: #00ff88;
|
||||
}
|
||||
.message.error {
|
||||
display: block;
|
||||
background: rgba(255, 71, 87, 0.15);
|
||||
border-left: 4px solid #ff4757;
|
||||
color: #ff4757;
|
||||
}
|
||||
.message.warning {
|
||||
display: block;
|
||||
background: rgba(255, 193, 7, 0.15);
|
||||
border-left: 4px solid #ffc107;
|
||||
color: #ffc107;
|
||||
}
|
||||
.message.info {
|
||||
display: block;
|
||||
background: rgba(0, 212, 255, 0.1);
|
||||
border-left: 4px solid #00d4ff;
|
||||
color: #a0d4e8;
|
||||
}
|
||||
.elapsed {
|
||||
margin-top: auto;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="logo">EAGLE0</div>
|
||||
<div class="status" id="status">Initializing...</div>
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" id="progress"></div>
|
||||
</div>
|
||||
<div class="percentage" id="percentage">0%</div>
|
||||
<div class="message" id="message"></div>
|
||||
<div class="elapsed" id="elapsed"></div>
|
||||
<script>
|
||||
function updateProgress(percent, description) {
|
||||
document.getElementById('progress').style.width = percent + '%';
|
||||
document.getElementById('percentage').textContent = percent + '%';
|
||||
if (description) {
|
||||
document.getElementById('status').textContent = description;
|
||||
}
|
||||
}
|
||||
function updateStatus(status) {
|
||||
document.getElementById('status').textContent = status;
|
||||
}
|
||||
function showMessage(type, text) {
|
||||
const msg = document.getElementById('message');
|
||||
msg.className = 'message ' + type;
|
||||
msg.textContent = text;
|
||||
}
|
||||
function showElapsed(seconds) {
|
||||
document.getElementById('elapsed').textContent = 'Completed in ' + seconds.toFixed(1) + ' seconds';
|
||||
}
|
||||
function clearMessage() {
|
||||
document.getElementById('message').className = 'message';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
// NewWebViewUI creates a new WebView-based UI.
|
||||
// Returns nil if WebView2 is not available.
|
||||
func NewWebViewUI() *WebViewUI {
|
||||
w := webview.New(false)
|
||||
if w == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
w.SetTitle("Eagle0 Installer")
|
||||
w.SetSize(520, 340, webview.HintFixed)
|
||||
w.SetHtml(htmlTemplate)
|
||||
|
||||
ui := &WebViewUI{
|
||||
w: w,
|
||||
startTime: time.Now(),
|
||||
ready: make(chan struct{}),
|
||||
}
|
||||
|
||||
return ui
|
||||
}
|
||||
|
||||
// ShowBanner is a no-op for WebView (logo is in HTML)
|
||||
func (ui *WebViewUI) ShowBanner() {
|
||||
// Logo is already in the HTML template
|
||||
}
|
||||
|
||||
// UpdateStatus shows the current status message
|
||||
func (ui *WebViewUI) UpdateStatus(status string) {
|
||||
ui.mu.Lock()
|
||||
defer ui.mu.Unlock()
|
||||
|
||||
if status == ui.lastStatus {
|
||||
return
|
||||
}
|
||||
ui.lastStatus = status
|
||||
|
||||
ui.w.Dispatch(func() {
|
||||
ui.w.Eval(fmt.Sprintf("updateStatus(%q)", escapeJS(status)))
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateProgress shows a progress bar (0-100)
|
||||
func (ui *WebViewUI) UpdateProgress(percent int, description string) {
|
||||
if percent < 0 {
|
||||
percent = 0
|
||||
}
|
||||
if percent > 100 {
|
||||
percent = 100
|
||||
}
|
||||
|
||||
ui.w.Dispatch(func() {
|
||||
ui.w.Eval(fmt.Sprintf("updateProgress(%d, %q)", percent, escapeJS(description)))
|
||||
})
|
||||
}
|
||||
|
||||
// ShowSuccess displays a success message
|
||||
func (ui *WebViewUI) ShowSuccess(message string) {
|
||||
ui.w.Dispatch(func() {
|
||||
ui.w.Eval(fmt.Sprintf("showMessage('success', %q)", escapeJS(message)))
|
||||
})
|
||||
}
|
||||
|
||||
// ShowError displays an error message
|
||||
func (ui *WebViewUI) ShowError(message string) {
|
||||
ui.w.Dispatch(func() {
|
||||
ui.w.Eval(fmt.Sprintf("showMessage('error', %q)", escapeJS(message)))
|
||||
})
|
||||
}
|
||||
|
||||
// ShowWarning displays a warning message
|
||||
func (ui *WebViewUI) ShowWarning(message string) {
|
||||
ui.w.Dispatch(func() {
|
||||
ui.w.Eval(fmt.Sprintf("showMessage('warning', %q)", escapeJS(message)))
|
||||
})
|
||||
}
|
||||
|
||||
// ShowInfo displays an info message
|
||||
func (ui *WebViewUI) ShowInfo(message string) {
|
||||
ui.w.Dispatch(func() {
|
||||
ui.w.Eval(fmt.Sprintf("showMessage('info', %q)", escapeJS(message)))
|
||||
})
|
||||
}
|
||||
|
||||
// WaitForKeypress waits for user acknowledgment
|
||||
// For WebView, we just pause briefly to let the user see the message
|
||||
func (ui *WebViewUI) WaitForKeypress(prompt string) {
|
||||
ui.UpdateStatus(prompt)
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
|
||||
// ShowElapsedTime shows how long the operation took
|
||||
func (ui *WebViewUI) ShowElapsedTime() {
|
||||
elapsed := time.Since(ui.startTime).Seconds()
|
||||
ui.w.Dispatch(func() {
|
||||
ui.w.Eval(fmt.Sprintf("showElapsed(%f)", elapsed))
|
||||
})
|
||||
}
|
||||
|
||||
// Run starts the WebView event loop (blocking)
|
||||
func (ui *WebViewUI) Run() {
|
||||
ui.w.Run()
|
||||
}
|
||||
|
||||
// Close cleans up WebView resources
|
||||
func (ui *WebViewUI) Close() {
|
||||
ui.w.Destroy()
|
||||
}
|
||||
|
||||
// escapeJS escapes a string for safe use in JavaScript
|
||||
func escapeJS(s string) string {
|
||||
// The %q format in Sprintf handles most escaping, but we use it for the whole string
|
||||
// So we just return the string as-is since Sprintf("%q", ...) will handle it
|
||||
return s
|
||||
}
|
||||
@@ -32,10 +32,10 @@ type InstallerUpdateInfo struct {
|
||||
type Updater struct {
|
||||
serverURL string
|
||||
httpClient *http.Client
|
||||
ui *UI
|
||||
ui UIInterface
|
||||
}
|
||||
|
||||
func NewUpdater(serverURL string, ui *UI) *Updater {
|
||||
func NewUpdater(serverURL string, ui UIInterface) *Updater {
|
||||
return &Updater{
|
||||
serverURL: serverURL,
|
||||
httpClient: &http.Client{
|
||||
|
||||
Reference in New Issue
Block a user