diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 979609725..92e6dec5c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,9 +85,9 @@ Please do **not** manually edit the `Localizable.strings`, `Localizable.stringsd ### Continuous Integration -Element X uses a combination of Swift command line tools and Fastlane for running actions on the CI and tries to keep the configuration confined to either [Tools/Sources](Tools/Sources) or the [FastFile](fastlane/Fastfile) alongside the project's [xcodegen](project.yml) configuration. +Element X uses a suite of Swift command line tools for running actions on the CI and tries to keep the configuration confined to [Tools/Sources](Tools/Sources) alongside the project's [xcodegen](project.yml) configuration. -Please run `swift run tools ci --help` and `bundle exec fastlane` to see available options. +Please run `swift run tools ci --help` to see available options. Note: We are in the process of converting our Fastlane lanes to Swift and so long-term are intending to remove Fastlane from the project all together. diff --git a/Gemfile b/Gemfile deleted file mode 100644 index d7330ee7e..000000000 --- a/Gemfile +++ /dev/null @@ -1,12 +0,0 @@ -# Autogenerated by fastlane -# -# Ensure this file is checked in to source control! - -source "https://rubygems.org" - -gem 'fastlane' -gem "xcode-install" -gem 'semantic' - -plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') -eval_gemfile('fastlane/Pluginfile') if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 74da208f4..000000000 --- a/Gemfile.lock +++ /dev/null @@ -1,356 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.8) - abbrev (0.1.2) - addressable (2.8.8) - public_suffix (>= 2.0.2, < 8.0) - artifactory (3.0.17) - atomos (0.1.3) - aws-eventstream (1.4.0) - aws-partitions (1.1213.0) - aws-sdk-core (3.242.0) - aws-eventstream (~> 1, >= 1.3.0) - aws-partitions (~> 1, >= 1.992.0) - aws-sigv4 (~> 1.9) - base64 - bigdecimal - jmespath (~> 1, >= 1.6.1) - logger - aws-sdk-kms (1.121.0) - aws-sdk-core (~> 3, >= 3.241.4) - aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.213.0) - aws-sdk-core (~> 3, >= 3.241.4) - aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.5) - aws-sigv4 (1.12.1) - aws-eventstream (~> 1, >= 1.0.2) - babosa (1.0.4) - base64 (0.2.0) - benchmark (0.5.0) - bigdecimal (4.0.1) - claide (1.1.0) - colored (1.2) - colored2 (3.1.2) - commander (4.6.0) - highline (~> 2.0.0) - csv (3.3.5) - declarative (0.0.20) - digest-crc (0.7.0) - rake (>= 12.0.0, < 14.0.0) - domain_name (0.6.20240107) - dotenv (2.8.1) - emoji_regex (3.2.3) - excon (0.112.0) - faraday (1.8.0) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0.1) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.1) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - multipart-post (>= 1.2, < 3) - ruby2_keywords (>= 0.0.4) - faraday-cookie_jar (0.0.8) - faraday (>= 0.8.0) - http-cookie (>= 1.0.0) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.1) - faraday-excon (1.1.0) - faraday-httpclient (1.0.1) - faraday-net_http (1.0.2) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday_middleware (1.2.1) - faraday (~> 1.0) - fastimage (2.4.0) - fastlane (2.232.2) - CFPropertyList (>= 2.3, < 4.0.0) - abbrev (~> 0.1.2) - addressable (>= 2.8, < 3.0.0) - artifactory (~> 3.0) - aws-sdk-s3 (~> 1.197) - babosa (>= 1.0.3, < 2.0.0) - base64 (~> 0.2.0) - benchmark (>= 0.1.0) - bundler (>= 1.17.3, < 5.0.0) - colored (~> 1.2) - commander (~> 4.6) - csv (~> 3.3) - dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (>= 0.1, < 4.0) - excon (>= 0.71.0, < 1.0.0) - faraday (~> 1.0) - faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 1.0) - fastimage (>= 2.1.0, < 3.0.0) - fastlane-sirp (>= 1.0.0) - gh_inspector (>= 1.1.2, < 2.0.0) - google-apis-androidpublisher_v3 (~> 0.3) - google-apis-playcustomapp_v1 (~> 0.1) - google-cloud-env (>= 1.6.0, <= 2.1.1) - google-cloud-storage (~> 1.31) - highline (~> 2.0) - http-cookie (~> 1.0.5) - json (< 3.0.0) - jwt (>= 2.1.0, < 3) - logger (>= 1.6, < 2.0) - mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (>= 2.0.0, < 3.0.0) - mutex_m (~> 0.3.0) - naturally (~> 2.2) - nkf (~> 0.2.0) - optparse (>= 0.1.1, < 1.0.0) - ostruct (>= 0.1.0) - plist (>= 3.1.0, < 4.0.0) - rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.5) - simctl (~> 1.6.3) - terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (~> 3) - tty-screen (>= 0.6.3, < 1.0.0) - tty-spinner (>= 0.8.0, < 1.0.0) - word_wrap (~> 1.0.0) - xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.4.1) - xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) - fastlane-plugin-brew (0.1.1) - fastlane-plugin-sentry (2.1.0) - os (~> 1.1, >= 1.1.4) - fastlane-plugin-xcconfig (2.1.0) - fastlane-plugin-xcodegen (1.1.0) - fastlane-plugin-brew (~> 0.1.1) - fastlane-sirp (1.0.0) - sysrandom (~> 1.0) - gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.96.0) - google-apis-core (>= 0.15.0, < 2.a) - google-apis-core (0.18.0) - addressable (~> 2.5, >= 2.5.1) - googleauth (~> 1.9) - httpclient (>= 2.8.3, < 3.a) - mini_mime (~> 1.0) - mutex_m - representable (~> 3.0) - retriable (>= 2.0, < 4.a) - google-apis-iamcredentials_v1 (0.26.0) - google-apis-core (>= 0.15.0, < 2.a) - google-apis-playcustomapp_v1 (0.17.0) - google-apis-core (>= 0.15.0, < 2.a) - google-apis-storage_v1 (0.60.0) - google-apis-core (>= 0.15.0, < 2.a) - google-cloud-core (1.8.0) - google-cloud-env (>= 1.0, < 3.a) - google-cloud-errors (~> 1.0) - google-cloud-env (2.1.1) - faraday (>= 1.0, < 3.a) - google-cloud-errors (1.5.0) - google-cloud-storage (1.58.0) - addressable (~> 2.8) - digest-crc (~> 0.4) - google-apis-core (>= 0.18, < 2) - google-apis-iamcredentials_v1 (~> 0.18) - google-apis-storage_v1 (>= 0.42) - google-cloud-core (~> 1.6) - googleauth (~> 1.9) - mini_mime (~> 1.0) - googleauth (1.11.2) - faraday (>= 1.0, < 3.a) - google-cloud-env (~> 2.1) - jwt (>= 1.4, < 3.0) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - signet (>= 0.16, < 2.a) - highline (2.0.3) - http-cookie (1.0.8) - domain_name (~> 0.5) - httpclient (2.9.0) - mutex_m - jmespath (1.6.2) - json (2.18.1) - jwt (2.10.2) - base64 - logger (1.7.0) - mini_magick (4.13.2) - mini_mime (1.1.5) - multi_json (1.19.1) - multipart-post (2.4.1) - mutex_m (0.3.0) - nanaimo (0.4.0) - naturally (2.3.0) - nkf (0.2.0) - optparse (0.8.1) - os (1.1.4) - ostruct (0.6.3) - plist (3.7.2) - public_suffix (7.0.2) - rake (13.3.1) - representable (3.2.0) - declarative (< 0.1.0) - trailblazer-option (>= 0.1.1, < 0.2.0) - uber (< 0.2.0) - retriable (3.1.2) - rexml (3.4.4) - rouge (3.28.0) - ruby2_keywords (0.0.5) - rubyzip (2.4.1) - security (0.1.5) - semantic (1.6.1) - signet (0.21.0) - addressable (~> 2.8) - faraday (>= 0.17.5, < 3.a) - jwt (>= 1.5, < 4.0) - multi_json (~> 1.10) - simctl (1.6.10) - CFPropertyList - naturally - sysrandom (1.0.5) - terminal-notifier (2.0.0) - terminal-table (3.0.2) - unicode-display_width (>= 1.1.1, < 3) - trailblazer-option (0.1.2) - tty-cursor (0.7.1) - tty-screen (0.8.2) - tty-spinner (0.9.3) - tty-cursor (~> 0.7) - uber (0.1.0) - unicode-display_width (2.6.0) - word_wrap (1.0.0) - xcode-install (2.8.1) - claide (>= 0.9.1) - fastlane (>= 2.1.0, < 3.0.0) - xcodeproj (1.27.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.4.0) - rexml (>= 3.3.6, < 4.0) - xcpretty (0.4.1) - rouge (~> 3.28.0) - xcpretty-travis-formatter (1.0.1) - xcpretty (~> 0.2, >= 0.0.7) - -PLATFORMS - arm64-darwin-25 - ruby - -DEPENDENCIES - abbrev - fastlane - fastlane-plugin-sentry - fastlane-plugin-xcconfig - fastlane-plugin-xcodegen - mutex_m - ostruct - semantic - xcode-install - -CHECKSUMS - CFPropertyList (3.0.8) sha256=2c99d0d980536d3d7ab252f7bd59ac8be50fbdd1ff487c98c949bb66bb114261 - abbrev (0.1.2) sha256=ad1b4eaaaed4cb722d5684d63949e4bde1d34f2a95e20db93aecfe7cbac74242 - addressable (2.8.8) sha256=7c13b8f9536cf6364c03b9d417c19986019e28f7c00ac8132da4eb0fe393b057 - artifactory (3.0.17) sha256=3023d5c964c31674090d655a516f38ca75665c15084140c08b7f2841131af263 - atomos (0.1.3) sha256=7d43b22f2454a36bace5532d30785b06de3711399cb1c6bf932573eda536789f - aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b - aws-partitions (1.1213.0) sha256=5ec132d91d44ef2702125b8f71f0e4fc2cd7de040e02c5d0aefb87219fd2e05e - aws-sdk-core (3.242.0) sha256=c17b3003acc78d80c1a8437b285a1cfc5e4d7749ce7821cf3071e847535a29a0 - aws-sdk-kms (1.121.0) sha256=d563c1cfb4b5754efbc671216c8eca875338748adad0f42518c28dfa0a2d01e0 - aws-sdk-s3 (1.213.0) sha256=af596ccf544582406db610e95cc9099276eaf03142f57a2f30f76940e598e50d - aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00 - babosa (1.0.4) sha256=18dea450f595462ed7cb80595abd76b2e535db8c91b350f6c4b3d73986c5bc99 - base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507 - benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c - bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7 - claide (1.1.0) sha256=6d3c5c089dde904d96aa30e73306d0d4bd444b1accb9b3125ce14a3c0183f82e - colored (1.2) sha256=9d82b47ac589ce7f6cab64b1f194a2009e9fd00c326a5357321f44afab2c1d2c - colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a - commander (4.6.0) sha256=7d1ddc3fccae60cc906b4131b916107e2ef0108858f485fdda30610c0f2913d9 - csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f - declarative (0.0.20) sha256=8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9 - digest-crc (0.7.0) sha256=64adc23a26a241044cbe6732477ca1b3c281d79e2240bcff275a37a5a0d78c07 - domain_name (0.6.20240107) sha256=5f693b2215708476517479bf2b3802e49068ad82167bcd2286f899536a17d933 - dotenv (2.8.1) sha256=c5944793349ae03c432e1780a2ca929d60b88c7d14d52d630db0508c3a8a17d8 - emoji_regex (3.2.3) sha256=ecd8be856b7691406c6bf3bb3a5e55d6ed683ffab98b4aa531bb90e1ddcc564b - excon (0.112.0) sha256=daf9ac3a4c2fc9aa48383a33da77ecb44fa395111e973084d5c52f6f214ae0f0 - faraday (1.8.0) sha256=d1fb776cf25973b7f52a82b625bb0a009fe30ad6021ef838fb9109bf1ea6d029 - faraday-cookie_jar (0.0.8) sha256=0140605823f8cc63c7028fccee486aaed8e54835c360cffc1f7c8c07c4299dbb - faraday-em_http (1.0.0) sha256=7a3d4c7079789121054f57e08cd4ef7e40ad1549b63101f38c7093a9d6c59689 - faraday-em_synchrony (1.0.1) sha256=bf3ce45dcf543088d319ab051f80985ea6d294930635b7a0b966563179f81750 - faraday-excon (1.1.0) sha256=b055c842376734d7f74350fe8611542ae2000c5387348d9ba9708109d6e40940 - faraday-httpclient (1.0.1) sha256=4c8ff1f0973ff835be8d043ef16aaf54f47f25b7578f6d916deee8399a04d33b - faraday-net_http (1.0.2) sha256=63992efea42c925a20818cf3c0830947948541fdcf345842755510d266e4c682 - faraday-net_http_persistent (1.2.0) sha256=0b0cbc8f03dab943c3e1cc58d8b7beb142d9df068b39c718cd83e39260348335 - faraday-patron (1.0.0) sha256=dc2cd7b340bb3cc8e36bcb9e6e7eff43d134b6d526d5f3429c7a7680ddd38fa7 - faraday-rack (1.0.0) sha256=ef60ec969a2bb95b8dbf24400155aee64a00fc8ba6c6a4d3968562bcc92328c0 - faraday_middleware (1.2.1) sha256=d45b78c8ee864c4783fbc276f845243d4a7918a67301c052647bacabec0529e9 - fastimage (2.4.0) sha256=5fce375e27d3bdbb46c18dbca6ba9af29d3304801ae1eb995771c4796c5ac7e8 - fastlane (2.232.2) sha256=978689f60f0fc3d54699de86ef12be4eda9f5b52217c1798965257c390d2b112 - fastlane-plugin-brew (0.1.1) sha256=c2d1c98ead82f78818621b5b1eb123e0bf9da7e1a7b3efeaac77a776725a7fbf - fastlane-plugin-sentry (2.1.0) sha256=21400b8d06c764c546221ef26baceb9d7ef665761219cf0a789fe73ef84db5f2 - fastlane-plugin-xcconfig (2.1.0) sha256=208a01c4f913f38fb19bb258e596f90c3a92ae4796f5d07347c034451d6895c2 - fastlane-plugin-xcodegen (1.1.0) sha256=9bbed6c2ea21ca6869567e350d0b62cf41770949d15e40869058d689f1a6d06d - fastlane-sirp (1.0.0) sha256=66478f25bcd039ec02ccf65625373fca29646fa73d655eb533c915f106c5e641 - gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939 - google-apis-androidpublisher_v3 (0.96.0) sha256=9e27b03295fdd2c4a67b5e4d11f891492c89f73beff4a3f9323419165a56d01c - google-apis-core (0.18.0) sha256=96b057816feeeab448139ed5b5c78eab7fc2a9d8958f0fbc8217dedffad054ee - google-apis-iamcredentials_v1 (0.26.0) sha256=3ff70a10a1d6cddf2554e95b7c5df2c26afdeaeb64100048a355194da19e48a3 - google-apis-playcustomapp_v1 (0.17.0) sha256=d5bc90b705f3f862bab4998086449b0abe704ee1685a84821daa90ca7fa95a78 - google-apis-storage_v1 (0.60.0) sha256=ad2511b7128344248c2a16adb56ad4b1b48f37d53925455b7b77acccfe75367a - google-cloud-core (1.8.0) sha256=e572edcbf189cfcab16590628a516cec3f4f63454b730e59f0b36575120281cf - google-cloud-env (2.1.1) sha256=cf4bb8c7d517ee1ea692baedf06e0b56ce68007549d8d5a66481aa9f97f46999 - google-cloud-errors (1.5.0) sha256=b56be28b8c10628125214dde571b925cfcebdbc58619e598250c37a2114f7b4b - google-cloud-storage (1.58.0) sha256=1bedc07a9c75af169e1ede1dd306b9f941f9ffa9e7095d0364c0803c468fdffd - googleauth (1.11.2) sha256=7e6bacaeed7aea3dd66dcea985266839816af6633e9f5983c3c2e0e40a44731e - highline (2.0.3) sha256=2ddd5c127d4692721486f91737307236fe005352d12a4202e26c48614f719479 - http-cookie (1.0.8) sha256=b14fe0445cf24bf9ae098633e9b8d42e4c07c3c1f700672b09fbfe32ffd41aa6 - httpclient (2.9.0) sha256=4b645958e494b2f86c2f8a2f304c959baa273a310e77a2931ddb986d83e498c8 - jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 - json (2.18.1) sha256=fe112755501b8d0466b5ada6cf50c8c3f41e897fa128ac5d263ec09eedc9f986 - jwt (2.10.2) sha256=31e1ee46f7359883d5e622446969fe9c118c3da87a0b1dca765ce269c3a0c4f4 - logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 - mini_magick (4.13.2) sha256=71d6258e0e8a3d04a9a0a09784d5d857b403a198a51dd4f882510435eb95ddd9 - mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef - multi_json (1.19.1) sha256=7aefeff8f2c854bf739931a238e4aea64592845e0c0395c8a7d2eea7fdd631b7 - multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8 - mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751 - nanaimo (0.4.0) sha256=faf069551bab17f15169c1f74a1c73c220657e71b6e900919897a10d991d0723 - naturally (2.3.0) sha256=459923cf76c2e6613048301742363200c3c7e4904c324097d54a67401e179e01 - nkf (0.2.0) sha256=fbc151bda025451f627fafdfcb3f4f13d0b22ae11f58c6d3a2939c76c5f5f126 - optparse (0.8.1) sha256=42bea10d53907ccff4f080a69991441d611fbf8733b60ed1ce9ee365ce03bd1a - os (1.1.4) sha256=57816d6a334e7bd6aed048f4b0308226c5fb027433b67d90a9ab435f35108d3f - ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912 - plist (3.7.2) sha256=d37a4527cc1116064393df4b40e1dbbc94c65fa9ca2eec52edf9a13616718a42 - public_suffix (7.0.2) sha256=9114090c8e4e7135c1fd0e7acfea33afaab38101884320c65aaa0ffb8e26a857 - rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c - representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace - retriable (3.1.2) sha256=0a5a5d0ca4ba61a76fb31a17ab8f7f80281beb040c329d34dfc137a1398688e0 - rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 - rouge (3.28.0) sha256=0d6de482c7624000d92697772ab14e48dca35629f8ddf3f4b21c99183fd70e20 - ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef - rubyzip (2.4.1) sha256=8577c88edc1fde8935eb91064c5cb1aef9ad5494b940cf19c775ee833e075615 - security (0.1.5) sha256=3a977a0eca7706e804c96db0dd9619e0a94969fe3aac9680fcfc2bf9b8a833b7 - semantic (1.6.1) sha256=3cdbb48f59198ebb782a3fdfb87b559e0822a311610db153bae22777a7d0c163 - signet (0.21.0) sha256=d617e9fbf24928280d39dcfefba9a0372d1c38187ffffd0a9283957a10a8cd5b - simctl (1.6.10) sha256=b99077f4d13ad81eace9f86bf5ba4df1b0b893a4d1b368bd3ed59b5b27f9236b - sysrandom (1.0.5) sha256=5ac1ac3c2ec64ef76ac91018059f541b7e8f437fbda1ccddb4f2c56a9ccf1e75 - terminal-notifier (2.0.0) sha256=7a0d2b2212ab9835c07f4b2e22a94cff64149dba1eed203c04835f7991078cea - terminal-table (3.0.2) sha256=f951b6af5f3e00203fb290a669e0a85c5dd5b051b3b023392ccfd67ba5abae91 - trailblazer-option (0.1.2) sha256=20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3 - tty-cursor (0.7.1) sha256=79534185e6a777888d88628b14b6a1fdf5154a603f285f80b1753e1908e0bf48 - tty-screen (0.8.2) sha256=c090652115beae764336c28802d633f204fb84da93c6a968aa5d8e319e819b50 - tty-spinner (0.9.3) sha256=0e036f047b4ffb61f2aa45f5a770ec00b4d04130531558a94bfc5b192b570542 - uber (0.1.0) sha256=5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc - unicode-display_width (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a - word_wrap (1.0.0) sha256=f556d4224c812e371000f12a6ee8102e0daa724a314c3f246afaad76d82accc7 - xcode-install (2.8.1) sha256=74b7262156f23584145dd69441d26190b69d1add0172c8ead4beb358be97b386 - xcodeproj (1.27.0) sha256=8cc7a73b4505c227deab044dce118ede787041c702bc47636856a2e566f854d3 - xcpretty (0.4.1) sha256=b14c50e721f6589ee3d6f5353e2c2cfcd8541fa1ea16d6c602807dd7327f3892 - xcpretty-travis-formatter (1.0.1) sha256=aacc332f17cb7b2cba222994e2adc74223db88724fe76341483ad3098e232f93 - -BUNDLED WITH - 4.0.3 diff --git a/Tools/Sources/Commands/CI/AccessibilityTests.swift b/Tools/Sources/Commands/CI/AccessibilityTests.swift index 54c2dc62e..063af6254 100644 --- a/Tools/Sources/Commands/CI/AccessibilityTests.swift +++ b/Tools/Sources/Commands/CI/AccessibilityTests.swift @@ -1,5 +1,4 @@ import ArgumentParser -import CommandLineTools import Foundation struct AccessibilityTests: AsyncParsableCommand { diff --git a/Tools/Sources/Commands/CI/CI.swift b/Tools/Sources/Commands/CI/CI.swift index 1610c3e2e..ff5a0b048 100644 --- a/Tools/Sources/Commands/CI/CI.swift +++ b/Tools/Sources/Commands/CI/CI.swift @@ -14,7 +14,8 @@ struct CI: ParsableCommand { ConfigureNightly.self, ConfigureProduction.self, TagNightly.self, - UploadDSYMs.self + UploadDSYMs.self, + ReleaseToGitHub.self ]) static let testOutputDirectory = "test_output" @@ -123,4 +124,41 @@ struct CI: ParsableCommand { return result } + + // MARK: - Git + + static func gitConfigureGlobals() async throws { + try await CI.run(.name("git"), ["config", "--global", "user.name", "Element CI"]) + try await CI.run(.name("git"), ["config", "--global", "user.email", "ci@element.io"]) + } + + static func gitRepositoryURL() async throws -> String { + guard let rawURL = try await CI.run(.name("git"), ["ls-remote", "--get-url", "origin"], + output: .string(limit: 4096)).standardOutput else { + throw ValidationError("Could not determine the git remote URL.") + } + + return rawURL + .replacingOccurrences(of: "http://", with: "") + .replacingOccurrences(of: "https://", with: "") + .replacingOccurrences(of: "git@", with: "") + .replacingOccurrences(of: ".git", with: "") + .trimmingCharacters(in: .whitespacesAndNewlines) + } + + static func gitPush(tagName: String? = nil) async throws { + guard let apiToken = ProcessInfo.processInfo.environment["GITHUB_TOKEN"], !apiToken.isEmpty + else { + throw ValidationError("GITHUB_TOKEN environment variable is not set.") + } + + let repoURL = try await CI.gitRepositoryURL() + + if let tagName { + try await CI.run(.name("git"), ["tag", tagName]) + try await CI.run(.name("git"), ["push", "https://\(apiToken)@\(repoURL)", tagName]) + } else { + try await CI.run(.name("git"), ["push", "https://\(apiToken)@\(repoURL)"]) + } + } } diff --git a/Tools/Sources/Commands/CI/ConfigureNightly.swift b/Tools/Sources/Commands/CI/ConfigureNightly.swift index 17acfd8e1..7e856f763 100644 --- a/Tools/Sources/Commands/CI/ConfigureNightly.swift +++ b/Tools/Sources/Commands/CI/ConfigureNightly.swift @@ -1,5 +1,4 @@ import ArgumentParser -import CommandLineTools import Foundation import Yams diff --git a/Tools/Sources/Commands/CI/ConfigureProduction.swift b/Tools/Sources/Commands/CI/ConfigureProduction.swift index c2639ae64..fd023a582 100644 --- a/Tools/Sources/Commands/CI/ConfigureProduction.swift +++ b/Tools/Sources/Commands/CI/ConfigureProduction.swift @@ -1,5 +1,4 @@ import ArgumentParser -import CommandLineTools import Foundation struct ConfigureProduction: AsyncParsableCommand { diff --git a/Tools/Sources/Commands/CI/IntegrationTests.swift b/Tools/Sources/Commands/CI/IntegrationTests.swift index 1798bc964..0a978973c 100644 --- a/Tools/Sources/Commands/CI/IntegrationTests.swift +++ b/Tools/Sources/Commands/CI/IntegrationTests.swift @@ -1,5 +1,4 @@ import ArgumentParser -import CommandLineTools import Foundation struct IntegrationTests: AsyncParsableCommand { diff --git a/Tools/Sources/Commands/CI/ReleaseToGithub.swift b/Tools/Sources/Commands/CI/ReleaseToGithub.swift new file mode 100644 index 000000000..f4421b112 --- /dev/null +++ b/Tools/Sources/Commands/CI/ReleaseToGithub.swift @@ -0,0 +1,159 @@ +import ArgumentParser +import Foundation +import Yams + +struct ReleaseToGitHub: AsyncParsableCommand { + static let configuration = CommandConfiguration(commandName: "release-to-github", + abstract: "Creates a GitHub release and updates CHANGES.md with generated release notes.") + + enum ReleaseError: LocalizedError { + case missingGitHubToken + case failedToCreateRelease(String) + case failedToParseResponse + case missingReleaseNotes + case failedToReadVersion + + var errorDescription: String? { + switch self { + case .missingGitHubToken: + return "The GITHUB_TOKEN environment variable is not set." + case .failedToCreateRelease(let message): + return "Failed to create GitHub release: \(message)" + case .failedToParseResponse: + return "Failed to parse the GitHub API response." + case .missingReleaseNotes: + return "The generated release notes are empty." + case .failedToReadVersion: + return "Failed to read the marketing version from project.yml." + } + } + } + + func run() async throws { + let currentVersion = try CI.readMarketingVersion() + logger.info("Creating GitHub release for version \(currentVersion)…") + + let releaseBody = try await createGitHubRelease(version: currentVersion) + + try updateChangelog(version: currentVersion, generatedNotes: releaseBody) + + let changesFilePath = URL.projectDirectory.appendingPathComponent("CHANGES.md").path + try await CI.run(.name("git"), ["add", changesFilePath]) + + logger.info("Successfully created GitHub release \(currentVersion) and updated CHANGES.md.") + + let targetFilePath = "project.yml" + let xcodeProjPath = "ElementX.xcodeproj" + + guard let newVersion = bumpPatchVersion(currentVersion) else { + throw ValidationError("Invalid version format: \(currentVersion)") + } + + // Bump the patch version using sed (preserves file formatting) + try await CI.run(.name("sed"), ["-i", "''", "'s/MARKETING_VERSION: \(currentVersion)/MARKETING_VERSION: \(newVersion)/g'", targetFilePath]) + logger.info("Version updated from \(currentVersion) to \(newVersion)") + + try await CI.run(.name("xcodegen")) + + try await CI.gitConfigureGlobals() + + try await CI.run(.name("git"), ["add", targetFilePath, xcodeProjPath]) + try await CI.run(.name("git"), ["commit", "-m", "Prepare next release"]) + + try await CI.gitPush() + + try await rebaseMainOntoCurrentBranch() + } + + // MARK: - Private + + private func createGitHubRelease(version: String) async throws -> String { + guard let apiToken = ProcessInfo.processInfo.environment["GITHUB_TOKEN"], !apiToken.isEmpty + else { + throw ReleaseError.missingGitHubToken + } + + let url = URL(string: "https://api.github.com/repos/element-hq/element-x-ios/releases")! + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("Bearer \(apiToken)", forHTTPHeaderField: "Authorization") + request.setValue("application/vnd.github+json", forHTTPHeaderField: "Accept") + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + let body: [String: Any] = ["tag_name": "release/\(version)", + "name": version, + "generate_release_notes": true] + request.httpBody = try JSONSerialization.data(withJSONObject: body) + + let (data, response) = try await URLSession.shared.data(for: request) + + guard let httpResponse = response as? HTTPURLResponse else { + throw ReleaseError.failedToParseResponse + } + + guard (200...299).contains(httpResponse.statusCode) else { + let errorBody = String(data: data, encoding: .utf8) ?? "Unknown error" + throw ReleaseError.failedToCreateRelease("HTTP \(httpResponse.statusCode): \(errorBody)") + } + + guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any], + let releaseBody = json["body"] as? String else { + throw ReleaseError.failedToParseResponse + } + + return releaseBody + } + + private func updateChangelog(version: String, generatedNotes: String) throws { + let changesURL = URL.projectDirectory.appending(component: "CHANGES.md") + + // Clean up the generated notes: remove HTML comments and adjust header levels + let cleanedNotes = generatedNotes + .replacingOccurrences(of: "", with: "", options: .regularExpression) + .replacingOccurrences(of: "### ", with: "\n") + .replacingOccurrences(of: "## ", with: "### ") + + guard !cleanedNotes.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { + throw ReleaseError.missingReleaseNotes + } + + let releaseDate = Date().formatted(.iso8601.year().month().day()) + + let existingContent = try String(contentsOf: changesURL) + let newContent = "## Changes in \(version) (\(releaseDate))\(cleanedNotes)\n\n\(existingContent)" + + try newContent.write(to: changesURL, atomically: true, encoding: .utf8) + logger.info("Updated CHANGES.md with release notes.") + } + + private func bumpPatchVersion(_ version: String) -> String? { + let regex = /^(\d{2})\.(\d{2})\.(\d+)$/ + guard let match = version.firstMatch(of: regex), var patch = Int(match.3) else { + return nil + } + + let year = String(match.1) + let month = String(match.2) + patch = patch + 1 + + return "\(year).\(month).\(patch)" + } + + private func rebaseMainOntoCurrentBranch() async throws { + guard let currentBranch = try await CI.run(.name("git"), ["rev-parse", "--abbrev-ref", "HEAD"], output: .string(limit: 4096)) + .standardOutput.map({ $0.trimmingCharacters(in: .whitespacesAndNewlines) }) else { + throw ValidationError("Could not determine the current branch.") + } + + logger.info("Current branch: \(currentBranch)") + + try await CI.run(.name("git"), ["reset", "--hard"]) + try await CI.run(.name("git"), ["checkout", "main"]) + try await CI.run(.name("git"), ["pull", "origin", "main"]) + try await CI.run(.name("git"), ["rebase", "currentBranch"]) + + try await CI.gitPush() + + logger.info("Successfully rebased main onto \(currentBranch)") + } +} diff --git a/Tools/Sources/Commands/CI/RunTests.swift b/Tools/Sources/Commands/CI/RunTests.swift index ea8363fac..b315a70d0 100644 --- a/Tools/Sources/Commands/CI/RunTests.swift +++ b/Tools/Sources/Commands/CI/RunTests.swift @@ -1,5 +1,4 @@ import ArgumentParser -import CommandLineTools import Foundation struct RunTests: AsyncParsableCommand { diff --git a/Tools/Sources/Commands/CI/TagNightly.swift b/Tools/Sources/Commands/CI/TagNightly.swift index 22841ec3c..16cb8ba36 100644 --- a/Tools/Sources/Commands/CI/TagNightly.swift +++ b/Tools/Sources/Commands/CI/TagNightly.swift @@ -1,5 +1,4 @@ import ArgumentParser -import CommandLineTools import Foundation import Yams @@ -14,38 +13,13 @@ struct TagNightly: AsyncParsableCommand { throw ValidationError("Invalid build number.") } - guard let apiToken = ProcessInfo.processInfo.environment["GITHUB_TOKEN"], - !apiToken.isEmpty else { - throw ValidationError("Invalid GitHub API token. Please set the GITHUB_TOKEN environment variable.") - } - - let repoURL = try getRepoURL() - - try await CI.run(.name("git"), ["config", "--global", "user.name", "Element CI"]) - try await CI.run(.name("git"), ["config", "--global", "user.email", "ci@element.io"]) + try await CI.gitConfigureGlobals() let currentVersion = try CI.readMarketingVersion() let tagName = "nightly/\(currentVersion).\(buildNumber)" - try await CI.run(.name("git"), ["tag", tagName]) - try await CI.run(.name("git"), ["push", "https://\(apiToken)@\(repoURL)", tagName]) + try await CI.gitPush(tagName: tagName) logger.info("\nšŸš€ Successfully tagged nightly: \(tagName)\n") } - - // MARK: - Private - - private func getRepoURL() throws -> String { - guard let rawURL = try Zsh.run(command: "git ls-remote --get-url origin") else { - throw ValidationError("Could not determine the git remote URL.") - } - - return - rawURL - .replacingOccurrences(of: "http://", with: "") - .replacingOccurrences(of: "https://", with: "") - .replacingOccurrences(of: "git@", with: "") - .replacingOccurrences(of: ".git", with: "") - .trimmingCharacters(in: .whitespacesAndNewlines) - } } diff --git a/Tools/Sources/Commands/CI/UITests.swift b/Tools/Sources/Commands/CI/UITests.swift index 25c8dad6f..69d80b1bb 100644 --- a/Tools/Sources/Commands/CI/UITests.swift +++ b/Tools/Sources/Commands/CI/UITests.swift @@ -1,5 +1,4 @@ import ArgumentParser -import CommandLineTools import Foundation struct UITests: AsyncParsableCommand { diff --git a/Tools/Sources/Commands/CI/UnitTests.swift b/Tools/Sources/Commands/CI/UnitTests.swift index d29c0cb03..04a7d66f5 100644 --- a/Tools/Sources/Commands/CI/UnitTests.swift +++ b/Tools/Sources/Commands/CI/UnitTests.swift @@ -1,5 +1,4 @@ import ArgumentParser -import CommandLineTools import Foundation struct UnitTests: AsyncParsableCommand { diff --git a/Tools/Sources/Commands/CI/UploadDSYMs.swift b/Tools/Sources/Commands/CI/UploadDSYMs.swift index 6dc07ea76..294520a96 100644 --- a/Tools/Sources/Commands/CI/UploadDSYMs.swift +++ b/Tools/Sources/Commands/CI/UploadDSYMs.swift @@ -1,5 +1,4 @@ import ArgumentParser -import CommandLineTools import Foundation struct UploadDSYMs: AsyncParsableCommand { diff --git a/ci_scripts/ci_common.sh b/ci_scripts/ci_common.sh index df41ca282..d9a035ffc 100755 --- a/ci_scripts/ci_common.sh +++ b/ci_scripts/ci_common.sh @@ -1,33 +1,9 @@ #!/bin/sh -setup_xcode_cloud_environment () { - # Return on failures - # Fail when expanding unset variables - # Trace each command before executing it - set -eEu - - # Move to the project root - cd .. - - # Prevent installing dependencies in system directories - echo 'export GEM_HOME=$HOME/.gem' >>~/.zshrc - echo 'export PATH=$GEM_HOME/bin:$PATH' >>~/.zshrc - echo 'export PATH="/usr/local/opt/ruby/bin:$PATH"' >> ~/.zshrc - echo 'export PATH="/Users/local/Library/Python/3.9/bin:$PATH"' >> ~/.zshrc - - export GEM_HOME=$HOME/.gem - export PATH=$GEM_HOME/bin:$PATH - export PATH="/usr/local/opt/ruby/bin:$PATH" - export PATH="/Users/local/Library/Python/3.9/bin:$PATH" - - # Things don't work well on the default ruby version - brew install ruby - - gem install bundler - - bundle config path vendor/bundle - bundle install --jobs 4 --retry 3 -} +# Return on failures +# Fail when expanding unset variables +# Trace each command before executing it +set -eEu install_xcode_cloud_brew_dependencies () { brew update && brew install xcodegen pkl getsentry/tools/sentry-cli diff --git a/ci_scripts/ci_post_clone.sh b/ci_scripts/ci_post_clone.sh index e215c7df3..3c130726f 100755 --- a/ci_scripts/ci_post_clone.sh +++ b/ci_scripts/ci_post_clone.sh @@ -2,7 +2,8 @@ source ci_common.sh -setup_xcode_cloud_environment +# Move to the project root +cd .. install_xcode_cloud_brew_dependencies diff --git a/ci_scripts/ci_post_xcodebuild.sh b/ci_scripts/ci_post_xcodebuild.sh index c98ed8712..bc6ace569 100755 --- a/ci_scripts/ci_post_xcodebuild.sh +++ b/ci_scripts/ci_post_xcodebuild.sh @@ -2,7 +2,8 @@ source ci_common.sh -setup_xcode_cloud_environment +# Move to the project root +cd .. # Xcode Cloud shallow clones the repo. We need to deepen it to fetch tags, commit history and be able to rebase main on develop at the end of releases. fetch_unshallow_repository @@ -14,8 +15,7 @@ swift run -q tools ci upload-dsyms --dsym-path "$CI_ARCHIVE_PATH/dSYMs" generate_what_to_test_notes if [ "$CI_WORKFLOW" = "Release" ]; then - bundle exec fastlane release_to_github - bundle exec fastlane prepare_next_release + swift run -q tools ci release-to-github elif [ "$CI_WORKFLOW" = "Nightly" ]; then swift run -q tools ci tag-nightly --build-number "$CI_BUILD_NUMBER" fi diff --git a/fastlane/Fastfile b/fastlane/Fastfile deleted file mode 100644 index ec10c59fe..000000000 --- a/fastlane/Fastfile +++ /dev/null @@ -1,123 +0,0 @@ -require 'yaml' - -skip_docs - -simulator_version = "26.1" - -before_all do - xcversion(version: "~> 26.1.0") - - ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "180" - ENV["FASTLANE_XCODE_LIST_TIMEOUT"] = "180" -end - -lane :release_to_github do - api_token = ENV["GITHUB_TOKEN"] - UI.user_error!("Invalid GitHub API token.") unless !api_token.to_s.empty? - - release_version = get_version_number(target: "ElementX") - - github_release = set_github_release( - repository_name: "element-hq/element-x-ios", - api_token: api_token, - name: release_version, - tag_name: "release/#{release_version}", - is_generate_release_notes: true, - ) - - release_date = Date.today.strftime("%Y-%m-%d") - generated_notes = github_release["body"].gsub(//, '').gsub("### ", "\n").gsub("## ", "### ") - UI.user_error!("The generated release notes are missing!") unless !generated_notes.to_s.empty? - - # Prepend the new release notes to the CHANGES.md file - changes_file = "../CHANGES.md" - File.open(changes_file, "r+") do |file| - content = file.read - file.rewind - file.write("## Changes in #{release_version} (#{release_date})#{generated_notes}\n\n#{content}") - end - - # The changelog will be committed when prepare_next_release is called. - sh("git add #{changes_file}") -end - -lane :prepare_next_release do - target_file_path = "../project.yml" - xcode_project_file_path = "../ElementX.xcodeproj" - - data = YAML.load_file target_file_path - current_version = data["settings"]["MARKETING_VERSION"] - - matches = current_version.match(/^(\d{2})\.(\d{2})\.(\d+)$/) - unless matches - UI.user_error!("Invalid version format: #{current_version}") - end - - year, month, build = matches.captures - new_build = build.to_i + 1 - new_version = "#{year}.#{month}.#{new_build}" - - # Bump the patch version. The empty string after -i is so that sed doesn't - # create a backup file on macOS - sh("sed -i '' 's/MARKETING_VERSION: #{current_version}/MARKETING_VERSION: #{new_version}/g' #{target_file_path}") - UI.message("Version updated from #{current_version} to #{new_version}") - - xcodegen(spec: "project.yml") - - setup_git() - - sh("git add #{target_file_path} #{xcode_project_file_path}") - - sh("git commit -m 'Prepare next release'") - - git_push() - - rebase_main_onto_current_branch() -end - -def rebase_main_onto_current_branch - # Capture the current branch name - current_branch = sh("git rev-parse --abbrev-ref HEAD").strip - - UI.message("Current branch: #{current_branch}") - - # Switch to main and update it - sh("git reset --hard") - sh("git checkout main") - sh("git pull origin main") - sh("git rebase #{current_branch}") - - git_push() - - UI.success("Successfully rebased main onto #{current_branch}") -end - -private_lane :setup_git do - sh("git config --global user.name 'Element CI'") - sh("git config --global user.email 'ci@element.io'") -end - -private_lane :git_push do |options| - # Use the Github API token for repo write access - api_token = ENV["GITHUB_TOKEN"] - UI.user_error!("Invalid GitHub API token.") unless !api_token.to_s.empty? - - # Get repo url path, without `http`, `https` or `git@` prefixes or `.git` suffix - repo_url = sh("git ls-remote --get-url origin | sed 's#http://##g' | sed 's#https:\/\/##g' | sed 's#git@##g' | sed 's#.git##g'") - - # This sometimes has a trailing newline - repo_url = repo_url.strip - - # Push the tag separately if available - if options[:tag_name] - sh("git push https://#{api_token}@#{repo_url} #{options[:tag_name]}") - end - - sh("git push https://#{api_token}@#{repo_url}") -end - -private_lane :bump_build_number do - # Increment build number to current date - build_number = Time.now.strftime("%Y%m%d%H%M") - increment_build_number(build_number: build_number) -end diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile deleted file mode 100644 index 012aa20c2..000000000 --- a/fastlane/Pluginfile +++ /dev/null @@ -1,12 +0,0 @@ -# Autogenerated by fastlane -# -# Ensure this file is checked in to source control! - -gem 'fastlane-plugin-xcodegen' -gem 'fastlane-plugin-xcconfig' - -# Until Fastlane includes them directly. -# https://github.com/fastlane/fastlane/issues/29183 -gem "abbrev" -gem "mutex_m" -gem "ostruct"