syncmoss 0.4.0__tar.gz → 0.4.2__tar.gz
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.
- syncmoss-0.4.2/.gitattributes +3 -0
- syncmoss-0.4.2/.github/workflows/_MacOS.yml +548 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/COPYING.txt +7 -1
- syncmoss-0.4.2/NOTICE.txt +120 -0
- {syncmoss-0.4.0/syncmoss.egg-info → syncmoss-0.4.2}/PKG-INFO +39 -3
- {syncmoss-0.4.0 → syncmoss-0.4.2}/README.md +38 -2
- {syncmoss-0.4.0 → syncmoss-0.4.2}/bundle/MacOS.spec +8 -1
- syncmoss-0.4.2/bundle/SYNCmoss.png +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/bundle/Windows.spec +6 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/bundle/bundle.py +5 -0
- syncmoss-0.4.2/bundle/dmg_background.pdf +0 -0
- syncmoss-0.4.2/bundle/licenses/GPL-3.0.txt +674 -0
- syncmoss-0.4.2/bundle/licenses/LGPL-3.0.txt +165 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/bundle/macos_bundle.py +6 -0
- syncmoss-0.4.2/bundle/third_party_licenses.py +82 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Calibration.py +3 -3
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/alpha-Fe (metallic iron, bcc).mdl +1 -1
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library_window.py +1 -1
- syncmoss-0.4.2/syncmoss/bliss_channel.py +82 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/constants.py +5 -0
- syncmoss-0.4.2/syncmoss/error_reporter.py +431 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/fitting_io.py +55 -8
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/instrumental_io.py +27 -29
- syncmoss-0.4.2/syncmoss/license_window.py +201 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/main.py +17 -3
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/model_io.py +245 -88
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/models.py +41 -5
- syncmoss-0.4.2/syncmoss/parameters/Be.txt +1 -0
- syncmoss-0.4.2/syncmoss/parameters/Calibration.dat +513 -0
- syncmoss-0.4.2/syncmoss/parameters/INSth.txt +1 -0
- syncmoss-0.4.2/syncmoss/parameters/KB.txt +1 -0
- syncmoss-0.4.2/syncmoss/parameters/calibr.png +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/parameters/help.md +98 -4
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/parameters/models_description.md +14 -12
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/parameters_table.py +160 -12
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/results_table.py +60 -30
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/sms_theory.py +7 -2
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/spectrum_io.py +11 -3
- syncmoss-0.4.2/syncmoss/spectrum_parameters.py +402 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/spectrum_plotter.py +22 -6
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/supp_menu.py +280 -12
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/syncmoss_main.py +619 -132
- {syncmoss-0.4.0 → syncmoss-0.4.2/syncmoss.egg-info}/PKG-INFO +39 -3
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss.egg-info/SOURCES.txt +24 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss.egg-info/scm_file_list.json +24 -0
- syncmoss-0.4.2/tests/syncmoss_test.py +676 -0
- syncmoss-0.4.2/tests/test_bliss_channel.py +200 -0
- syncmoss-0.4.2/tests/test_chi2_spread.py +54 -0
- syncmoss-0.4.2/tests/test_contact_author.py +88 -0
- syncmoss-0.4.2/tests/test_error_reporter.py +254 -0
- syncmoss-0.4.2/tests/test_expression_fit_start.py +89 -0
- syncmoss-0.4.2/tests/test_impurity_presets.py +173 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_instrumental_metadata.py +4 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_instrumental_recentre.py +21 -5
- syncmoss-0.4.2/tests/test_interrupt.py +310 -0
- syncmoss-0.4.2/tests/test_library_dir.py +239 -0
- syncmoss-0.4.2/tests/test_license_window.py +131 -0
- syncmoss-0.4.2/tests/test_results_table_image.py +76 -0
- syncmoss-0.4.2/tests/test_save_result_model.py +386 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_search_guards.py +3 -9
- syncmoss-0.4.2/tests/test_spectrum_parameters.py +390 -0
- syncmoss-0.4.2/tests/test_table_marks.py +201 -0
- syncmoss-0.4.2/tests/test_take_result.py +148 -0
- syncmoss-0.4.0/.github/workflows/_MacOS.yml +0 -329
- syncmoss-0.4.0/NOTICE.txt +0 -50
- syncmoss-0.4.0/syncmoss/parameters/Be.txt +0 -1
- syncmoss-0.4.0/syncmoss/parameters/Calibration.dat +0 -513
- syncmoss-0.4.0/syncmoss/parameters/INSth.txt +0 -1
- syncmoss-0.4.0/syncmoss/parameters/KB.txt +0 -1
- syncmoss-0.4.0/syncmoss/parameters/calibr.png +0 -0
- syncmoss-0.4.0/tests/syncmoss_test.py +0 -359
- {syncmoss-0.4.0 → syncmoss-0.4.2}/.github/workflows/Tests.yml +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/.github/workflows/_PyPI.yml +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/.github/workflows/_TagRelease.yml +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/.github/workflows/_Tests.yml +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/.github/workflows/_UploadRelease.yml +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/.github/workflows/_Version.yml +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/.github/workflows/_Wheels.yml +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/.github/workflows/_WindowsExe.yml +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/.github/workflows/release.yml +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/.gitignore +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/.vscode/settings.json +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/LICENSE +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/Lib_test/test.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/docs/thick_models.md +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/docs/thick_vs_thin.md +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/pyproject.toml +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/requirements.txt +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/setup.cfg +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Hamiltonian_helper.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Aegirine.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Akaganeite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Almandine.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Antitaenite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Arsenopyrite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Bornite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Bridgmanite (Fe,Al-bearing) @100 GPa.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Bridgmanite (Fe,Al-bearing).mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Celadonite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Cementite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Chalcopyrite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Clinopyroxene (augite).mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Copiapite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Cubanite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Epidote.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Fayalite (Fe-olivine).mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Ferrihydrite (2-line).mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Ferropericlase.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Goethite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Green rust (fougerite, carbonate).mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Haegg carbide.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Hedenbergite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Hematite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Heterosite (FePO4).mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Ilmenite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Iron phosphide.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Isocubanite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Jarosite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Kamacite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Lepidocrocite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Maghemite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Magnetite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Marcasite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Melanterite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Orthopyroxene (enstatite-ferrosilite).mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Prussian blue (ferric ferrocyanide).mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Pyrite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Pyrrhotite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Rhomboclase.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Roemerite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Siderite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Szomolnokite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Taenite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Talnakhite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Tetrataenite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Triphylite (LiFePO4).mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Troilite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Vivianite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library/Voltaite.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/Library_io.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/icons/CheckBox.png +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/icons/CheckBox_.png +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/icons/CheckBox_L.png +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/icons/CheckBox_L2.png +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/icons/DU.png +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/icons/Switch.png +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/icons/UD.png +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/icons/icon_r.ico +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/instrumental_window.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/legacy.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/minimi_lib.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/models_description_window.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/models_positions.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/parameters/ABSorber3.txt +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/parameters/GCMS.txt +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/parameters/INS_APS.txt +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/parameters/INSexp.txt +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/parameters/INSint.txt +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/parameters/NFS.txt +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/support_math.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/test_graf.txt +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/test_param.txt +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/theme_dark.json +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss/theme_light.json +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss.egg-info/dependency_links.txt +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss.egg-info/entry_points.txt +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss.egg-info/requires.txt +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/syncmoss.egg-info/top_level.txt +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/conftest.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/data/Distr_corr.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/data/Distr_distr.mdl +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/data/alpha_fe_sms_000.mca +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/data/calibration_000_golden.json +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/data/generate_cal_fixtures.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/data/synthetic_alpha_fe_cms_linear.mca +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/golden_models.json +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_calibration.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_calibration_helpers.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_calibration_path.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_distr_composition.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_distr_expansion.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_distr_mdl.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_distr_targets.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_expression_validation.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_golden_models.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_gui_smoke.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_hamiltonian_texture.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_instrumental_method_choice.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_instrumental_window.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_integral_points.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_legacy.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_library_io.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_model_append.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_model_io.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_model_only_mode.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_nbaseline_results.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_parameter_links.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_polarization.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_recon_composition.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_recon_mdl.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_recon_results.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_reference_shift.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_sdw_grid.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_sms_theory.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_support_math.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_texture_s1.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_theory_search.py +0 -0
- {syncmoss-0.4.0 → syncmoss-0.4.2}/tests/test_thick_gui.py +0 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
name: _macOS (PyInstaller universal2 app)
|
|
2
|
+
|
|
3
|
+
# Modelled on crispy's release workflow (github.com/mretegan/crispy):
|
|
4
|
+
# 1. build the app natively on an Apple Silicon AND an Intel runner,
|
|
5
|
+
# 2. fuse the two bundles into one universal2 SYNCmoss.app,
|
|
6
|
+
# 3. sign it (Developer ID + hardened runtime), pack the drag-and-drop DMG
|
|
7
|
+
# (window laid out as crispy's create-dmg.sh does), notarize, staple.
|
|
8
|
+
#
|
|
9
|
+
# Signing and notarization run only when the Apple secrets are set (repository
|
|
10
|
+
# Settings -> Secrets and variables -> Actions; release.yml forwards them).
|
|
11
|
+
# Without them the job still ships a universal2 DMG, ad-hoc signed and not
|
|
12
|
+
# notarized, named SYNCmoss-macOS-universal2.dmg. A notarized one is named
|
|
13
|
+
# SYNCmoss-macOS-universal2-signed.dmg; _TagRelease.yml / _UploadRelease.yml
|
|
14
|
+
# tell the two apart by that name.
|
|
15
|
+
#
|
|
16
|
+
# APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64
|
|
17
|
+
# the "Developer ID Application" certificate WITH its private key,
|
|
18
|
+
# exported from Keychain Access as .p12, then: base64 -i cert.p12 | pbcopy
|
|
19
|
+
# APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_PASSWORD password of that .p12
|
|
20
|
+
# APPLE_ID Apple Account e-mail of the developer
|
|
21
|
+
# APPLE_TEAM_ID 10-character Team ID (developer.apple.com -> Membership)
|
|
22
|
+
# APPLE_APPLICATION_SPECIFIC_PASSWORD app-specific password (account.apple.com -> Sign-In and Security)
|
|
23
|
+
# APPLE_KEYCHAIN_PASSWORD optional: password of the throwaway CI keychain
|
|
24
|
+
# (a random one is used when unset)
|
|
25
|
+
|
|
26
|
+
on:
|
|
27
|
+
workflow_call:
|
|
28
|
+
secrets:
|
|
29
|
+
APPLE_ID:
|
|
30
|
+
required: false
|
|
31
|
+
APPLE_TEAM_ID:
|
|
32
|
+
required: false
|
|
33
|
+
APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_PASSWORD:
|
|
34
|
+
required: false
|
|
35
|
+
APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64:
|
|
36
|
+
required: false
|
|
37
|
+
APPLE_KEYCHAIN_PASSWORD:
|
|
38
|
+
required: false
|
|
39
|
+
APPLE_APPLICATION_SPECIFIC_PASSWORD:
|
|
40
|
+
required: false
|
|
41
|
+
|
|
42
|
+
jobs:
|
|
43
|
+
build:
|
|
44
|
+
name: Build macOS app (${{ matrix.arch }})
|
|
45
|
+
runs-on: ${{ matrix.runner }}
|
|
46
|
+
timeout-minutes: 60
|
|
47
|
+
strategy:
|
|
48
|
+
fail-fast: false
|
|
49
|
+
matrix:
|
|
50
|
+
include:
|
|
51
|
+
- runner: macos-15
|
|
52
|
+
arch: arm64
|
|
53
|
+
- runner: macos-15-intel
|
|
54
|
+
arch: x86_64
|
|
55
|
+
env:
|
|
56
|
+
MACOSX_DEPLOYMENT_TARGET: "11"
|
|
57
|
+
CFLAGS: "-mmacosx-version-min=11"
|
|
58
|
+
LDFLAGS: "-mmacosx-version-min=11"
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/checkout@v6
|
|
61
|
+
|
|
62
|
+
- uses: actions/setup-python@v6
|
|
63
|
+
with:
|
|
64
|
+
python-version: "3.13"
|
|
65
|
+
cache: "pip"
|
|
66
|
+
|
|
67
|
+
- name: Check the runner architecture
|
|
68
|
+
run: |
|
|
69
|
+
MACHINE="$(python -c 'import platform; print(platform.machine())')"
|
|
70
|
+
echo "Python runs as ${MACHINE}"
|
|
71
|
+
if [ "${MACHINE}" != "${{ matrix.arch }}" ]; then
|
|
72
|
+
echo "::error::expected a ${{ matrix.arch }} runner, got ${MACHINE}"
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
- name: Download wheel artifact
|
|
77
|
+
uses: actions/download-artifact@v6
|
|
78
|
+
with:
|
|
79
|
+
name: wheels
|
|
80
|
+
path: wheels
|
|
81
|
+
|
|
82
|
+
# numba 0.62.x (llvmlite 0.45.x) is the last release with macOS x86_64
|
|
83
|
+
# wheels. The pin applies to BOTH architectures: the universal2 app holds
|
|
84
|
+
# one copy of the pure-Python code, so the two builds must resolve the
|
|
85
|
+
# same versions (checked again before the merge). --only-binary stops pip
|
|
86
|
+
# from quietly building an sdist on one architecture only.
|
|
87
|
+
- name: Install dependencies
|
|
88
|
+
run: |
|
|
89
|
+
python -m pip install --upgrade pip setuptools wheel
|
|
90
|
+
python -m pip install --only-binary=:all: "numba==0.62.1" -r requirements.txt pyinstaller wheels/*.whl
|
|
91
|
+
python -m pip list --format=freeze
|
|
92
|
+
|
|
93
|
+
- name: Build the app
|
|
94
|
+
run: python bundle/macos_bundle.py --dist-dir bundle/MacOS/${{ matrix.arch }} --work-dir bundle/build/MacOS/${{ matrix.arch }}
|
|
95
|
+
|
|
96
|
+
# tar keeps the symlinks (Qt frameworks) and executable bits that
|
|
97
|
+
# upload-artifact would drop.
|
|
98
|
+
- name: Archive the app bundle
|
|
99
|
+
run: |
|
|
100
|
+
set -euo pipefail
|
|
101
|
+
DIST="bundle/MacOS/${{ matrix.arch }}"
|
|
102
|
+
APP_PATH=""
|
|
103
|
+
for candidate in "${DIST}/SYNCmoss.app" "${DIST}/SYNCmoss/SYNCmoss.app"; do
|
|
104
|
+
if [ -d "${candidate}" ]; then
|
|
105
|
+
APP_PATH="${candidate}"
|
|
106
|
+
break
|
|
107
|
+
fi
|
|
108
|
+
done
|
|
109
|
+
if [ -z "${APP_PATH}" ]; then
|
|
110
|
+
echo "::error::SYNCmoss.app not found in the ${{ matrix.arch }} build output"
|
|
111
|
+
find "${DIST}" -maxdepth 3 -print
|
|
112
|
+
exit 1
|
|
113
|
+
fi
|
|
114
|
+
echo "Executable architectures: $(lipo -archs "${APP_PATH}/Contents/MacOS/SYNCmoss")"
|
|
115
|
+
mkdir -p macos-artifact
|
|
116
|
+
tar -cpzf macos-artifact/SYNCmoss.app.tar.gz -C "$(dirname "${APP_PATH}")" SYNCmoss.app
|
|
117
|
+
python -m pip list --format=freeze > macos-artifact/pip-freeze.txt
|
|
118
|
+
echo "APP_PATH=${APP_PATH}" >> "${GITHUB_ENV}"
|
|
119
|
+
|
|
120
|
+
- name: Upload the app bundle
|
|
121
|
+
uses: actions/upload-artifact@v5
|
|
122
|
+
with:
|
|
123
|
+
name: macos-app-${{ matrix.arch }}
|
|
124
|
+
path: macos-artifact/
|
|
125
|
+
if-no-files-found: error
|
|
126
|
+
retention-days: 1
|
|
127
|
+
|
|
128
|
+
# Runs each architecture natively (the x86_64 slice is never exercised
|
|
129
|
+
# later). After archiving, so nothing the test writes reaches the artifact.
|
|
130
|
+
- name: Smoke-test the frozen app
|
|
131
|
+
run: |
|
|
132
|
+
"${APP_PATH}/Contents/MacOS/SYNCmoss" --test
|
|
133
|
+
|
|
134
|
+
universal2:
|
|
135
|
+
name: Merge, sign and notarize the universal2 app
|
|
136
|
+
needs: build
|
|
137
|
+
runs-on: macos-15
|
|
138
|
+
timeout-minutes: 90
|
|
139
|
+
steps:
|
|
140
|
+
- uses: actions/checkout@v6
|
|
141
|
+
|
|
142
|
+
- uses: actions/setup-python@v6
|
|
143
|
+
with:
|
|
144
|
+
python-version: "3.13"
|
|
145
|
+
|
|
146
|
+
- name: Download the arm64 app
|
|
147
|
+
uses: actions/download-artifact@v6
|
|
148
|
+
with:
|
|
149
|
+
name: macos-app-arm64
|
|
150
|
+
path: bundles/arm64
|
|
151
|
+
|
|
152
|
+
- name: Download the x86_64 app
|
|
153
|
+
uses: actions/download-artifact@v6
|
|
154
|
+
with:
|
|
155
|
+
name: macos-app-x86_64
|
|
156
|
+
path: bundles/x86_64
|
|
157
|
+
|
|
158
|
+
- name: Check both builds used the same packages
|
|
159
|
+
run: |
|
|
160
|
+
if ! diff -u bundles/arm64/pip-freeze.txt bundles/x86_64/pip-freeze.txt; then
|
|
161
|
+
echo "::error::arm64 and x86_64 resolved different package versions, so the universal2 app would mix them; pin the differing packages in the build job's install step"
|
|
162
|
+
exit 1
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
- name: Extract the app bundles
|
|
166
|
+
run: |
|
|
167
|
+
set -euo pipefail
|
|
168
|
+
mkdir -p arm64 x86_64
|
|
169
|
+
tar -xpf bundles/arm64/SYNCmoss.app.tar.gz -C arm64
|
|
170
|
+
tar -xpf bundles/x86_64/SYNCmoss.app.tar.gz -C x86_64
|
|
171
|
+
|
|
172
|
+
# Adapted from crispy's package/merge-universal.py: walk both bundles,
|
|
173
|
+
# fuse every pair of thin Mach-O files with lipo, copy everything else
|
|
174
|
+
# (identical files, already-universal binaries, data files, symlinks),
|
|
175
|
+
# then require every Mach-O file of the result to carry both slices.
|
|
176
|
+
- name: Merge into a universal2 app
|
|
177
|
+
shell: python
|
|
178
|
+
run: |
|
|
179
|
+
import filecmp
|
|
180
|
+
import os
|
|
181
|
+
import shutil
|
|
182
|
+
import subprocess
|
|
183
|
+
import sys
|
|
184
|
+
|
|
185
|
+
ARM_APP = "arm64/SYNCmoss.app"
|
|
186
|
+
X86_APP = "x86_64/SYNCmoss.app"
|
|
187
|
+
OUT_APP = "dist/universal2/SYNCmoss.app"
|
|
188
|
+
|
|
189
|
+
THIN_MAGICS = {b"\xfe\xed\xfa\xce", b"\xfe\xed\xfa\xcf", b"\xce\xfa\xed\xfe", b"\xcf\xfa\xed\xfe"}
|
|
190
|
+
FAT_MAGICS = {b"\xca\xfe\xba\xbe", b"\xbe\xba\xfe\xca"}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def macho_kind(path):
|
|
194
|
+
"""'thin', 'fat' or None, from the magic bytes: PyInstaller ships
|
|
195
|
+
extension-less Mach-O files. Java .class files share the fat magic,
|
|
196
|
+
but a real universal binary has only a few architectures."""
|
|
197
|
+
with open(path, "rb") as f:
|
|
198
|
+
header = f.read(8)
|
|
199
|
+
if len(header) < 8:
|
|
200
|
+
return None
|
|
201
|
+
if header[:4] in THIN_MAGICS:
|
|
202
|
+
return "thin"
|
|
203
|
+
if header[:4] in FAT_MAGICS:
|
|
204
|
+
byteorder = "big" if header[:4] == b"\xca\xfe\xba\xbe" else "little"
|
|
205
|
+
return "fat" if int.from_bytes(header[4:8], byteorder) < 30 else None
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def collect(root):
|
|
210
|
+
"""Relative path -> 'dir', 'symlink' or 'file' (symlinked dirs are not descended)."""
|
|
211
|
+
entries = {}
|
|
212
|
+
for current, dirnames, filenames in os.walk(root):
|
|
213
|
+
for name in dirnames + filenames:
|
|
214
|
+
path = os.path.join(current, name)
|
|
215
|
+
if os.path.islink(path):
|
|
216
|
+
kind = "symlink"
|
|
217
|
+
elif os.path.isdir(path):
|
|
218
|
+
kind = "dir"
|
|
219
|
+
else:
|
|
220
|
+
kind = "file"
|
|
221
|
+
entries[os.path.relpath(path, root)] = kind
|
|
222
|
+
return entries
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
arm = collect(ARM_APP)
|
|
226
|
+
x86 = collect(X86_APP)
|
|
227
|
+
os.makedirs(OUT_APP)
|
|
228
|
+
single_arch = []
|
|
229
|
+
fused = 0
|
|
230
|
+
|
|
231
|
+
for rel in sorted(set(arm) | set(x86)):
|
|
232
|
+
arm_kind, x86_kind = arm.get(rel), x86.get(rel)
|
|
233
|
+
arm_path = os.path.join(ARM_APP, rel)
|
|
234
|
+
x86_path = os.path.join(X86_APP, rel)
|
|
235
|
+
out_path = os.path.join(OUT_APP, rel)
|
|
236
|
+
|
|
237
|
+
if "dir" in (arm_kind, x86_kind):
|
|
238
|
+
os.makedirs(out_path, exist_ok=True)
|
|
239
|
+
continue
|
|
240
|
+
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
|
241
|
+
|
|
242
|
+
if "symlink" in (arm_kind, x86_kind):
|
|
243
|
+
if arm_kind == x86_kind and os.readlink(arm_path) != os.readlink(x86_path):
|
|
244
|
+
print(f"WARNING: symlink target differs, using arm64: {rel}")
|
|
245
|
+
os.symlink(os.readlink(arm_path if arm_kind == "symlink" else x86_path), out_path)
|
|
246
|
+
continue
|
|
247
|
+
|
|
248
|
+
if arm_kind is None or x86_kind is None:
|
|
249
|
+
present = arm_path if arm_kind else x86_path
|
|
250
|
+
if macho_kind(present):
|
|
251
|
+
single_arch.append(rel)
|
|
252
|
+
else:
|
|
253
|
+
print(f"WARNING: data file present in one build only: {rel}")
|
|
254
|
+
shutil.copy2(present, out_path)
|
|
255
|
+
continue
|
|
256
|
+
|
|
257
|
+
if filecmp.cmp(arm_path, x86_path, shallow=False):
|
|
258
|
+
shutil.copy2(arm_path, out_path)
|
|
259
|
+
continue
|
|
260
|
+
|
|
261
|
+
arm_macho, x86_macho = macho_kind(arm_path), macho_kind(x86_path)
|
|
262
|
+
if arm_macho == x86_macho == "thin":
|
|
263
|
+
subprocess.run(["lipo", "-create", arm_path, x86_path, "-output", out_path], check=True)
|
|
264
|
+
shutil.copymode(arm_path, out_path)
|
|
265
|
+
fused += 1
|
|
266
|
+
elif "fat" in (arm_macho, x86_macho):
|
|
267
|
+
# Already universal; the two copies differ only in their signature.
|
|
268
|
+
shutil.copy2(arm_path if arm_macho == "fat" else x86_path, out_path)
|
|
269
|
+
elif arm_macho or x86_macho:
|
|
270
|
+
single_arch.append(rel)
|
|
271
|
+
else:
|
|
272
|
+
print(f"WARNING: data file differs, using arm64: {rel}")
|
|
273
|
+
shutil.copy2(arm_path, out_path)
|
|
274
|
+
|
|
275
|
+
if single_arch:
|
|
276
|
+
sys.exit(
|
|
277
|
+
"ERROR: these Mach-O binaries exist for one architecture only, so the "
|
|
278
|
+
"app would crash on the other:\n" + "\n".join(f" - {p}" for p in single_arch)
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
not_universal = []
|
|
282
|
+
checked = 0
|
|
283
|
+
for rel, kind in sorted(collect(OUT_APP).items()):
|
|
284
|
+
path = os.path.join(OUT_APP, rel)
|
|
285
|
+
if kind != "file" or not macho_kind(path):
|
|
286
|
+
continue
|
|
287
|
+
checked += 1
|
|
288
|
+
archs = subprocess.run(["lipo", "-archs", path], capture_output=True, text=True).stdout.split()
|
|
289
|
+
if not {"arm64", "x86_64"} <= set(archs):
|
|
290
|
+
not_universal.append(f" - {rel}: {' '.join(archs) or 'unreadable'}")
|
|
291
|
+
|
|
292
|
+
print(f"Fused {fused} binaries, checked {checked} Mach-O files")
|
|
293
|
+
if not_universal:
|
|
294
|
+
sys.exit("ERROR: these binaries are not universal2:\n" + "\n".join(not_universal))
|
|
295
|
+
print(f"Universal2 app written to {OUT_APP}")
|
|
296
|
+
|
|
297
|
+
# The merge leaves the bundle's seal stale, so the app is always re-signed:
|
|
298
|
+
# with the Developer ID when the certificate secrets are set, otherwise
|
|
299
|
+
# ad-hoc (so macOS reports "unverified developer", not "damaged").
|
|
300
|
+
- name: Sign the app
|
|
301
|
+
id: sign
|
|
302
|
+
env:
|
|
303
|
+
CERTIFICATE_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64 }}
|
|
304
|
+
CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_PASSWORD }}
|
|
305
|
+
KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
|
|
306
|
+
run: |
|
|
307
|
+
set -euo pipefail
|
|
308
|
+
APP="dist/universal2/SYNCmoss.app"
|
|
309
|
+
|
|
310
|
+
# The hardened runtime needs these for the numba/llvmlite JIT (writable
|
|
311
|
+
# + executable memory) and for loading the bundled Python extensions.
|
|
312
|
+
ENTITLEMENTS="${RUNNER_TEMP}/entitlements.plist"
|
|
313
|
+
cat > "${ENTITLEMENTS}" <<'EOF'
|
|
314
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
315
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
316
|
+
<plist version="1.0">
|
|
317
|
+
<dict>
|
|
318
|
+
<key>com.apple.security.cs.allow-jit</key>
|
|
319
|
+
<true/>
|
|
320
|
+
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
|
321
|
+
<true/>
|
|
322
|
+
<key>com.apple.security.cs.disable-library-validation</key>
|
|
323
|
+
<true/>
|
|
324
|
+
</dict>
|
|
325
|
+
</plist>
|
|
326
|
+
EOF
|
|
327
|
+
|
|
328
|
+
# Extended attributes would invalidate the signature.
|
|
329
|
+
xattr -cr "${APP}"
|
|
330
|
+
|
|
331
|
+
if [ -n "${CERTIFICATE_BASE64}" ] && [ -n "${CERTIFICATE_PASSWORD}" ]; then
|
|
332
|
+
KEYCHAIN_PATH="${RUNNER_TEMP}/signing.keychain-db"
|
|
333
|
+
CERTIFICATE_PATH="${RUNNER_TEMP}/certificate.p12"
|
|
334
|
+
if [ -z "${KEYCHAIN_PASSWORD}" ]; then
|
|
335
|
+
KEYCHAIN_PASSWORD="$(openssl rand -base64 24)"
|
|
336
|
+
echo "::add-mask::${KEYCHAIN_PASSWORD}"
|
|
337
|
+
fi
|
|
338
|
+
|
|
339
|
+
echo "Creating a temporary keychain and importing the certificate"
|
|
340
|
+
echo "${CERTIFICATE_BASE64}" | base64 --decode -o "${CERTIFICATE_PATH}"
|
|
341
|
+
security create-keychain -p "${KEYCHAIN_PASSWORD}" "${KEYCHAIN_PATH}"
|
|
342
|
+
security set-keychain-settings -lut 21600 "${KEYCHAIN_PATH}"
|
|
343
|
+
security unlock-keychain -p "${KEYCHAIN_PASSWORD}" "${KEYCHAIN_PATH}"
|
|
344
|
+
security import "${CERTIFICATE_PATH}" -P "${CERTIFICATE_PASSWORD}" -A -t cert -f pkcs12 -k "${KEYCHAIN_PATH}"
|
|
345
|
+
rm -f "${CERTIFICATE_PATH}"
|
|
346
|
+
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "${KEYCHAIN_PASSWORD}" "${KEYCHAIN_PATH}" > /dev/null
|
|
347
|
+
security list-keychains -d user -s "${KEYCHAIN_PATH}"
|
|
348
|
+
|
|
349
|
+
IDENTITY="$(security find-identity -v -p codesigning "${KEYCHAIN_PATH}" | awk '/"Developer ID Application/ && !found { print $2; found = 1 }')"
|
|
350
|
+
if [ -z "${IDENTITY}" ]; then
|
|
351
|
+
echo "::error::the certificate holds no 'Developer ID Application' identity"
|
|
352
|
+
security find-identity -v -p codesigning "${KEYCHAIN_PATH}"
|
|
353
|
+
exit 1
|
|
354
|
+
fi
|
|
355
|
+
|
|
356
|
+
echo "Signing with the Developer ID (hardened runtime)"
|
|
357
|
+
codesign --force --deep --timestamp --options runtime \
|
|
358
|
+
--entitlements "${ENTITLEMENTS}" \
|
|
359
|
+
--keychain "${KEYCHAIN_PATH}" \
|
|
360
|
+
--sign "${IDENTITY}" \
|
|
361
|
+
"${APP}"
|
|
362
|
+
security delete-keychain "${KEYCHAIN_PATH}"
|
|
363
|
+
echo "developer_id=true" >> "${GITHUB_OUTPUT}"
|
|
364
|
+
else
|
|
365
|
+
echo "::warning::Apple certificate secrets are not set: the macOS DMG is ad-hoc signed and not notarized"
|
|
366
|
+
codesign --force --deep --sign - "${APP}"
|
|
367
|
+
echo "developer_id=false" >> "${GITHUB_OUTPUT}"
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
codesign --verify --deep --strict --verbose=2 "${APP}"
|
|
371
|
+
echo "Universal executable architectures: $(lipo -archs "${APP}/Contents/MacOS/SYNCmoss")"
|
|
372
|
+
|
|
373
|
+
# On a copy, so files the test writes never reach the DMG. For a Developer
|
|
374
|
+
# ID build this runs under the hardened runtime, i.e. it also checks that
|
|
375
|
+
# the entitlements above are enough for numba.
|
|
376
|
+
- name: Smoke-test the signed universal2 app
|
|
377
|
+
run: |
|
|
378
|
+
set -euo pipefail
|
|
379
|
+
mkdir -p "${RUNNER_TEMP}/smoke"
|
|
380
|
+
ditto dist/universal2/SYNCmoss.app "${RUNNER_TEMP}/smoke/SYNCmoss.app"
|
|
381
|
+
"${RUNNER_TEMP}/smoke/SYNCmoss.app/Contents/MacOS/SYNCmoss" --test
|
|
382
|
+
|
|
383
|
+
# The drag-and-drop installer window, built as crispy's
|
|
384
|
+
# package/create-dmg.sh does: a writable image holding the app, a link to
|
|
385
|
+
# /Applications, the background picture (bundle/dmg_background.pdf) and
|
|
386
|
+
# the volume icon; Finder lays the window out (positions match the
|
|
387
|
+
# picture); then it is converted to the compressed read-only DMG.
|
|
388
|
+
- name: Create the DMG
|
|
389
|
+
run: |
|
|
390
|
+
set -euo pipefail
|
|
391
|
+
APP="dist/universal2/SYNCmoss.app"
|
|
392
|
+
STAGING="${RUNNER_TEMP}/dmg"
|
|
393
|
+
RW_DMG="${RUNNER_TEMP}/SYNCmoss-rw.dmg"
|
|
394
|
+
rm -rf "${STAGING}" "${RW_DMG}"
|
|
395
|
+
|
|
396
|
+
mkdir -p "${STAGING}/.background" "${STAGING}/.fseventsd"
|
|
397
|
+
ditto "${APP}" "${STAGING}/SYNCmoss.app"
|
|
398
|
+
ln -s /Applications "${STAGING}/Applications"
|
|
399
|
+
cp bundle/dmg_background.pdf "${STAGING}/.background/background.pdf"
|
|
400
|
+
# The volume shows the app's own icon.
|
|
401
|
+
ICON_FILE="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIconFile' "${APP}/Contents/Info.plist")"
|
|
402
|
+
case "${ICON_FILE}" in *.icns) ;; *) ICON_FILE="${ICON_FILE}.icns" ;; esac
|
|
403
|
+
cp "${APP}/Contents/Resources/${ICON_FILE}" "${STAGING}/.VolumeIcon.icns"
|
|
404
|
+
touch "${STAGING}/.fseventsd/no_log"
|
|
405
|
+
|
|
406
|
+
# Room for the .DS_Store Finder writes. hdiutil is occasionally
|
|
407
|
+
# "Resource busy" on hosted runners: retry.
|
|
408
|
+
SIZE_MB=$(( $(du -sm "${STAGING}" | cut -f1) + 50 ))
|
|
409
|
+
for attempt in 1 2 3; do
|
|
410
|
+
if hdiutil create -format UDRW -fs APFS -volname SYNCmoss -size "${SIZE_MB}m" \
|
|
411
|
+
-srcfolder "${STAGING}" -ov "${RW_DMG}"; then
|
|
412
|
+
break
|
|
413
|
+
fi
|
|
414
|
+
if [ "${attempt}" -eq 3 ]; then
|
|
415
|
+
exit 1
|
|
416
|
+
fi
|
|
417
|
+
echo "hdiutil create failed, retrying in 10 s"
|
|
418
|
+
sleep 10
|
|
419
|
+
done
|
|
420
|
+
|
|
421
|
+
ATTACH="$(hdiutil attach -readwrite -noverify -noautoopen "${RW_DMG}" | grep '^/dev/')"
|
|
422
|
+
DEVICE="$(echo "${ATTACH}" | head -n 1 | awk '{print $1}')"
|
|
423
|
+
MOUNT_POINT="$(echo "${ATTACH}" | awk -F'\t' '$3 != "" {print $3}' | tail -n 1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
|
424
|
+
echo "Attached ${DEVICE} at ${MOUNT_POINT}"
|
|
425
|
+
if [ ! -d "${MOUNT_POINT}" ]; then
|
|
426
|
+
echo "::error::could not find where the image was mounted"
|
|
427
|
+
echo "${ATTACH}"
|
|
428
|
+
exit 1
|
|
429
|
+
fi
|
|
430
|
+
SetFile -a C "${MOUNT_POINT}"
|
|
431
|
+
SetFile -a V "${MOUNT_POINT}/.background"
|
|
432
|
+
|
|
433
|
+
LAYOUT="${RUNNER_TEMP}/dmg-layout.applescript"
|
|
434
|
+
cat > "${LAYOUT}" <<'EOF'
|
|
435
|
+
on run argv
|
|
436
|
+
tell application "Finder"
|
|
437
|
+
tell disk (item 1 of argv)
|
|
438
|
+
open
|
|
439
|
+
set current view of container window to icon view
|
|
440
|
+
set toolbar visible of container window to false
|
|
441
|
+
set statusbar visible of container window to false
|
|
442
|
+
set bounds of container window to {200, 120, 700, 420}
|
|
443
|
+
set viewOptions to the icon view options of container window
|
|
444
|
+
set arrangement of viewOptions to not arranged
|
|
445
|
+
set icon size of viewOptions to 96
|
|
446
|
+
set background picture of viewOptions to file ".background:background.pdf"
|
|
447
|
+
set position of item "SYNCmoss.app" to {114, 124}
|
|
448
|
+
set position of item "Applications" to {386, 124}
|
|
449
|
+
close
|
|
450
|
+
open
|
|
451
|
+
delay 1
|
|
452
|
+
update without registering applications
|
|
453
|
+
delay 2
|
|
454
|
+
close
|
|
455
|
+
end tell
|
|
456
|
+
end tell
|
|
457
|
+
end run
|
|
458
|
+
EOF
|
|
459
|
+
# Finder scripting can time out on a busy runner: retry.
|
|
460
|
+
for attempt in 1 2 3; do
|
|
461
|
+
if osascript "${LAYOUT}" "$(basename "${MOUNT_POINT}")"; then
|
|
462
|
+
break
|
|
463
|
+
fi
|
|
464
|
+
if [ "${attempt}" -eq 3 ]; then
|
|
465
|
+
echo "::error::Finder could not lay out the DMG window"
|
|
466
|
+
exit 1
|
|
467
|
+
fi
|
|
468
|
+
echo "Finder layout failed, retrying in 5 s"
|
|
469
|
+
sleep 5
|
|
470
|
+
done
|
|
471
|
+
# Finder saves the layout in .DS_Store when the window closes.
|
|
472
|
+
for _ in $(seq 1 30); do
|
|
473
|
+
[ -f "${MOUNT_POINT}/.DS_Store" ] && break
|
|
474
|
+
sleep 1
|
|
475
|
+
done
|
|
476
|
+
if [ ! -f "${MOUNT_POINT}/.DS_Store" ]; then
|
|
477
|
+
echo "::error::Finder did not save the DMG window layout (.DS_Store)"
|
|
478
|
+
exit 1
|
|
479
|
+
fi
|
|
480
|
+
|
|
481
|
+
sync
|
|
482
|
+
for attempt in 1 2 3 4 5; do
|
|
483
|
+
if hdiutil detach "${DEVICE}"; then
|
|
484
|
+
break
|
|
485
|
+
fi
|
|
486
|
+
if [ "${attempt}" -eq 5 ]; then
|
|
487
|
+
hdiutil detach "${DEVICE}" -force
|
|
488
|
+
break
|
|
489
|
+
fi
|
|
490
|
+
echo "volume still busy, retrying in 5 s"
|
|
491
|
+
sleep 5
|
|
492
|
+
done
|
|
493
|
+
hdiutil convert "${RW_DMG}" -format UDZO -imagekey zlib-level=9 -ov -o dist/SYNCmoss.dmg
|
|
494
|
+
rm -f "${RW_DMG}"
|
|
495
|
+
|
|
496
|
+
- name: Notarize and staple the DMG
|
|
497
|
+
id: notarize
|
|
498
|
+
if: steps.sign.outputs.developer_id == 'true'
|
|
499
|
+
env:
|
|
500
|
+
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
501
|
+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
502
|
+
APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APPLICATION_SPECIFIC_PASSWORD }}
|
|
503
|
+
run: |
|
|
504
|
+
set -euo pipefail
|
|
505
|
+
if [ -z "${APPLE_ID}" ] || [ -z "${APPLE_TEAM_ID}" ] || [ -z "${APPLICATION_SPECIFIC_PASSWORD}" ]; then
|
|
506
|
+
echo "::warning::APPLE_ID / APPLE_TEAM_ID / APPLE_APPLICATION_SPECIFIC_PASSWORD are not set: the DMG is signed but not notarized, so Gatekeeper will still block it"
|
|
507
|
+
exit 0
|
|
508
|
+
fi
|
|
509
|
+
DMG="dist/SYNCmoss.dmg"
|
|
510
|
+
|
|
511
|
+
echo "Submitting ${DMG} for notarization"
|
|
512
|
+
RESULT="$(xcrun notarytool submit "${DMG}" \
|
|
513
|
+
--apple-id "${APPLE_ID}" --team-id "${APPLE_TEAM_ID}" --password "${APPLICATION_SPECIFIC_PASSWORD}" \
|
|
514
|
+
--wait --timeout 1h --output-format json)" || true
|
|
515
|
+
echo "${RESULT}"
|
|
516
|
+
SUBMISSION_ID="$(python -c 'import json, sys; print(json.loads(sys.argv[1]).get("id", ""))' "${RESULT}" 2> /dev/null || true)"
|
|
517
|
+
STATUS="$(python -c 'import json, sys; print(json.loads(sys.argv[1]).get("status", ""))' "${RESULT}" 2> /dev/null || true)"
|
|
518
|
+
if [ "${STATUS}" != "Accepted" ]; then
|
|
519
|
+
echo "::error::notarization did not succeed (status: '${STATUS:-none}')"
|
|
520
|
+
if [ -n "${SUBMISSION_ID}" ]; then
|
|
521
|
+
xcrun notarytool log "${SUBMISSION_ID}" \
|
|
522
|
+
--apple-id "${APPLE_ID}" --team-id "${APPLE_TEAM_ID}" --password "${APPLICATION_SPECIFIC_PASSWORD}" || true
|
|
523
|
+
fi
|
|
524
|
+
exit 1
|
|
525
|
+
fi
|
|
526
|
+
|
|
527
|
+
xcrun stapler staple "${DMG}"
|
|
528
|
+
xcrun stapler validate "${DMG}"
|
|
529
|
+
echo "notarized=true" >> "${GITHUB_OUTPUT}"
|
|
530
|
+
|
|
531
|
+
- name: Name the DMG
|
|
532
|
+
env:
|
|
533
|
+
NOTARIZED: ${{ steps.notarize.outputs.notarized }}
|
|
534
|
+
run: |
|
|
535
|
+
if [ "${NOTARIZED}" = "true" ]; then
|
|
536
|
+
mv dist/SYNCmoss.dmg dist/SYNCmoss-macOS-universal2-signed.dmg
|
|
537
|
+
else
|
|
538
|
+
mv dist/SYNCmoss.dmg dist/SYNCmoss-macOS-universal2.dmg
|
|
539
|
+
fi
|
|
540
|
+
ls -lh dist/*.dmg
|
|
541
|
+
|
|
542
|
+
- name: Upload the universal2 DMG
|
|
543
|
+
uses: actions/upload-artifact@v5
|
|
544
|
+
with:
|
|
545
|
+
name: macos-universal2
|
|
546
|
+
path: dist/SYNCmoss-macOS-universal2*.dmg
|
|
547
|
+
if-no-files-found: error
|
|
548
|
+
retention-days: 5
|
|
@@ -10,5 +10,11 @@ Third-party components are used under their respective licenses:
|
|
|
10
10
|
- NumPy: BSD 3-Clause
|
|
11
11
|
- SciPy: BSD 3-Clause
|
|
12
12
|
- Matplotlib: BSD-compatible
|
|
13
|
+
- Numba: BSD 2-Clause
|
|
14
|
+
- llvmlite: BSD 2-Clause (contains LLVM: Apache 2.0 with LLVM Exceptions)
|
|
15
|
+
- Bliss (optional, not included; used only if installed separately):
|
|
16
|
+
LGPL v3.0
|
|
13
17
|
|
|
14
|
-
For complete third-party license details, see NOTICE.txt.
|
|
18
|
+
For complete third-party license details, see NOTICE.txt. The Windows and
|
|
19
|
+
macOS bundles also carry the full license text of every third-party package
|
|
20
|
+
they contain, in a "licenses" folder.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
SYNCmoss - Third-Party Software Notices and Information
|
|
2
|
+
=========================================================
|
|
3
|
+
|
|
4
|
+
This project is licensed under the MIT License. See LICENSE for details.
|
|
5
|
+
It uses the following third-party libraries, each distributed under
|
|
6
|
+
their own license terms.
|
|
7
|
+
|
|
8
|
+
The Windows and macOS bundles contain the full license text of every
|
|
9
|
+
third-party package they include -- the libraries below, their own
|
|
10
|
+
dependencies and the Python runtime -- in a "licenses" folder:
|
|
11
|
+
- Windows: _internal/licenses/
|
|
12
|
+
- macOS: SYNCmoss.app/Contents/Resources/licenses/
|
|
13
|
+
In the program, Supp -> License shows this notice and all of those texts.
|
|
14
|
+
|
|
15
|
+
PySide6 (Qt for Python)
|
|
16
|
+
-----------------------
|
|
17
|
+
Licensed under the GNU Lesser General Public License (LGPL) v3.0
|
|
18
|
+
Copyright (C) The Qt Company Ltd.
|
|
19
|
+
https://www.qt.io/licensing/
|
|
20
|
+
|
|
21
|
+
LGPL v3.0 COMPLIANCE NOTICE:
|
|
22
|
+
This software dynamically links to PySide6. Under the terms of the
|
|
23
|
+
LGPL v3.0, you may:
|
|
24
|
+
- Use this MIT-licensed software without your own code becoming LGPL.
|
|
25
|
+
- Replace the PySide6 library with any compatible version.
|
|
26
|
+
- Obtain PySide6 source code at:
|
|
27
|
+
https://code.qt.io/cgit/pyside/pyside-setup.git/
|
|
28
|
+
- Read the full LGPL v3.0 license, and the GNU GPL v3.0 it builds on,
|
|
29
|
+
in the bundles' licenses/LGPL-3.0.txt and licenses/GPL-3.0.txt, or at:
|
|
30
|
+
https://www.gnu.org/licenses/lgpl-3.0.html
|
|
31
|
+
(The PySide6 wheels themselves carry only a Qt commercial-license notice,
|
|
32
|
+
which is why the two texts are shipped separately.) Qt itself contains
|
|
33
|
+
third-party components (FreeType, HarfBuzz, libpng, zlib, ...) under their
|
|
34
|
+
own licenses; The Qt Company lists them at
|
|
35
|
+
https://doc.qt.io/qt-6/licenses-used-in-qt.html
|
|
36
|
+
|
|
37
|
+
NumPy
|
|
38
|
+
-----
|
|
39
|
+
Licensed under the BSD 3-Clause License
|
|
40
|
+
Copyright (c) 2005-2025, NumPy Developers
|
|
41
|
+
https://numpy.org/doc/stable/license.html
|
|
42
|
+
|
|
43
|
+
SciPy
|
|
44
|
+
-----
|
|
45
|
+
Licensed under the BSD 3-Clause License
|
|
46
|
+
Copyright (c) 2001-2002 Enthought, Inc. 2003, SciPy Developers
|
|
47
|
+
https://scipy.org/scipylib/license.html
|
|
48
|
+
|
|
49
|
+
Matplotlib
|
|
50
|
+
----------
|
|
51
|
+
Licensed under the Matplotlib License (BSD-compatible)
|
|
52
|
+
Copyright (c) 2012- Matplotlib Development Team
|
|
53
|
+
Copyright (c) 2002-2011 John D. Hunter
|
|
54
|
+
https://matplotlib.org/stable/users/project/license.html
|
|
55
|
+
|
|
56
|
+
Numba
|
|
57
|
+
-----
|
|
58
|
+
Licensed under the BSD 2-Clause License
|
|
59
|
+
Copyright (c) 2012, Anaconda, Inc.
|
|
60
|
+
https://github.com/numba/numba/blob/main/LICENSE
|
|
61
|
+
|
|
62
|
+
llvmlite
|
|
63
|
+
--------
|
|
64
|
+
Licensed under the BSD 2-Clause License
|
|
65
|
+
Copyright (c) 2014, Anaconda, Inc.
|
|
66
|
+
https://github.com/numba/llvmlite/blob/main/LICENSE
|
|
67
|
+
It contains LLVM, licensed under the Apache License v2.0 with LLVM
|
|
68
|
+
Exceptions (https://llvm.org/LICENSE.txt).
|
|
69
|
+
|
|
70
|
+
Python
|
|
71
|
+
------
|
|
72
|
+
Licensed under the Python Software Foundation License
|
|
73
|
+
Copyright (c) Python Software Foundation; All Rights Reserved
|
|
74
|
+
https://docs.python.org/3/license.html
|
|
75
|
+
The bundles contain the Python runtime; its LICENSE.txt is included as
|
|
76
|
+
licenses/Python/LICENSE.txt. The C libraries built into the runtime
|
|
77
|
+
(OpenSSL, expat, libffi, zlib, libmpdec, ...) and their licenses are
|
|
78
|
+
listed at the address above.
|
|
79
|
+
|
|
80
|
+
Microsoft Visual C++ Runtime (Windows bundle only)
|
|
81
|
+
--------------------------------------------------
|
|
82
|
+
Copyright (c) Microsoft Corporation
|
|
83
|
+
The Windows bundle contains Microsoft's C and C++ run-time libraries
|
|
84
|
+
(VCRUNTIME140*.dll, MSVCP140*.dll, VCOMP140.dll and, where present,
|
|
85
|
+
ucrtbase.dll with the api-ms-win-*.dll forwarders). They are "Microsoft
|
|
86
|
+
Distributable Code", which Microsoft allows to be redistributed unmodified
|
|
87
|
+
with the programs that use them, provided that whoever uses or passes on
|
|
88
|
+
the Windows bundle does not:
|
|
89
|
+
- alter any copyright, trademark or patent notice in Microsoft's
|
|
90
|
+
Distributable Code;
|
|
91
|
+
- use Microsoft's trademarks in their programs' names or in a way that
|
|
92
|
+
suggests the programs come from or are endorsed by Microsoft;
|
|
93
|
+
- distribute Microsoft's Distributable Code to run on a platform other
|
|
94
|
+
than Microsoft operating systems, run-time technologies or application
|
|
95
|
+
platforms; or
|
|
96
|
+
- include Microsoft Distributable Code in malicious, deceptive or
|
|
97
|
+
unlawful programs.
|
|
98
|
+
These conditions apply only to Microsoft's files, not to SYNCmoss or to
|
|
99
|
+
the other components. The full terms are in Microsoft's documentation
|
|
100
|
+
(included in its developer tools and on microsoft.com).
|
|
101
|
+
|
|
102
|
+
Bliss (optional -- NOT included)
|
|
103
|
+
--------------------------------
|
|
104
|
+
Licensed under the GNU Lesser General Public License (LGPL) v3.0
|
|
105
|
+
Copyright (c) Beamline Control Unit, ESRF
|
|
106
|
+
https://gitlab.esrf.fr/bliss/bliss
|
|
107
|
+
SYNCmoss neither contains nor distributes Bliss, and the Windows and macOS
|
|
108
|
+
bundles never include it. Only when Bliss has been installed separately
|
|
109
|
+
(Linux beamline installations) does SYNCmoss import it at run time, to read
|
|
110
|
+
spectra online from a Bliss channel.
|
|
111
|
+
|
|
112
|
+
PyInstaller (bundling only)
|
|
113
|
+
---------------------------
|
|
114
|
+
Licensed under the GPL 2.0 with a special bootloader exception that
|
|
115
|
+
permits distributing non-GPL applications bundled with PyInstaller.
|
|
116
|
+
Copyright (c) 2010-2023, PyInstaller Development Team
|
|
117
|
+
https://pyinstaller.org/en/stable/license.html
|
|
118
|
+
The Windows and macOS bundles are created with PyInstaller. The
|
|
119
|
+
bootloader exception explicitly allows this use without requiring
|
|
120
|
+
the application itself to be GPL-licensed.
|