reflex-packager 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/package.yml +310 -0
  3. data/.github/workflows/release-gem.yml +2 -49
  4. data/.github/workflows/tag.yml +11 -28
  5. data/.github/workflows/test.yml +16 -37
  6. data/.gitignore +2 -0
  7. data/ChangeLog.md +28 -0
  8. data/Gemfile.lock +26 -0
  9. data/README.md +232 -27
  10. data/Rakefile +21 -0
  11. data/VERSION +1 -1
  12. data/bin/reflex +2 -32
  13. data/examples/hello/icon.png +0 -0
  14. data/examples/hello/main.rb +18 -0
  15. data/examples/hello/reflex.yml +3 -0
  16. data/lib/reflex/packager/app_store_connect.rb +162 -0
  17. data/lib/reflex/packager/cli.rb +42 -9
  18. data/lib/reflex/packager/config.rb +233 -36
  19. data/lib/reflex/packager/data_compiler.rb +59 -0
  20. data/lib/reflex/packager/data_file.rb +183 -0
  21. data/lib/reflex/packager/data_loader.rb +127 -0
  22. data/lib/reflex/packager/gems.rb +282 -0
  23. data/lib/reflex/packager/library.rb +120 -0
  24. data/lib/reflex/packager/macos.rb +592 -94
  25. data/lib/reflex/packager/platform.rb +289 -10
  26. data/lib/reflex/packager/profile.rb +38 -26
  27. data/lib/reflex/packager/profiles/reflex.rb +43 -0
  28. data/lib/reflex/packager/rake.rb +115 -0
  29. data/lib/reflex/packager/stores/app_store.rb +60 -0
  30. data/lib/reflex/packager/stores/itchio.rb +53 -0
  31. data/lib/reflex/packager/templates/macos/AppStore.entitlements.erb +8 -0
  32. data/lib/reflex/packager/templates/macos/AppStoreExportOptions.plist.erb +14 -0
  33. data/lib/reflex/packager/templates/macos/ExportOptions.plist.erb +20 -0
  34. data/lib/reflex/packager/templates/macos/InfoPlist.strings.erb +5 -0
  35. data/lib/reflex/packager/templates/macos/boot.rb.erb +29 -0
  36. data/lib/reflex/packager/templates/macos/compile.rb.erb +7 -0
  37. data/lib/reflex/packager/templates/macos/main.mm.erb +10 -7
  38. data/lib/reflex/packager/templates/macos/project.yml.erb +60 -3
  39. data/lib/reflex/packager/templates/windows/app.manifest.erb +25 -0
  40. data/lib/reflex/packager/templates/windows/app.rc.erb +38 -0
  41. data/lib/reflex/packager/templates/windows/boot.rb.erb +32 -0
  42. data/lib/reflex/packager/templates/windows/main.cpp.erb +47 -0
  43. data/lib/reflex/packager/templates/windows/runtime.manifest.erb +7 -0
  44. data/lib/reflex/packager/windows.rb +370 -0
  45. data/lib/reflex/packager.rb +18 -1
  46. data/reflex-packager.gemspec +8 -7
  47. data/test/helper.rb +50 -5
  48. data/test/test_packager_app_store_connect.rb +108 -0
  49. data/test/test_packager_cli.rb +38 -3
  50. data/test/test_packager_config.rb +166 -27
  51. data/test/test_packager_data_file.rb +96 -0
  52. data/test/test_packager_data_loader.rb +114 -0
  53. data/test/test_packager_library.rb +81 -0
  54. data/test/test_packager_macos.rb +714 -86
  55. data/test/test_packager_platform.rb +16 -0
  56. data/test/test_packager_rake.rb +165 -0
  57. data/test/test_packager_stores_app_store.rb +66 -0
  58. data/test/test_packager_stores_itchio.rb +38 -0
  59. data/test/test_packager_windows.rb +685 -0
  60. metadata +72 -15
  61. data/.github/workflows/utils.rb +0 -127
  62. data/lib/reflex/packager/templates/macos/Podfile.erb +0 -30
@@ -0,0 +1,53 @@
1
+ require 'shellwords'
2
+
3
+
4
+ module Reflex
5
+
6
+
7
+ module Packager
8
+
9
+
10
+ module Stores
11
+
12
+
13
+ # itch.io, which the app is uploaded to by butler, the command line tool
14
+ # of it, logged in by `butler login`, or by the API key in
15
+ # BUTLER_API_KEY, as on CI.
16
+ #
17
+ class Itchio
18
+
19
+ def initialize(project)
20
+ @project = project
21
+ end
22
+
23
+ # The project as user/game.
24
+ #
25
+ attr_reader :project
26
+
27
+ # Uploads +path+, a zip or a directory, into the channel of the
28
+ # project, which tells the platform by its name, as macos or windows,
29
+ # telling the version to show.
30
+ #
31
+ def upload(path, channel:, version:)
32
+ raise Error, "nothing to upload: '#{path}'" unless File.exist? path
33
+
34
+ cmd = ['butler', 'push', path, "#{project}:#{channel}", '--userversion', version]
35
+ puts "==> #{cmd.shelljoin}"
36
+ case system(*cmd)
37
+ when nil
38
+ raise Error, 'butler not found, see https://itch.io/docs/butler/installing.html'
39
+ when false
40
+ raise Error, "failed to upload: #{cmd.shelljoin}"
41
+ end
42
+ end
43
+
44
+ end# Itchio
45
+
46
+
47
+ end# Stores
48
+
49
+
50
+ end# Packager
51
+
52
+
53
+ end# Reflex
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>com.apple.security.app-sandbox</key>
6
+ <true/>
7
+ </dict>
8
+ </plist>
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>method</key>
6
+ <string>app-store-connect</string>
7
+ <key>signingStyle</key>
8
+ <string>automatic</string>
9
+ <key>teamID</key>
10
+ <string><%= config.macos.codesign_team_id %></string>
11
+ <key>destination</key>
12
+ <string>export</string>
13
+ </dict>
14
+ </plist>
@@ -0,0 +1,20 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>method</key>
6
+ <string>developer-id</string>
7
+ <key>signingStyle</key>
8
+ <%- if cloud_signing? -%>
9
+ <string>automatic</string>
10
+ <%- else -%>
11
+ <string>manual</string>
12
+ <key>signingCertificate</key>
13
+ <string><%= codesign_identity.encode xml: :text %></string>
14
+ <%- end -%>
15
+ <key>teamID</key>
16
+ <string><%= config.macos.codesign_team_id %></string>
17
+ <key>destination</key>
18
+ <string><%= notarize.include?(:submit) ? 'upload' : 'export' %></string>
19
+ </dict>
20
+ </plist>
@@ -0,0 +1,5 @@
1
+ "CFBundleName" = <%= name.to_json %>;
2
+ "CFBundleDisplayName" = <%= name.to_json %>;
3
+ <%- if copyright -%>
4
+ "NSHumanReadableCopyright" = <%= copyright.to_json %>;
5
+ <%- end -%>
@@ -0,0 +1,29 @@
1
+ begin
2
+ Dir.chdir File.join(__dir__, 'app')
3
+ $LOAD_PATH.unshift Dir.pwd
4
+ <%- if pack? -%>
5
+ require 'reflex/packager/data_loader'
6
+ Reflex::Packager::DataLoader.setup Dir.pwd
7
+
8
+ <%- end -%>
9
+ load File.join(Dir.pwd, <%= start_script.dump %>)
10
+ rescue SystemExit, SignalException
11
+ raise
12
+ rescue Exception => e
13
+ if $stderr.tty?
14
+ $stderr.puts e.full_message
15
+ else
16
+ begin
17
+ require 'reflex'
18
+ dirs = Regexp.union [File.dirname(__FILE__), __dir__].map {"#{_1}/"}
19
+ message = e.full_message(highlight: false).lines
20
+ .reject {_1.match? %r{^\s+from (eval|#{Regexp.escape __FILE__}|(#{dirs})reflex\.bundle/Contents/Resources/lib/reflex/packager/data_loader\.rb):}}
21
+ .join
22
+ .gsub(dirs, '')
23
+ Reflex.alert message, title: <%= config.name.dump %>
24
+ rescue Exception => alert_error
25
+ $stderr.puts "failed to alert: #{alert_error.message} (#{alert_error.class})"
26
+ end
27
+ end
28
+ raise
29
+ end
@@ -0,0 +1,7 @@
1
+ begin
2
+ require <%= compiler.dump %>
3
+ Reflex::Packager::DataCompiler.write <%= app_dir.dump %>, <%= path.dump %>
4
+ rescue Exception => e
5
+ $stderr.puts e.full_message
6
+ exit 1
7
+ end
@@ -1,6 +1,7 @@
1
1
  // -*- mode: objc -*-
2
2
  #import <Cocoa/Cocoa.h>
3
3
  #import <CRuby.h>
4
+ #include <locale.h>
4
5
 
5
6
 
6
7
  extern "C"
@@ -14,20 +15,22 @@ extern "C"
14
15
  int
15
16
  main (int argc, const char* argv[])
16
17
  {
18
+ // for non-ASCII paths, before initializing CRuby
19
+ setlocale(LC_CTYPE, "UTF-8");
20
+
17
21
  @autoreleasepool
18
22
  {
19
- NSString* appDir = [NSBundle.mainBundle.resourcePath stringByAppendingPathComponent:@"app"];
20
- [NSFileManager.defaultManager changeCurrentDirectoryPath:appDir];
21
-
22
23
  <%- extensions.each do |ext| -%>
23
24
  [CRuby addExtension:@"<%= ext %>" init:^{Init_<%= ext %>();}];
24
25
  <%- end -%>
25
26
 
26
- for (NSString* lib in @[<%= libraries.map {"@\"#{_1}\""}.join ', ' %>])
27
+ for (NSString* lib in @[<%= lib_names.reverse.map {"@\"#{_1}\""}.join ', ' %>])
27
28
  [CRuby addLibrary:lib bundle:NSBundle.mainBundle];
28
29
 
29
- [CRuby evaluate:[NSString stringWithFormat:@"$LOAD_PATH.unshift '%@'", appDir]];
30
- [CRuby start:[appDir stringByAppendingPathComponent:@"<%= profile.boot_main || config.main %>"]];
30
+ NSString* boot = [NSBundle.mainBundle.resourcePath stringByAppendingPathComponent:@"boot.rb"];
31
+ return [CRuby start:boot rescue:^(CRBValue* exception) {
32
+ CRBValue* message = [exception call:@"full_message" rescue:^(CRBValue* e) {}];
33
+ NSLog(@"%@", (message ?: [exception call:@"class"]).toString);
34
+ }];
31
35
  }
32
- return 0;
33
36
  }
@@ -7,13 +7,28 @@ options:
7
7
  settings:
8
8
  base:
9
9
  PRODUCT_BUNDLE_IDENTIFIER: <%= config.bundle_id %>
10
- MARKETING_VERSION: "<%= config.version %>"
10
+ PRODUCT_NAME: <%= app_name.to_json %>
11
+ MARKETING_VERSION: <%= config.display_version.to_json %>
12
+ CURRENT_PROJECT_VERSION: <%= config.build_version.to_json %>
11
13
  ARCHS: <%= config.macos.archs.join ' ' %>
12
14
  ONLY_ACTIVE_ARCH: NO
13
- CODE_SIGN_IDENTITY: <%= config.macos.codesign_identity.inspect %>
15
+ <%- if signing_to_distribute? # Developer ID signs the app exported, not the archive -%>
16
+ CODE_SIGN_IDENTITY: "-"
17
+ <%- else -%>
18
+ CODE_SIGN_IDENTITY: <%= codesign_identity.inspect %>
19
+ <%- end -%>
14
20
  <%- if config.macos.codesign_team_id -%>
15
21
  DEVELOPMENT_TEAM: <%= config.macos.codesign_team_id %>
22
+ ENABLE_HARDENED_RUNTIME: YES
23
+ <%- end -%>
24
+ CLANG_CXX_LANGUAGE_STANDARD: c++20
25
+ CLANG_ENABLE_OBJC_ARC: NO
26
+ <%- unless verbose? -%>
27
+ GCC_WARN_INHIBIT_ALL_WARNINGS: YES
16
28
  <%- end -%>
29
+ HEADER_SEARCH_PATHS: <%= header_search_paths.to_json %>
30
+ SYSTEM_HEADER_SEARCH_PATHS: <%= system_header_search_paths.to_json %>
31
+ OTHER_LDFLAGS: [-lz, -lffi]
17
32
 
18
33
  targets:
19
34
  <%= target %>:
@@ -24,17 +39,59 @@ targets:
24
39
  - path: app
25
40
  type: folder
26
41
  buildPhase: resources
42
+ - path: boot.rb
43
+ buildPhase: resources
44
+ <%- Dir.glob('*.bundle', base: File.join(build_dir, 'Bundles')).sort.each do |bundle| -%>
45
+ - path: Bundles/<%= bundle %>
46
+ buildPhase: resources
47
+ <%- end -%>
27
48
  <%- if config.icon -%>
28
49
  - path: AppIcon.icns
29
50
  buildPhase: resources
51
+ <%- end -%>
52
+ - path: <%= File.join(cruby_dir, 'src').to_json %>
53
+ name: CRuby
54
+ <%- build_infos.each do |lib, info| -%>
55
+ <%- root = lib.root -%>
56
+ <%- flags = info[:defs].map {"-D#{_1}"}.join ' ' -%>
57
+ <%- unless info[:srcs].empty? -%>
58
+ - path: <%= root.to_json %>
59
+ name: <%= lib %>
60
+ includes: <%= info[:srcs].map {_1.delete_prefix "#{root}/"}.to_json %>
61
+ compilerFlags: <%= flags.to_json %>
62
+ <%- end -%>
63
+ <%- unless info[:vendor_srcs].empty? -%>
64
+ - path: <%= File.join(root, 'vendor').to_json %>
65
+ name: <%= lib %>-vendor
66
+ includes: <%= info[:vendor_srcs].map {_1.delete_prefix "#{root}/vendor/"}.to_json %>
67
+ compilerFlags: <%= "#{flags} -w".to_json %>
68
+ <%- end -%>
69
+ <%- end -%>
70
+ dependencies:
71
+ - framework: <%= File.join(cruby_dir, 'CRuby', 'CRuby.xcframework').to_json %>
72
+ embed: false
73
+ <%- frameworks.each do |framework| -%>
74
+ - sdk: <%= framework %>.framework
30
75
  <%- end -%>
31
76
  info:
32
77
  path: src/Info.plist
33
78
  properties:
34
79
  CFBundleName: <%= config.name.inspect %>
35
80
  CFBundleDisplayName: <%= config.name.inspect %>
36
- CFBundleShortVersionString: "<%= config.version %>"
81
+ CFBundleShortVersionString: <%= config.display_version.to_json %>
82
+ CFBundleVersion: <%= config.build_version.to_json %>
83
+ <%- if config.copyright -%>
84
+ NSHumanReadableCopyright: <%= config.copyright.to_json %>
85
+ <%- end -%>
37
86
  NSHighResolutionCapable: true
87
+ <%- if config.macos.category -%>
88
+ LSApplicationCategoryType: <%= config.macos.category %>
89
+ <%- end -%>
90
+ # no encryption but the system's, not to be asked by App Store Connect
91
+ ITSAppUsesNonExemptEncryption: false
92
+ <%- if config.localizations.size > 1 -%>
93
+ LSHasLocalizedDisplayName: true
94
+ <%- end -%>
38
95
  <%- if config.icon -%>
39
96
  CFBundleIconFile: AppIcon
40
97
  <%- end -%>
@@ -0,0 +1,25 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2
+ <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
3
+ <assemblyIdentity type="win32" name="<%= target %>" version="<%= product_version %>"/>
4
+
5
+ <!-- the dlls in lib/, a private assembly listed in lib/lib.manifest -->
6
+ <dependency>
7
+ <dependentAssembly>
8
+ <assemblyIdentity type="win32" name="<%= Windows::RUNTIME_DIR %>" version="<%= Windows::RUNTIME_VERSION %>"/>
9
+ </dependentAssembly>
10
+ </dependency>
11
+
12
+ <!-- Windows 10 and 11 -->
13
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
14
+ <application>
15
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
16
+ </application>
17
+ </compatibility>
18
+
19
+ <!-- paths and file names in UTF-8 whatever the system locale is -->
20
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">
21
+ <windowsSettings>
22
+ <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
23
+ </windowsSettings>
24
+ </application>
25
+ </assembly>
@@ -0,0 +1,38 @@
1
+ // application manifest (RT_MANIFEST)
2
+ 1 24 "app.manifest"
3
+ <%- if config.icon -%>
4
+
5
+ // icon of the executable, and of its windows through this id
6
+ 1 ICON "app.ico"
7
+ <%- end -%>
8
+
9
+ // shown in the properties of the executable, and as the name of the
10
+ // process in the task manager
11
+ 1 VERSIONINFO
12
+ FILEVERSION <%= file_version .tr '.', ',' %>
13
+ PRODUCTVERSION <%= product_version.tr '.', ',' %>
14
+ FILEOS 0x40004// VOS_NT_WINDOWS32
15
+ FILETYPE 0x1// VFT_APP
16
+ BEGIN
17
+ BLOCK "StringFileInfo"
18
+ BEGIN
19
+ <%- config.localizations.each do |lang, values| -%>
20
+ BLOCK "<%= "%04X04B0" % langid(lang) %>"// <%= lang %>, Unicode
21
+ BEGIN
22
+ VALUE "FileDescription", <%= rc_string values[:name] %>
23
+ VALUE "FileVersion", <%= rc_string config.build_version %>
24
+ VALUE "InternalName", <%= rc_string target %>
25
+ <%- if values[:copyright] -%>
26
+ VALUE "LegalCopyright", <%= rc_string values[:copyright] %>
27
+ <%- end -%>
28
+ VALUE "OriginalFilename", <%= rc_string "#{target}.exe" %>
29
+ VALUE "ProductName", <%= rc_string values[:name] %>
30
+ VALUE "ProductVersion", <%= rc_string config.display_version %>
31
+ END
32
+ <%- end -%>
33
+ END
34
+ BLOCK "VarFileInfo"
35
+ BEGIN
36
+ VALUE "Translation", <%= config.localizations.keys.map {"0x%x, 1200" % langid(_1)}.join ", " %>
37
+ END
38
+ END
@@ -0,0 +1,32 @@
1
+ %w[<%= load_dirs.join ' ' %>].reverse_each do |dir|
2
+ $LOAD_PATH.unshift File.join(__dir__, dir, 'lib')
3
+ end
4
+
5
+ Dir.chdir File.join(__dir__, 'app')
6
+ $LOAD_PATH.unshift Dir.pwd
7
+
8
+ begin
9
+ <%- if pack? -%>
10
+ require 'reflex/packager/data_loader'
11
+ Reflex::Packager::DataLoader.setup Dir.pwd
12
+
13
+ <%- end -%>
14
+ load File.join(Dir.pwd, <%= start_script.dump %>)
15
+ <%- unless config.windows.console? -%>
16
+ rescue SystemExit, SignalException
17
+ raise
18
+ rescue Exception => e
19
+ begin
20
+ require 'reflex'
21
+ dirs = Regexp.union [File.dirname(__FILE__), __dir__].map {"#{_1}/"}
22
+ message = e.full_message(highlight: false).lines
23
+ .reject {_1.match? %r{^\s+from (eval|#{Regexp.escape __FILE__}|(#{dirs})reflex/lib/reflex/packager/data_loader\.rb):}}
24
+ .join
25
+ .gsub(dirs, '')
26
+ Reflex.alert message, title: <%= config.name.dump %>
27
+ rescue Exception => alert_error
28
+ $stderr.puts "failed to alert: #{alert_error.message} (#{alert_error.class})"
29
+ end
30
+ raise
31
+ <%- end -%>
32
+ end
@@ -0,0 +1,47 @@
1
+ // -*- mode: c++ -*-
2
+ #include <ruby.h>
3
+ #include <windows.h>
4
+ #include <string>
5
+ #include <vector>
6
+
7
+
8
+ extern "C"
9
+ {
10
+ void ruby_init_ext (const char* name, void (*init)(void));
11
+ <%- extensions.each do |ext| -%>
12
+ void Init_<%= ext %> ();
13
+ <%- end -%>
14
+ }
15
+
16
+
17
+ static std::string
18
+ boot_script ()
19
+ {
20
+ std::wstring path(32768, L'\0');
21
+ path.resize(GetModuleFileNameW(NULL, &path[0], (DWORD) path.size()));
22
+ path.erase(path.find_last_of(L"\\/") + 1);
23
+ path += L"lib\\boot.rb";
24
+
25
+ int size = WideCharToMultiByte(CP_UTF8, 0, path.c_str(), -1, NULL, 0, NULL, NULL);
26
+ std::string utf8(size, '\0');
27
+ WideCharToMultiByte(CP_UTF8, 0, path.c_str(), -1, &utf8[0], size, NULL, NULL);
28
+ utf8.resize(size - 1);
29
+ return utf8;
30
+ }
31
+
32
+ int
33
+ main (int argc, char** argv)
34
+ {
35
+ ruby_sysinit(&argc, &argv);
36
+ RUBY_INIT_STACK;
37
+ ruby_init();
38
+
39
+ <%- extensions.each do |ext| -%>
40
+ ruby_init_ext("<%= ext %>.so", Init_<%= ext %>);
41
+ <%- end -%>
42
+
43
+ std::string boot = boot_script();
44
+ std::vector<char*> args = {argv[0], &boot[0]};
45
+ args.insert(args.end(), argv + 1, argv + argc);
46
+ return ruby_run_node(ruby_options((int) args.size(), args.data()));
47
+ }
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2
+ <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
3
+ <assemblyIdentity type="win32" name="<%= Windows::RUNTIME_DIR %>" version="<%= Windows::RUNTIME_VERSION %>"/>
4
+ <%- dlls.each do |dll| -%>
5
+ <file name="<%= dll %>"/>
6
+ <%- end -%>
7
+ </assembly>