Compare commits

...

43 Commits

Author SHA1 Message Date
af23b63518 chore(release): 0.6.16 2021-10-10 18:44:41 +01:00
9c20f77fe3 Merge pull request #58 from jimeh/release-improvements 2021-10-10 18:42:37 +01:00
2e2f9bc98a feat(build): handle macOS Big Sur and later version number 2021-10-10 18:25:38 +01:00
d63cd545aa feat(cask): make cask template helpers more flexible
Helpers available in cask templates can now accept one or more strings
as input, and first asset filename that matches all given strings is
returned.
2021-10-10 18:25:38 +01:00
1bbfe5d3ea feat(plan): allow build plan to be output as YAML or JSON 2021-10-10 18:25:37 +01:00
b4c5184cef fix(release): publish arguments are not handled as asset files to upload 2021-10-10 18:25:37 +01:00
e7a991ef92 feat(release): force-replace existing asset files by default
The file-size check is obviously not a very reliable way to determine if
the local and remote files are different. Hence we now default to always
uploading all given asset files, replacing any existing ones with the
same file name.

The size check logic is still available via the --asset-size-check flag.
2021-10-10 18:25:37 +01:00
7118ed8560 feat(release): add description to GitHub Releases
The description includes links to the Emacs source repo used, the git
ref, commit, tarball download URL, and build log (GitHub Actions Run).
2021-10-10 18:25:36 +01:00
a71cbda511 chore(release): remove default release repository value
This should be explicitly provided via environment variable of command
line flag.
2021-10-10 18:25:36 +01:00
4c0eb37353 chore(debug): remove left-over debug print statement 2021-10-10 18:25:36 +01:00
b81e101ca7 style(lint): Renamed unused function arguments to _ 2021-10-10 18:25:35 +01:00
c760ffa25e Merge pull request #55 from puzza007/patch-1
Fix typo in log message
2021-08-30 10:11:58 +01:00
Paul Oliver
931e6ddf9e Fix typo in log message 2021-08-26 16:00:12 +12:00
f17f485b9f chore(release): 0.6.15 2021-08-05 02:38:15 +01:00
3622df550c fix(build): another --relink-eln-files flag fix
I probably should not be doing these kind of changes after midnight
as I get too lazy to test it as it takes like 20 minutes to do a build
locally :P
2021-08-05 02:37:01 +01:00
2a9707ec89 chore(release): 0.6.14 2021-08-05 01:20:13 +01:00
1fc7faac1f fix(build): silly typo 2021-08-05 01:19:39 +01:00
786d253df6 chore(plan): do not modify archive behavior based on plan disk image
This allows archive creation (or not) to still be controlled via the
--[no-]archive flag.
2021-08-05 00:21:17 +01:00
228ae0939c chore(release): 0.6.13 2021-08-04 23:59:58 +01:00
ac943c430c fix(native_comp): add option to enable/disable relinking *.eln files
Relinking the /usr/local/lib/gcc/10/libgcc_s.1.dylib shared library
within bundled *.eln files is still causing issues with code signing, so
I'm adding an option to toggle *.eln file re-linking on/off, with it on
by default.
2021-08-04 23:58:27 +01:00
bc3923c9ca chore(release): 0.6.12 2021-08-03 00:19:21 +01:00
e6b1e5a554 fix(sign): resolve signing issue caused by re-linking shared lib in *.eln files
When shared libs are stored in `Contents/Frameworks`, the re-link path
for `/usr/local/lib/gcc/11/libgcc_s.1.dylib` within bundled *.eln files
becomes `@executable_path/../Frameworks/libgcc_s.1.dylib`, which seems to
not leave enough space in the *.eln binary header to add a code
signature with codesign.

This used to work when shared libraries were bundled into
`Contents/MacOS/lib`, leading to a shorter relink path of
`@executable_path/lib/libgcc_s.1.dylib`, which does leave enough space
to add a code signature to *.eln files.
2021-08-03 00:13:10 +01:00
94625fce38 chore(release): 0.6.11 2021-07-17 13:08:10 +01:00
b03343f506 fix(native-comp): fix re-linking and signing issue with *.eln files
With the recent move of shared libraries and native lisp *.eln files to
Contents/Frameworks, the re-linking paths became longer, causing code
signing to fail with headerpad errors. This change ensures there's
enough space within binary files for longer shared library relink paths,
and code signing payloads.
2021-07-17 13:04:33 +01:00
b1896d4a4f chore(release): 0.6.10 2021-07-17 00:20:42 +01:00
9d32509c61 fix(native-comp): *.eln files were not being found during shared lib embedding 2021-07-16 21:00:24 +01:00
1c2745cd36 docs(readme): mention binary builds repo 2021-07-10 21:19:11 +01:00
80a0d55b24 chore(release): 0.6.9 2021-07-04 23:28:28 +01:00
fd0ec4d772 fix(plan): correctly parse --test-release-type flag
The --test-release-type flag was essentially broken and ignored, always
creating a prerelease release for test builds. Now it can also produce
draft releases.
2021-07-04 23:27:00 +01:00
c0c809a86a style(lint): fix linting complaint 2021-07-03 02:39:01 +01:00
cb63806262 feat(release): add bulk edit command to quickly change multiple GitHub releases 2021-07-03 02:36:15 +01:00
6d7ab95ca2 chore(release): 0.6.8 2021-07-03 00:19:26 +01:00
f4d6e3a56d feat(builds): add support for stable builds
Stable builds are based off of release git tags in Emacs' git
repo. Examples of what release tags look like:

- emacs-26.1
- emacs-26.2
- emacs-26.3
- emacs-27.1
- emacs-27.2

When the specified git ref looks like a stable release, the plan command
will generate a release a different and simpler release name that does
not include the date, git sha or ref. Instead, for "emacs-27.2" for
example, the emacs-builds release name will be "Emacs-27.2".

The "build name", used for naming the disk image, still retains the same
format as the nightly builds.

Also, non-stable releases are now marked as pre-release on GitHub by
default.

The reason for the different release name format for stable builds is
both to separate them, but also to make it easier to keep the version of
the homebrew cask as simply "27.2".
2021-07-03 00:11:59 +01:00
3f1059940d chore(release): 0.6.7 2021-07-02 16:50:25 +01:00
5c722e36c5 feat(bundle): move bundled shared libraries to Contents/Frameworks
The Frameworks folder is the recommended location to store shared
libraries within macOS application bundles. Previously we stored them in
Contents/MacOS/lib.

Latest nightly builds already store all *.eln files under the Frameworks
folder, so it seemed like a good time to make the change with the
library bundler/embedder too.
2021-07-02 16:49:32 +01:00
f52dd8dc6d chore(release): 0.6.6 2021-07-01 23:52:50 +01:00
4cdbaf1ec0 chore(cask): fix cask vs formula terminology
Homebrew casks are their own thing, separate from formulas. Hence remove
all mentions to "formula" to avoid confusion.
2021-07-01 23:50:50 +01:00
f3a289b11c chore(release): 0.6.5 2021-07-01 23:37:21 +01:00
9019e73d60 fix(native_comp): improve handling of *.eln files in .app bundle
Specifically support latest changes in master which places *.eln files
within the .app bundle in "Contents/Frameworks".
2021-07-01 23:33:50 +01:00
28930381a8 chore(release): 0.6.4 2021-06-30 10:41:34 +01:00
df25e54ef7 chore(sign): simplify *.eln locating logic
Instead of only checking very specific paths within the .app bundle,
just check the whole bundle for any and all *.eln files.
2021-06-30 10:20:22 +01:00
6d21d1bef4 chore(release): 0.6.3 2021-06-29 14:40:18 +01:00
99aa76b398 fix(patches): correctly set ref when loading a build plan YAML
The ref was not correctly set when loading a build plan, resulting in
the set of patches being selected were always for Emacs 28.x, preventing
builds of Emacs 27.x and 26.x.
2021-06-29 14:37:43 +01:00
23 changed files with 933 additions and 230 deletions

View File

@@ -75,6 +75,9 @@ issues:
- source: "`json:" - source: "`json:"
linters: linters:
- lll - lll
- source: "`yaml:"
linters:
- lll
run: run:
skip-dirs: skip-dirs:

View File

@@ -2,6 +2,108 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.6.16](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.15...v0.6.16) (2021-10-10)
### Features
* **build:** handle macOS Big Sur and later version number ([2e2f9bc](https://github.com/jimeh/build-emacs-for-macos/commit/2e2f9bc98acdc972a22add3d1015bd80cad20b41))
* **cask:** make cask template helpers more flexible ([d63cd54](https://github.com/jimeh/build-emacs-for-macos/commit/d63cd545aab3a35e0cbbbcabd862525acbc414b8))
* **plan:** allow build plan to be output as YAML or JSON ([1bbfe5d](https://github.com/jimeh/build-emacs-for-macos/commit/1bbfe5d3ea810215b417e5b80d2902f03d68f366))
* **release:** add description to GitHub Releases ([7118ed8](https://github.com/jimeh/build-emacs-for-macos/commit/7118ed856053de06ddcdfba2a2d6fa40f58c17ab))
* **release:** force-replace existing asset files by default ([e7a991e](https://github.com/jimeh/build-emacs-for-macos/commit/e7a991ef92a5c546106a8badaf9c60247a1397b5))
### Bug Fixes
* **release:** publish arguments are not handled as asset files to upload ([b4c5184](https://github.com/jimeh/build-emacs-for-macos/commit/b4c5184cefe43fdc54b1ad5c8a1970f104137644))
### [0.6.15](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.14...v0.6.15) (2021-08-05)
### Bug Fixes
* **build:** another --relink-eln-files flag fix ([3622df5](https://github.com/jimeh/build-emacs-for-macos/commit/3622df550c72fc9da70235005239b278b5822cf6))
### [0.6.14](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.13...v0.6.14) (2021-08-05)
### Bug Fixes
* **build:** silly typo ([1fc7faa](https://github.com/jimeh/build-emacs-for-macos/commit/1fc7faac1f040466fa4474a873d2290273780ee2))
### [0.6.13](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.12...v0.6.13) (2021-08-04)
### Bug Fixes
* **native_comp:** add option to enable/disable relinking *.eln files ([ac943c4](https://github.com/jimeh/build-emacs-for-macos/commit/ac943c430c58e0761ac44e8d25d4d55a461d01a2))
### [0.6.12](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.11...v0.6.12) (2021-08-02)
### Bug Fixes
* **sign:** resolve signing issue caused by re-linking shared lib in *.eln files ([e6b1e5a](https://github.com/jimeh/build-emacs-for-macos/commit/e6b1e5a554fd0f776bd01c17cfb1ebbbdf7a7831))
### [0.6.11](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.10...v0.6.11) (2021-07-17)
### Bug Fixes
* **native-comp:** fix re-linking and signing issue with *.eln files ([b03343f](https://github.com/jimeh/build-emacs-for-macos/commit/b03343f506aa3ceabdfa03f8a2916b2db4873f3f))
### [0.6.10](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.9...v0.6.10) (2021-07-16)
### Bug Fixes
* **native-comp:** *.eln files were not being found during shared lib embedding ([9d32509](https://github.com/jimeh/build-emacs-for-macos/commit/9d32509c615076618957cc47c82f6e9d8f972fe7))
### [0.6.9](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.8...v0.6.9) (2021-07-04)
### Features
* **release:** add bulk edit command to quickly change multiple GitHub releases ([cb63806](https://github.com/jimeh/build-emacs-for-macos/commit/cb638062625d9bc3eee12515067fb09e05a08414))
### Bug Fixes
* **plan:** correctly parse --test-release-type flag ([fd0ec4d](https://github.com/jimeh/build-emacs-for-macos/commit/fd0ec4d772dd3da93afc234fb3024220b2099c88))
### [0.6.8](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.7...v0.6.8) (2021-07-02)
### Features
* **builds:** add support for stable builds ([f4d6e3a](https://github.com/jimeh/build-emacs-for-macos/commit/f4d6e3a56d2c15b0c86af18e8d16bebbeb92a8ab))
### [0.6.7](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.6...v0.6.7) (2021-07-02)
### Features
* **bundle:** move bundled shared libraries to Contents/Frameworks ([5c722e3](https://github.com/jimeh/build-emacs-for-macos/commit/5c722e36c571aa7bf558b7f210c011f12d8d8a1c))
### [0.6.6](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.5...v0.6.6) (2021-07-01)
### [0.6.5](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.4...v0.6.5) (2021-07-01)
### Bug Fixes
* **native_comp:** improve handling of *.eln files in .app bundle ([9019e73](https://github.com/jimeh/build-emacs-for-macos/commit/9019e73d606f0379f988f46d6008770f8f3f7a51))
### [0.6.4](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.3...v0.6.4) (2021-06-30)
### [0.6.3](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.2...v0.6.3) (2021-06-29)
### Bug Fixes
* **patches:** correctly set ref when loading a build plan YAML ([99aa76b](https://github.com/jimeh/build-emacs-for-macos/commit/99aa76b3985195c310a20bafa19a8c7a4c8558fd))
### [0.6.2](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.1...v0.6.2) (2021-06-29) ### [0.6.2](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.1...v0.6.2) (2021-06-29)

View File

@@ -45,6 +45,11 @@ The build produced does have some limitations:
that built the application will yield warnings. If you want to make a signed that built the application will yield warnings. If you want to make a signed
Emacs.app, google is you friend for finding signing instructions. Emacs.app, google is you friend for finding signing instructions.
## Binary Builds
Nightly and stable binary builds produced with this build script are available
from [jimeh/emacs-builds](https://github.com/jimeh/emacs-builds).
## Requirements ## Requirements
- [Xcode](https://apps.apple.com/gb/app/xcode/id497799835?mt=12) - [Xcode](https://apps.apple.com/gb/app/xcode/id497799835?mt=12)

View File

@@ -11,6 +11,7 @@ require 'net/http'
require 'optparse' require 'optparse'
require 'pathname' require 'pathname'
require 'time' require 'time'
require 'tmpdir'
require 'uri' require 'uri'
require 'yaml' require 'yaml'
@@ -52,7 +53,7 @@ class OSVersion
end end
def to_s def to_s
@to_s ||= "#{major}.#{minor}" @to_s ||= major >= 11 ? major.to_s : "#{major}.#{minor}"
end end
def major def major
@@ -106,7 +107,7 @@ class Build
handle_native_lisp(app) handle_native_lisp(app)
add_cli_helper(app) add_cli_helper(app)
LibEmbedder.new(app, brew_dir, extra_libs).embed LibEmbedder.new(app, brew_dir, extra_libs, options[:relink_eln]).embed
GccLibEmbedder.new(app, gcc_info).embed if options[:native_comp] GccLibEmbedder.new(app, gcc_info).embed if options[:native_comp]
archive_build(build_dir) if options[:archive] archive_build(build_dir) if options[:archive]
@@ -117,9 +118,10 @@ class Build
def load_plan(filename) def load_plan(filename)
plan = YAML.safe_load(File.read(filename), [:Time]) plan = YAML.safe_load(File.read(filename), [:Time])
@ref = plan.dig('source', 'ref')
@meta = { @meta = {
sha: plan.dig('source', 'commit', 'sha'), sha: plan.dig('source', 'commit', 'sha'),
ref: plan.dig('source', 'ref'), ref: @ref,
date: plan.dig('source', 'commit', 'date') date: plan.dig('source', 'commit', 'date')
} }
@@ -131,10 +133,6 @@ class Build
@archive_filename = plan.dig('output', 'archive') @archive_filename = plan.dig('output', 'archive')
end end
if plan.dig('output', 'disk_image') || !plan.dig('output', 'archive')
options[:archive] = false
end
@build_name = plan.dig('build', 'name') if plan.dig('build', 'name') @build_name = plan.dig('build', 'name') if plan.dig('build', 'name')
end end
@@ -155,11 +153,22 @@ class Build
end end
def extra_libs def extra_libs
@extra_libs ||= [ return @extra_libs if @extra_libs
libs = [
File.join(brew_dir, 'opt/expat/lib/libexpat.1.dylib'), File.join(brew_dir, 'opt/expat/lib/libexpat.1.dylib'),
File.join(brew_dir, 'opt/libiconv/lib/libiconv.2.dylib'), File.join(brew_dir, 'opt/libiconv/lib/libiconv.2.dylib'),
File.join(brew_dir, 'opt/zlib/lib/libz.1.dylib') File.join(brew_dir, 'opt/zlib/lib/libz.1.dylib')
] ]
if options[:native_comp]
libgcc_s = File.join(
brew_dir, 'lib', 'gcc', gcc_info.major_version, 'libgcc_s.1.dylib'
)
libs << libgcc_s if File.exist?(libgcc_s)
end
@extra_libs = libs
end end
def download_tarball(sha) def download_tarball(sha)
@@ -292,6 +301,8 @@ class Build
"-L#{gcc_info.libgccjit_lib_dir}", "-L#{gcc_info.libgccjit_lib_dir}",
"-I#{File.join(gcc_info.root_dir, 'include')}", "-I#{File.join(gcc_info.root_dir, 'include')}",
"-I#{File.join(gcc_info.libgccjit_root_dir, 'include')}", "-I#{File.join(gcc_info.libgccjit_root_dir, 'include')}",
# Ensure library re-linking and code signing will work after building.
'-Wl,-headerpad_max_install_names',
ENV['LDFLAGS'] ENV['LDFLAGS']
].compact.join(' ') ].compact.join(' ')
@@ -411,20 +422,17 @@ class Build
contents_dir = File.join(app, 'Contents') contents_dir = File.join(app, 'Contents')
FileUtils.cd(contents_dir) do FileUtils.cd(contents_dir) do
# Skip creation of symlinks if *.eln files are located under
# Resources/native-lisp. Emacs is capable of finding lisp sources and
# *.eln cache files without symlinks.
return if Dir['Resources/native-lisp/**/*.eln'].any?
info 'Creating symlinks within Emacs.app needed for native-comp'
FileUtils.ln_s('Resources/lisp', 'lisp') unless File.exist?('lisp')
source = Dir['MacOS/libexec/emacs/**/eln-cache', source = Dir['MacOS/libexec/emacs/**/eln-cache',
'MacOS/lib/emacs/**/native-lisp'].first 'MacOS/lib/emacs/**/native-lisp'].first
if source.nil? # Skip creation of symlinks if *.eln files are not located in a location
err 'Failed to find native-lisp cache directory for symlink creation.' # known to be used by builds which need symlinks and other tweaks.
return if source.nil?
info 'Creating symlinks within Emacs.app needed for native-comp'
if !File.exist?('lisp') && File.exist?('Resources/lisp')
FileUtils.ln_s('Resources/lisp', 'lisp')
end end
# Check for folder name containing two dots (.), as this causes Apple's # Check for folder name containing two dots (.), as this causes Apple's
@@ -434,10 +442,9 @@ class Build
# The workaround for now is to rename the folder replacing the dots with # The workaround for now is to rename the folder replacing the dots with
# hyphens (-), and create the native-lisp symlink pointing to the new # hyphens (-), and create the native-lisp symlink pointing to the new
# location. # location.
if source.match(%r{/.+\..+\..+/}) eln_dir = File.dirname(Dir[File.join(source, '**', '*.eln')].first)
# Dig deeper into native-lisp directory
eln_dir = File.dirname(Dir[File.join(source, '**', '*.eln')].first)
if eln_dir.match(%r{/.+\..+\..+/})
base = File.basename(eln_dir) base = File.basename(eln_dir)
parent = File.dirname(eln_dir) parent = File.dirname(eln_dir)
@@ -544,7 +551,7 @@ class Build
system('tar', '-cjf', archive_filename, build) system('tar', '-cjf', archive_filename, build)
if options[:archive_keep] == false if options[:archive_keep] == false
info "Removeing \"#{build}\" directory from #{parent_dir}" info "Removing \"#{build}\" directory from #{parent_dir}"
FileUtils.rm_rf(build_dir) FileUtils.rm_rf(build_dir)
end end
end end
@@ -738,8 +745,6 @@ class AbstractEmbedder
@app = app @app = app
end end
private
def invocation_dir def invocation_dir
File.join(app, 'Contents', 'MacOS') File.join(app, 'Contents', 'MacOS')
end end
@@ -756,12 +761,14 @@ end
class LibEmbedder < AbstractEmbedder class LibEmbedder < AbstractEmbedder
attr_reader :lib_source attr_reader :lib_source
attr_reader :extra_libs attr_reader :extra_libs
attr_reader :embed_eln_files
def initialize(app, lib_source, extra_libs = []) def initialize(app, lib_source, extra_libs = [], embed_eln_files = true)
super(app) super(app)
@lib_source = lib_source @lib_source = lib_source
@extra_libs = extra_libs @extra_libs = extra_libs
@embed_eln_files = embed_eln_files
end end
def embed def embed
@@ -779,7 +786,7 @@ class LibEmbedder < AbstractEmbedder
rel_path = Pathname.new(lib_dir).relative_path_from( rel_path = Pathname.new(lib_dir).relative_path_from(
Pathname.new(File.dirname(binary)) Pathname.new(File.dirname(binary))
).to_s ).to_s
eln_files.each { |f| copy_libs(f, rel_path) } eln_files.each { |f| copy_libs(f, rel_path) } if embed_eln_files
end end
end end
end end
@@ -787,19 +794,7 @@ class LibEmbedder < AbstractEmbedder
private private
def eln_files def eln_files
@eln_files ||= Dir[ @eln_files ||= Dir[File.join(app, 'Contents', '**', '*.eln')]
File.join(
app, 'Contents', 'Resources', 'native-lisp', '**', '*.eln'
),
File.join(
app, 'Contents', 'MacOS', 'lib', 'emacs', '**',
'native-lisp', '**', '*.eln'
),
File.join(
app, 'Contents', 'MacOS', 'libexec', 'emacs', '**',
'eln-cache', '**', '*.eln'
)
]
end end
def copy_libs(exe, rel_path = nil) def copy_libs(exe, rel_path = nil)
@@ -905,15 +900,15 @@ class GccLibEmbedder < AbstractEmbedder
end end
def target_dir def target_dir
File.join(invocation_dir, gcc_info.relative_lib_dir) File.join(lib_dir, gcc_info.relative_lib_dir)
end end
def source_darwin_dir def source_darwin_dir
File.join(invocation_dir, gcc_info.relative_darwin_lib_dir) File.join(lib_dir, gcc_info.relative_darwin_lib_dir)
end end
def target_darwin_dir def target_darwin_dir
File.join(invocation_dir, gcc_info.sanitized_relative_darwin_lib_dir) File.join(lib_dir, gcc_info.sanitized_relative_darwin_lib_dir)
end end
def source_dir def source_dir
@@ -969,7 +964,21 @@ class GccInfo
def sanitized_relative_darwin_lib_dir def sanitized_relative_darwin_lib_dir
@sanitized_relative_darwin_lib_dir ||= File.join( @sanitized_relative_darwin_lib_dir ||= File.join(
File.dirname(relative_darwin_lib_dir), File.dirname(relative_darwin_lib_dir),
File.basename(relative_darwin_lib_dir).split('.').first File.basename(relative_darwin_lib_dir).gsub('.', '_')
)
end
def app_bundle_relative_lib_dir
@app_bundle_relative_lib_dir ||= relative_dir(
File.join(embedder.lib_dir, relative_lib_dir),
embedder.invocation_dir
)
end
def app_bundle_relative_darwin_lib_dir
@app_bundle_relative_darwin_lib_dir ||= relative_dir(
File.join(embedder.lib_dir, sanitized_relative_darwin_lib_dir),
embedder.invocation_dir
) )
end end
@@ -1016,6 +1025,10 @@ class GccInfo
private private
def embedder
@embedder ||= AbstractEmbedder.new(Dir.mktmpdir(['Emacs', '.app']))
end
def relative_dir(path, root) def relative_dir(path, root)
Pathname.new(path).relative_path_from(Pathname.new(root)).to_s Pathname.new(path).relative_path_from(Pathname.new(root)).to_s
end end
@@ -1025,6 +1038,7 @@ if __FILE__ == $PROGRAM_NAME
cli_options = { cli_options = {
work_dir: File.expand_path(__dir__), work_dir: File.expand_path(__dir__),
native_full_aot: false, native_full_aot: false,
relink_eln: true,
native_march: false, native_march: false,
parallel: Etc.nprocessors, parallel: Etc.nprocessors,
rsvg: true, rsvg: true,
@@ -1082,6 +1096,12 @@ if __FILE__ == $PROGRAM_NAME
cli_options[:native_full_aot] = v cli_options[:native_full_aot] = v
end end
opts.on('--[no-]relink-eln-files',
'Enable/disable re-linking shared libraries in bundled *.eln ' \
'files (default: enabled)') do |v|
cli_options[:relink_eln] = v
end
opts.on('--[no-]rsvg', opts.on('--[no-]rsvg',
'Enable/disable SVG image support via librsvg ' \ 'Enable/disable SVG image support via librsvg ' \
'(default: enabled)') do |v| '(default: enabled)') do |v|

View File

@@ -1,8 +1,8 @@
diff --git a/lisp/emacs-lisp/comp.el b/lisp/emacs-lisp/comp.el diff --git a/lisp/emacs-lisp/comp.el b/lisp/emacs-lisp/comp.el
index 8c638312b0..87af889ef4 100644 index 638d4b274c..2599211936 100644
--- a/lisp/emacs-lisp/comp.el --- a/lisp/emacs-lisp/comp.el
+++ b/lisp/emacs-lisp/comp.el +++ b/lisp/emacs-lisp/comp.el
@@ -4215,6 +4215,52 @@ native-compile-async @@ -4224,6 +4224,52 @@ native-compile-async
(let ((load (not (not load)))) (let ((load (not (not load))))
(native--compile-async files recursively load selector))) (native--compile-async files recursively load selector)))
@@ -16,10 +16,10 @@ index 8c638312b0..87af889ef4 100644
+ (devtools-dir + (devtools-dir
+ "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib") + "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib")
+ (gcc-dir (expand-file-name + (gcc-dir (expand-file-name
+ "<%= relative_lib_dir %>" + "<%= app_bundle_relative_lib_dir %>"
+ invocation-directory)) + invocation-directory))
+ (darwin-dir (expand-file-name + (darwin-dir (expand-file-name
+ "<%= sanitized_relative_darwin_lib_dir %>" + "<%= app_bundle_relative_darwin_lib_dir %>"
+ invocation-directory)) + invocation-directory))
+ (lib-paths (list))) + (lib-paths (list)))
+ +

View File

@@ -11,9 +11,11 @@ type ReleaseInfo struct {
Assets map[string]*ReleaseAsset Assets map[string]*ReleaseAsset
} }
func (s *ReleaseInfo) Asset(nameMatch string) *ReleaseAsset { func (s *ReleaseInfo) Asset(needles ...string) *ReleaseAsset {
if a, ok := s.Assets[nameMatch]; ok { if len(needles) == 1 {
return a if a, ok := s.Assets[needles[0]]; ok {
return a
}
} }
// Dirty and inefficient way to ensure assets are searched in a predictable // Dirty and inefficient way to ensure assets are searched in a predictable
@@ -27,16 +29,20 @@ func (s *ReleaseInfo) Asset(nameMatch string) *ReleaseAsset {
}) })
for _, a := range assets { for _, a := range assets {
if strings.Contains(a.Filename, nameMatch) { for _, needle := range needles {
return a if !strings.Contains(a.Filename, needle) {
continue
}
} }
return a
} }
return nil return nil
} }
func (s *ReleaseInfo) DownloadURL(nameMatch string) string { func (s *ReleaseInfo) DownloadURL(needles ...string) string {
a := s.Asset(nameMatch) a := s.Asset(needles...)
if a == nil { if a == nil {
return "" return ""
} }
@@ -44,8 +50,8 @@ func (s *ReleaseInfo) DownloadURL(nameMatch string) string {
return a.DownloadURL return a.DownloadURL
} }
func (s *ReleaseInfo) SHA256(nameMatch string) string { func (s *ReleaseInfo) SHA256(needles ...string) string {
a := s.Asset(nameMatch) a := s.Asset(needles...)
if a == nil { if a == nil {
return "" return ""
} }

View File

@@ -19,6 +19,7 @@ import (
"github.com/hexops/gotextdiff/myers" "github.com/hexops/gotextdiff/myers"
"github.com/hexops/gotextdiff/span" "github.com/hexops/gotextdiff/span"
"github.com/jimeh/build-emacs-for-macos/pkg/gh" "github.com/jimeh/build-emacs-for-macos/pkg/gh"
"github.com/jimeh/build-emacs-for-macos/pkg/release"
"github.com/jimeh/build-emacs-for-macos/pkg/repository" "github.com/jimeh/build-emacs-for-macos/pkg/repository"
) )
@@ -42,11 +43,11 @@ type UpdateOptions struct {
// BuildsRepo is the GitHub repository containing binary releases. // BuildsRepo is the GitHub repository containing binary releases.
BuildsRepo *repository.Repository BuildsRepo *repository.Repository
// TapRepo is the GitHub repository to update the cask formula in. // TapRepo is the GitHub repository to update the casks in.
TapRepo *repository.Repository TapRepo *repository.Repository
// Ref is the git ref to apply cask formula updates on top of. Default // Ref is the git ref to apply cask updates on top of. Default branch will
// branch will be used if empty. // be used if empty.
Ref string Ref string
// OutputDir specifies a directory to write cask files to. When set, tap // OutputDir specifies a directory to write cask files to. When set, tap
@@ -56,10 +57,10 @@ type UpdateOptions struct {
// Force update will ignore the outdated live check flag, and process all // Force update will ignore the outdated live check flag, and process all
// casks regardless. But it will only update the cask in question if the // casks regardless. But it will only update the cask in question if the
// resulting output cask formula is different. // resulting output cask is different.
Force bool Force bool
// TemplatesDir is the directory where cask formula templates are located. // TemplatesDir is the directory where cask templates are located.
TemplatesDir string TemplatesDir string
LiveChecks []*LiveCheck LiveChecks []*LiveCheck
@@ -150,7 +151,7 @@ func (s *Updater) putFile(
content []byte, content []byte,
) (bool, error) { ) (bool, error) {
parent := filepath.Dir(filename) parent := filepath.Dir(filename)
s.logger.Info("processing formula update", s.logger.Info("processing cask update",
"output-directory", parent, "cask", chk.Cask, "file", filename, "output-directory", parent, "cask", chk.Cask, "file", filename,
) )
@@ -164,18 +165,18 @@ func (s *Updater) putFile(
return false, err return false, err
} }
infoMsg := "creating formula" infoMsg := "creating cask"
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
infoMsg = "updating formula" infoMsg = "updating cask"
if bytes.Equal(existingContent, content) { if bytes.Equal(existingContent, content) {
s.logger.Info( s.logger.Info(
"skip update: no change to cask formula content", "skip update: no change to cask content",
"cask", chk.Cask, "file", filename, "cask", chk.Cask, "file", filename,
) )
s.logger.Debug( s.logger.Debug(
"formula content", "cask content",
"file", filename, "content", string(content), "file", filename, "content", string(content),
) )
@@ -198,7 +199,7 @@ func (s *Updater) putFile(
) )
s.logger.Debug( s.logger.Debug(
"formula content", "cask content",
"file", filename, "content", string(content), "file", filename, "content", string(content),
) )
@@ -218,7 +219,7 @@ func (s *Updater) putRepoFile(
filename string, filename string,
content []byte, content []byte,
) (bool, error) { ) (bool, error) {
s.logger.Info("processing formula update", s.logger.Info("processing cask update",
"tap-repo", repo.Source, "cask", chk.Cask, "file", filename, "tap-repo", repo.Source, "cask", chk.Cask, "file", filename,
) )
repoContent, _, resp, err := s.gh.Repositories.GetContents( repoContent, _, resp, err := s.gh.Repositories.GetContents(
@@ -264,12 +265,12 @@ func (s *Updater) createRepoFile(
diff := fmt.Sprint(gotextdiff.ToUnified(filename, filename, "", edits)) diff := fmt.Sprint(gotextdiff.ToUnified(filename, filename, "", edits))
s.logger.Info( s.logger.Info(
"creating formula", "creating cask",
"cask", chk.Cask, "version", chk.Version.Latest, "file", filename, "cask", chk.Cask, "version", chk.Version.Latest, "file", filename,
"diff", diff, "diff", diff,
) )
s.logger.Debug( s.logger.Debug(
"formula content", "cask content",
"file", filename, "content", string(content), "file", filename, "content", string(content),
) )
contResp, _, err := s.gh.Repositories.CreateFile( contResp, _, err := s.gh.Repositories.CreateFile(
@@ -307,7 +308,7 @@ func (s *Updater) updateRepoFile(
if existingContent == string(content) { if existingContent == string(content) {
s.logger.Info( s.logger.Info(
"skip update: no change to formula content", "skip update: no change to cask content",
"cask", chk.Cask, "file", filename, "cask", chk.Cask, "file", filename,
) )
@@ -329,12 +330,12 @@ func (s *Updater) updateRepoFile(
)) ))
s.logger.Info( s.logger.Info(
"updating formula", "updating cask",
"cask", chk.Cask, "version", chk.Version.Latest, "file", filename, "cask", chk.Cask, "version", chk.Version.Latest, "file", filename,
"diff", diff, "diff", diff,
) )
s.logger.Debug( s.logger.Debug(
"formula content", "cask content",
"file", filename, "content", string(content), "file", filename, "content", string(content),
) )
@@ -363,9 +364,14 @@ func (s *Updater) renderCask(
ctx context.Context, ctx context.Context,
chk *LiveCheck, chk *LiveCheck,
) ([]byte, error) { ) ([]byte, error) {
releaseName := "Emacs." + chk.Version.Latest releaseName, err := release.VersionToName(chk.Version.Latest)
if err != nil {
return nil, err
}
s.logger.Info("fetching release details", "release", releaseName) s.logger.Info("fetching release details",
"release", releaseName, "repo", s.BuildsRepo.URL(),
)
release, resp, err := s.gh.Repositories.GetReleaseByTag( release, resp, err := s.gh.Repositories.GetReleaseByTag(
ctx, s.BuildsRepo.Owner(), s.BuildsRepo.Name(), releaseName, ctx, s.BuildsRepo.Owner(), s.BuildsRepo.Name(), releaseName,
) )

View File

@@ -23,7 +23,7 @@ func caskCmd() *cli2.Command {
return &cli2.Command{ return &cli2.Command{
Name: "cask", Name: "cask",
Usage: "manage Homebrew Cask formula", Usage: "manage Homebrew Casks",
Flags: []cli2.Flag{ Flags: []cli2.Flag{
&cli2.StringFlag{ &cli2.StringFlag{
Name: "builds-repository", Name: "builds-repository",
@@ -101,7 +101,7 @@ func caskUpdateCmd() *cli2.Command {
Name: "force", Name: "force",
Aliases: []string{"f"}, Aliases: []string{"f"},
Usage: "force update file even if livecheck has it marked " + Usage: "force update file even if livecheck has it marked " +
"as not outdated (does not force update if formula " + "as not outdated (does not force update if cask " +
"content is unchanged)", "content is unchanged)",
Value: false, Value: false,
}, },
@@ -112,7 +112,7 @@ func caskUpdateCmd() *cli2.Command {
func caskUpdateAction( func caskUpdateAction(
c *cli2.Context, c *cli2.Context,
opts *Options, _ *Options,
cOpts *caskOptions, cOpts *caskOptions,
) error { ) error {
updateOpts := &cask.UpdateOptions{ updateOpts := &cask.UpdateOptions{

View File

@@ -52,7 +52,7 @@ func notarizeCmd() *cli2.Command {
} }
} }
func notarizeAction(c *cli2.Context, opts *Options) error { func notarizeAction(c *cli2.Context, _ *Options) error {
options := &notarize.Options{ options := &notarize.Options{
File: c.Args().Get(0), File: c.Args().Get(0),
BundleID: c.String("bundle-id"), BundleID: c.String("bundle-id"),

View File

@@ -1,6 +1,7 @@
package cli package cli
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
@@ -37,6 +38,12 @@ func planCmd() *cli2.Command {
Name: "sha", Name: "sha",
Usage: "override commit SHA of specified git branch/tag", Usage: "override commit SHA of specified git branch/tag",
}, },
&cli2.StringFlag{
Name: "format",
Aliases: []string{"f"},
Usage: "output format of build plan (yaml or json)",
Value: "yaml",
},
&cli2.StringFlag{ &cli2.StringFlag{
Name: "output", Name: "output",
Usage: "output filename to write plan to instead of printing " + Usage: "output filename to write plan to instead of printing " +
@@ -89,7 +96,7 @@ func planAction(c *cli2.Context, opts *Options) error {
GithubToken: c.String("github-token"), GithubToken: c.String("github-token"),
} }
if c.String("test-build-type") == "draft" { if c.String("test-release-type") == "draft" {
planOpts.TestBuildType = plan.Draft planOpts.TestBuildType = plan.Draft
} }
@@ -102,7 +109,18 @@ func planAction(c *cli2.Context, opts *Options) error {
return err return err
} }
planYAML, err := p.YAML() format := c.String("format")
var plan string
switch format {
case "yaml", "yml":
format = "yaml"
plan, err = p.YAML()
case "json":
format = "json"
plan, err = p.JSON()
default:
err = fmt.Errorf("--format must be yaml or json")
}
if err != nil { if err != nil {
return err return err
} }
@@ -111,7 +129,7 @@ func planAction(c *cli2.Context, opts *Options) error {
out = os.Stdout out = os.Stdout
if f := c.String("output"); f != "" { if f := c.String("output"); f != "" {
logger.Info("writing plan", "file", f) logger.Info("writing plan", "file", f)
logger.Debug("content", "yaml", planYAML) logger.Debug("content", format, plan)
out, err = os.Create(f) out, err = os.Create(f)
if err != nil { if err != nil {
return err return err
@@ -119,7 +137,7 @@ func planAction(c *cli2.Context, opts *Options) error {
defer out.Close() defer out.Close()
} }
_, err = out.WriteString(planYAML) _, err = out.WriteString(plan)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -1,6 +1,7 @@
package cli package cli
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@@ -42,7 +43,7 @@ func releaseCmd() *cli2.Command {
Usage: "owner/name of GitHub repo to check for release, " + Usage: "owner/name of GitHub repo to check for release, " +
"ignored if a plan is provided", "ignored if a plan is provided",
EnvVars: []string{"GITHUB_REPOSITORY"}, EnvVars: []string{"GITHUB_REPOSITORY"},
Value: "jimeh/emacs-builds", Value: "",
}, },
&cli2.StringFlag{ &cli2.StringFlag{
Name: "name", Name: "name",
@@ -60,6 +61,7 @@ func releaseCmd() *cli2.Command {
Subcommands: []*cli2.Command{ Subcommands: []*cli2.Command{
releaseCheckCmd(), releaseCheckCmd(),
releasePublishCmd(), releasePublishCmd(),
releaseBulkCmd(),
}, },
} }
} }
@@ -106,7 +108,7 @@ func releaseCheckCmd() *cli2.Command {
func releaseCheckAction( func releaseCheckAction(
c *cli2.Context, c *cli2.Context,
opts *Options, _ *Options,
rOpts *releaseOptions, rOpts *releaseOptions,
) error { ) error {
rlsOpts := &release.CheckOptions{ rlsOpts := &release.CheckOptions{
@@ -151,6 +153,12 @@ func releasePublishCmd() *cli2.Command {
"specified", "specified",
Value: "", Value: "",
}, },
&cli2.BoolFlag{
Name: "asset-size-check",
Usage: "Do not replace existing asset files if local and " +
"remote file sizes match.",
Value: false,
},
}, },
Action: releaseActionWrapper(releasePublishAction), Action: releaseActionWrapper(releasePublishAction),
} }
@@ -158,16 +166,17 @@ func releasePublishCmd() *cli2.Command {
func releasePublishAction( func releasePublishAction(
c *cli2.Context, c *cli2.Context,
opts *Options, _ *Options,
rOpts *releaseOptions, rOpts *releaseOptions,
) error { ) error {
rlsOpts := &release.PublishOptions{ rlsOpts := &release.PublishOptions{
Repository: rOpts.Repository, Repository: rOpts.Repository,
CommitRef: c.String("release-sha"), CommitRef: c.String("release-sha"),
ReleaseName: rOpts.Name, ReleaseName: rOpts.Name,
ReleaseTitle: c.String("title"), ReleaseTitle: c.String("title"),
AssetFiles: c.Args().Slice(), AssetFiles: c.Args().Slice(),
GithubToken: rOpts.GithubToken, AssetSizeCheck: c.Bool("asset-size-check"),
GithubToken: rOpts.GithubToken,
} }
rlsType := c.String("type") rlsType := c.String("type")
@@ -182,7 +191,13 @@ func releasePublishAction(
return fmt.Errorf("invalid --type \"%s\"", rlsType) return fmt.Errorf("invalid --type \"%s\"", rlsType)
} }
if c.Args().Len() > 0 {
rlsOpts.AssetFiles = c.Args().Slice()
}
if rOpts.Plan != nil { if rOpts.Plan != nil {
rlsOpts.Source = rOpts.Plan.Source
if rOpts.Plan.Release != nil { if rOpts.Plan.Release != nil {
rlsOpts.ReleaseName = rOpts.Plan.Release.Name rlsOpts.ReleaseName = rOpts.Plan.Release.Name
rlsOpts.ReleaseTitle = rOpts.Plan.Release.Title rlsOpts.ReleaseTitle = rOpts.Plan.Release.Title
@@ -194,7 +209,8 @@ func releasePublishAction(
} }
} }
if rOpts.Plan.Output != nil { // Set asset files based on plan if no file arguments were given.
if len(rlsOpts.AssetFiles) == 0 && rOpts.Plan.Output != nil {
rlsOpts.AssetFiles = []string{ rlsOpts.AssetFiles = []string{
filepath.Join( filepath.Join(
rOpts.Plan.Output.Directory, rOpts.Plan.Output.Directory,
@@ -206,3 +222,56 @@ func releasePublishAction(
return release.Publish(c.Context, rlsOpts) return release.Publish(c.Context, rlsOpts)
} }
func releaseBulkCmd() *cli2.Command {
return &cli2.Command{
Name: "bulk",
Usage: "bulk modify GitHub releases",
ArgsUsage: "",
Flags: []cli2.Flag{
&cli2.StringFlag{
Name: "name",
Usage: "regexp pattern matching release names to modify",
},
&cli2.StringFlag{
Name: "prerelease",
Usage: "change prerelease flag, must be \"true\" or " +
"\"false\", otherwise prerelease value is not changed",
},
&cli2.BoolFlag{
Name: "dry-run",
Usage: "do not perform any changes",
},
},
Action: releaseActionWrapper(releaseBulkAction),
}
}
func releaseBulkAction(
c *cli2.Context,
_ *Options,
rOpts *releaseOptions,
) error {
bulkOpts := &release.BulkOptions{
Repository: rOpts.Repository,
NamePattern: c.String("name"),
DryRun: c.Bool("dry-run"),
GithubToken: rOpts.GithubToken,
}
switch c.String("prerelease") {
case "true":
v := true
bulkOpts.Prerelease = &v
case "false":
v := false
bulkOpts.Prerelease = &v
case "":
default:
return errors.New(
"--prerelease by me \"true\" or \"false\" when specified",
)
}
return release.Bulk(c.Context, bulkOpts)
}

View File

@@ -8,11 +8,11 @@ import (
) )
type Commit struct { type Commit struct {
SHA string `yaml:"sha"` SHA string `yaml:"sha" json:"sha"`
Date *time.Time `yaml:"date"` Date *time.Time `yaml:"date" json:"date"`
Author string `yaml:"author"` Author string `yaml:"author" json:"author"`
Committer string `yaml:"committer"` Committer string `yaml:"committer" json:"committer"`
Message string `yaml:"message"` Message string `yaml:"message" json:"message"`
} }
func New(rc *github.RepositoryCommit) *Commit { func New(rc *github.RepositoryCommit) *Commit {

View File

@@ -2,13 +2,14 @@ package osinfo
import ( import (
"os/exec" "os/exec"
"strconv"
"strings" "strings"
) )
type OSInfo struct { type OSInfo struct {
Name string `yaml:"name"` Name string `yaml:"name" json:"name"`
Version string `yaml:"version"` Version string `yaml:"version" json:"version"`
Arch string `yaml:"arch"` Arch string `yaml:"arch" json:"arch"`
} }
func New() (*OSInfo, error) { func New() (*OSInfo, error) {
@@ -29,8 +30,17 @@ func New() (*OSInfo, error) {
}, nil }, nil
} }
func (s *OSInfo) MajorMinor() string { // DistinctVersion returns macOS version down to a distinct "major"
// version. For macOS 10.x, this will include the first two numeric parts of the
// version (10.15), while for 11.x and later, the first numeric part is enough
// (11).
func (s *OSInfo) DistinctVersion() string {
parts := strings.Split(s.Version, ".") parts := strings.Split(s.Version, ".")
if n, _ := strconv.Atoi(parts[0]); n >= 11 {
return parts[0]
}
max := len(parts) max := len(parts)
if max > 2 { if max > 2 {
max = 2 max = 2

View File

@@ -11,7 +11,9 @@ import (
"github.com/jimeh/build-emacs-for-macos/pkg/commit" "github.com/jimeh/build-emacs-for-macos/pkg/commit"
"github.com/jimeh/build-emacs-for-macos/pkg/gh" "github.com/jimeh/build-emacs-for-macos/pkg/gh"
"github.com/jimeh/build-emacs-for-macos/pkg/osinfo" "github.com/jimeh/build-emacs-for-macos/pkg/osinfo"
"github.com/jimeh/build-emacs-for-macos/pkg/release"
"github.com/jimeh/build-emacs-for-macos/pkg/repository" "github.com/jimeh/build-emacs-for-macos/pkg/repository"
"github.com/jimeh/build-emacs-for-macos/pkg/source"
) )
var nonAlphaNum = regexp.MustCompile(`[^\w_-]+`) var nonAlphaNum = regexp.MustCompile(`[^\w_-]+`)
@@ -64,16 +66,18 @@ func Create(ctx context.Context, opts *Options) (*Plan, error) {
return nil, err return nil, err
} }
releaseName := fmt.Sprintf( version := fmt.Sprintf(
"Emacs.%s.%s.%s", "%s.%s.%s",
commitInfo.DateString(), commitInfo.DateString(),
commitInfo.ShortSHA(), commitInfo.ShortSHA(),
sanitizeString(opts.Ref), sanitizeString(opts.Ref),
) )
releaseName := fmt.Sprintf("Emacs.%s", version)
buildName := fmt.Sprintf( buildName := fmt.Sprintf(
"%s.%s.%s", "Emacs.%s.%s.%s",
releaseName, version,
sanitizeString(osInfo.Name+"-"+osInfo.MajorMinor()), sanitizeString(osInfo.Name+"-"+osInfo.DistinctVersion()),
sanitizeString(osInfo.Arch), sanitizeString(osInfo.Arch),
) )
diskImage := buildName + ".dmg" diskImage := buildName + ".dmg"
@@ -82,17 +86,18 @@ func Create(ctx context.Context, opts *Options) (*Plan, error) {
Build: &Build{ Build: &Build{
Name: buildName, Name: buildName,
}, },
Source: &Source{ Source: &source.Source{
Ref: opts.Ref, Ref: opts.Ref,
Repository: repo, Repository: repo,
Commit: commitInfo, Commit: commitInfo,
Tarball: &Tarball{ Tarball: &source.Tarball{
URL: repo.TarballURL(commitInfo.SHA), URL: repo.TarballURL(commitInfo.SHA),
}, },
}, },
OS: osInfo, OS: osInfo,
Release: &Release{ Release: &Release{
Name: releaseName, Name: releaseName,
Prerelease: true,
}, },
Output: &Output{ Output: &Output{
Directory: opts.OutputDir, Directory: opts.OutputDir,
@@ -100,16 +105,28 @@ func Create(ctx context.Context, opts *Options) (*Plan, error) {
}, },
} }
// If given git ref is a stable release tag (emacs-23.2b, emacs-27.2, etc.)
// we modify release properties accordingly.
if v, err := release.GitRefToStableVersion(opts.Ref); err == nil {
plan.Release.Prerelease = false
plan.Release.Name, err = release.VersionToName(v)
if err != nil {
return nil, err
}
}
if opts.TestBuild != "" { if opts.TestBuild != "" {
testName := sanitizeString(opts.TestBuild) testName := sanitizeString(opts.TestBuild)
plan.Build.Name += ".test." + testName plan.Build.Name += ".test." + testName
plan.Release.Title = "Test Builds" plan.Release.Title = "Test Builds"
plan.Release.Name = "test-builds" plan.Release.Name = "test-builds"
plan.Release.Prerelease = true
plan.Release.Draft = false
if opts.TestBuildType == Draft { if opts.TestBuildType == Draft {
plan.Release.Prerelease = false
plan.Release.Draft = true plan.Release.Draft = true
} else {
plan.Release.Prerelease = true
} }
index := strings.LastIndex(diskImage, ".") index := strings.LastIndex(diskImage, ".")

View File

@@ -2,21 +2,21 @@ package plan
import ( import (
"bytes" "bytes"
"encoding/json"
"io" "io"
"os" "os"
"github.com/jimeh/build-emacs-for-macos/pkg/commit"
"github.com/jimeh/build-emacs-for-macos/pkg/osinfo" "github.com/jimeh/build-emacs-for-macos/pkg/osinfo"
"github.com/jimeh/build-emacs-for-macos/pkg/repository" "github.com/jimeh/build-emacs-for-macos/pkg/source"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type Plan struct { type Plan struct {
Build *Build `yaml:"build,omitempty"` Build *Build `yaml:"build,omitempty" json:"build,omitempty"`
Source *Source `yaml:"source,omitempty"` Source *source.Source `yaml:"source,omitempty" json:"source,omitempty"`
OS *osinfo.OSInfo `yaml:"os,omitempty"` OS *osinfo.OSInfo `yaml:"os,omitempty" json:"os,omitempty"`
Release *Release `yaml:"release,omitempty"` Release *Release `yaml:"release,omitempty" json:"release,omitempty"`
Output *Output `yaml:"output,omitempty"` Output *Output `yaml:"output,omitempty" json:"output,omitempty"`
} }
// Load attempts to loads a plan YAML from given filename. // Load attempts to loads a plan YAML from given filename.
@@ -54,29 +54,37 @@ func (s *Plan) YAML() (string, error) {
return buf.String(), nil return buf.String(), nil
} }
// WriteJSON writes plan in JSON format to given io.Writer.
func (s *Plan) WriteJSON(w io.Writer) error {
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
return enc.Encode(s)
}
// JSON returns plan in JSON format.
func (s *Plan) JSON() (string, error) {
var buf bytes.Buffer
err := s.WriteJSON(&buf)
if err != nil {
return "", err
}
return buf.String(), nil
}
type Build struct { type Build struct {
Name string `yaml:"name,omitempty"` Name string `yaml:"name,omitempty" json:"name,omitempty"`
}
type Source struct {
Ref string `yaml:"ref,omitempty"`
Repository *repository.Repository `yaml:"repository,omitempty"`
Commit *commit.Commit `yaml:"commit,omitempty"`
Tarball *Tarball `yaml:"tarball,omitempty"`
}
type Tarball struct {
URL string `yaml:"url,omitempty"`
} }
type Release struct { type Release struct {
Name string `yaml:"name"` Name string `yaml:"name" json:"name"`
Title string `yaml:"title,omitempty"` Title string `yaml:"title,omitempty" json:"title,omitempty"`
Draft bool `yaml:"draft,omitempty"` Draft bool `yaml:"draft,omitempty" json:"draft,omitempty"`
Prerelease bool `yaml:"prerelease,omitempty"` Prerelease bool `yaml:"prerelease,omitempty" json:"prerelease,omitempty"`
} }
type Output struct { type Output struct {
Directory string `yaml:"directory,omitempty"` Directory string `yaml:"directory,omitempty" json:"directory,omitempty"`
DiskImage string `yaml:"disk_image,omitempty"` DiskImage string `yaml:"disk_image,omitempty" json:"disk_image,omitempty"`
} }

84
pkg/release/bulk.go Normal file
View File

@@ -0,0 +1,84 @@
package release
import (
"context"
"regexp"
"github.com/google/go-github/v35/github"
"github.com/hashicorp/go-hclog"
"github.com/jimeh/build-emacs-for-macos/pkg/gh"
"github.com/jimeh/build-emacs-for-macos/pkg/repository"
)
type BulkOptions struct {
Repository *repository.Repository
NamePattern string
Prerelease *bool
DryRun bool
GithubToken string
}
func Bulk(ctx context.Context, opts *BulkOptions) error {
logger := hclog.FromContext(ctx).Named("release")
gh := gh.New(ctx, opts.GithubToken)
nameMatcher, err := regexp.Compile(opts.NamePattern)
if err != nil {
return err
}
nextPage := 1
lastPage := 1
for nextPage <= lastPage {
releases, resp, err := gh.Repositories.ListReleases(
ctx, opts.Repository.Owner(), opts.Repository.Name(),
&github.ListOptions{
Page: nextPage,
PerPage: 100,
},
)
if err != nil {
return err
}
nextPage = resp.NextPage
lastPage = resp.LastPage
for _, r := range releases {
if !nameMatcher.MatchString(r.GetName()) {
continue
}
logger.Info("match found", "release", r.GetName())
var changes []interface{}
if opts.Prerelease != nil && r.GetPrerelease() != *opts.Prerelease {
changes = append(changes, "prerelease", *opts.Prerelease)
r.Prerelease = opts.Prerelease
}
if len(changes) > 0 {
changes = append(
[]interface{}{"release", r.GetName()}, changes...,
)
logger.Info("modifying", changes...)
if !opts.DryRun {
_, _, err = gh.Repositories.EditRelease(
ctx, opts.Repository.Owner(), opts.Repository.Name(),
r.GetID(), r,
)
if err != nil {
return err
}
}
}
}
if nextPage == 0 || lastPage == 0 {
break
}
}
return nil
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
"github.com/jimeh/build-emacs-for-macos/pkg/gh" "github.com/jimeh/build-emacs-for-macos/pkg/gh"
"github.com/jimeh/build-emacs-for-macos/pkg/repository" "github.com/jimeh/build-emacs-for-macos/pkg/repository"
"github.com/jimeh/build-emacs-for-macos/pkg/source"
) )
type releaseType int type releaseType int
@@ -40,15 +41,26 @@ type PublishOptions struct {
// draft) // draft)
ReleaseType releaseType ReleaseType releaseType
// Source contains the source used to build the asset files. When set a
// release body/description text will be generated based on source commit
// details.
Source *source.Source
// AssetFiles is a list of files which must all exist in the release for // AssetFiles is a list of files which must all exist in the release for
// the check to pass. // the check to pass.
AssetFiles []string AssetFiles []string
// AssetSizeCheck causes a file size check for any existing asset files on a
// release which have the same filename as a asset we want to upload. If the
// size of the local and remote files are the same, the existing asset file
// is left in place. When this is false, given asset files will always be
// uploaded, replacing any asset files with the same filename.
AssetSizeCheck bool
// GitHubToken is the OAuth token used to talk to the GitHub API. // GitHubToken is the OAuth token used to talk to the GitHub API.
GithubToken string GithubToken string
} }
//nolint:funlen,gocyclo
// Publish creates and publishes a GitHub release. // Publish creates and publishes a GitHub release.
func Publish(ctx context.Context, opts *PublishOptions) error { func Publish(ctx context.Context, opts *PublishOptions) error {
logger := hclog.FromContext(ctx).Named("release") logger := hclog.FromContext(ctx).Named("release")
@@ -68,6 +80,16 @@ func Publish(ctx context.Context, opts *PublishOptions) error {
prerelease := opts.ReleaseType == Prerelease prerelease := opts.ReleaseType == Prerelease
draft := opts.ReleaseType == Draft draft := opts.ReleaseType == Draft
body := ""
if opts.Source != nil {
body, err = releaseBody(opts)
if err != nil {
return err
}
logger.Debug("rendered release body", "content", body)
}
created := false
logger.Info("checking release", "tag", tagName) logger.Info("checking release", "tag", tagName)
release, resp, err := gh.Repositories.GetReleaseByTag( release, resp, err := gh.Repositories.GetReleaseByTag(
ctx, opts.Repository.Owner(), opts.Repository.Name(), tagName, ctx, opts.Repository.Owner(), opts.Repository.Name(), tagName,
@@ -77,6 +99,7 @@ func Publish(ctx context.Context, opts *PublishOptions) error {
return err return err
} }
created = true
logger.Info("creating release", "tag", tagName, "name", name) logger.Info("creating release", "tag", tagName, "name", name)
release, _, err = gh.Repositories.CreateRelease( release, _, err = gh.Repositories.CreateRelease(
@@ -87,6 +110,7 @@ func Publish(ctx context.Context, opts *PublishOptions) error {
TargetCommitish: &opts.CommitRef, TargetCommitish: &opts.CommitRef,
Prerelease: boolPtr(false), Prerelease: boolPtr(false),
Draft: boolPtr(true), Draft: boolPtr(true),
Body: &body,
}, },
) )
if err != nil { if err != nil {
@@ -94,62 +118,9 @@ func Publish(ctx context.Context, opts *PublishOptions) error {
} }
} }
for _, fileName := range files { err = uploadReleaseAssets(ctx, gh, release, files, opts)
fileIO, err2 := os.Open(fileName) if err != nil {
if err2 != nil { return err
return err2
}
defer fileIO.Close()
fileInfo, err2 := fileIO.Stat()
if err2 != nil {
return err2
}
fileBaseName := filepath.Base(fileName)
assetExists := false
for _, a := range release.Assets {
if a.GetName() != fileBaseName {
continue
}
if a.GetSize() == int(fileInfo.Size()) {
logger.Info("asset exists with correct size",
"file", fileBaseName,
"local_size", byteCountIEC(fileInfo.Size()),
"remote_size", byteCountIEC(int64(a.GetSize())),
)
assetExists = true
} else {
_, err = gh.Repositories.DeleteReleaseAsset(
ctx, opts.Repository.Owner(), opts.Repository.Name(),
a.GetID(),
)
if err != nil {
return err
}
logger.Info(
"deleted asset with wrong size", "file", fileBaseName,
)
}
}
if !assetExists {
logger.Info("uploading asset",
"file", fileBaseName,
"size", byteCountIEC(fileInfo.Size()),
)
_, _, err2 = gh.Repositories.UploadReleaseAsset(
ctx, opts.Repository.Owner(), opts.Repository.Name(),
release.GetID(),
&github.UploadOptions{Name: fileBaseName},
fileIO,
)
if err2 != nil {
return err2
}
}
} }
changed := false changed := false
@@ -158,6 +129,11 @@ func Publish(ctx context.Context, opts *PublishOptions) error {
changed = true changed = true
} }
if body != "" && release.GetBody() != body {
release.Body = &body
changed = true
}
if release.GetDraft() != draft { if release.GetDraft() != draft {
release.Draft = &draft release.Draft = &draft
changed = true changed = true
@@ -169,6 +145,7 @@ func Publish(ctx context.Context, opts *PublishOptions) error {
} }
if changed { if changed {
logger.Info("updating release attributes", "url", release.GetHTMLURL())
release, _, err = gh.Repositories.EditRelease( release, _, err = gh.Repositories.EditRelease(
ctx, opts.Repository.Owner(), opts.Repository.Name(), ctx, opts.Repository.Owner(), opts.Repository.Name(),
release.GetID(), release, release.GetID(), release,
@@ -178,13 +155,89 @@ func Publish(ctx context.Context, opts *PublishOptions) error {
} }
} }
logger.Info("release created", "url", release.GetHTMLURL()) if created {
logger.Info("release created", "url", release.GetHTMLURL())
} else {
logger.Info("release updated", "url", release.GetHTMLURL())
}
return nil
}
func uploadReleaseAssets(
ctx context.Context,
gh *github.Client,
release *github.RepositoryRelease,
fileNames []string,
opts *PublishOptions,
) error {
logger := hclog.FromContext(ctx).Named("release")
for _, fileName := range fileNames {
logger.Debug("processing asset", "file", filepath.Base(fileName))
fileIO, err := os.Open(fileName)
if err != nil {
return err
}
defer fileIO.Close()
fileInfo, err := fileIO.Stat()
if err != nil {
return err
}
fileBaseName := filepath.Base(fileName)
assetExists := false
for _, a := range release.Assets {
if a.GetName() != fileBaseName {
continue
}
if opts.AssetSizeCheck && a.GetSize() == int(fileInfo.Size()) {
logger.Info("asset exists with correct size",
"file", fileBaseName,
"local_size", byteCountIEC(fileInfo.Size()),
"remote_size", byteCountIEC(int64(a.GetSize())),
)
assetExists = true
} else {
logger.Info(
"deleting existing asset", "file", fileBaseName,
)
_, err = gh.Repositories.DeleteReleaseAsset(
ctx, opts.Repository.Owner(), opts.Repository.Name(),
a.GetID(),
)
if err != nil {
return err
}
}
}
if !assetExists {
logger.Info("uploading asset",
"file", fileBaseName,
"size", byteCountIEC(fileInfo.Size()),
)
_, _, err = gh.Repositories.UploadReleaseAsset(
ctx, opts.Repository.Owner(), opts.Repository.Name(),
release.GetID(),
&github.UploadOptions{Name: fileBaseName},
fileIO,
)
if err != nil {
return err
}
}
}
return nil return nil
} }
func publishFileList(files []string) ([]string, error) { func publishFileList(files []string) ([]string, error) {
var output []string results := map[string]struct{}{}
for _, file := range files { for _, file := range files {
var err error var err error
file, err = filepath.Abs(file) file, err = filepath.Abs(file)
@@ -200,11 +253,10 @@ func publishFileList(files []string) ([]string, error) {
return nil, fmt.Errorf("\"%s\" is not a file", file) return nil, fmt.Errorf("\"%s\" is not a file", file)
} }
output = append(output, file) results[file] = struct{}{}
sumFile := file + ".sha256" sumFile := file + ".sha256"
_, err = os.Stat(sumFile) _, err = os.Stat(sumFile)
fmt.Printf("err: %+v\n", err)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
continue continue
@@ -212,7 +264,12 @@ func publishFileList(files []string) ([]string, error) {
return nil, err return nil, err
} }
output = append(output, sumFile) results[sumFile] = struct{}{}
}
var output []string
for f := range results {
output = append(output, f)
} }
return output, nil return output, nil

View File

@@ -0,0 +1,80 @@
package release
import (
"bytes"
"os"
"strings"
"text/template"
)
var tplFuncs = template.FuncMap{
"indent": func(n int, s string) string {
pad := strings.Repeat(" ", n)
return pad + strings.ReplaceAll(s, "\n", "\n"+pad)
},
}
var bodyTpl = template.Must(template.New("body").Funcs(tplFuncs).Parse(`
{{- $t := "` + "`" + `" -}}
### Build Details
{{ with .SourceURL -}}
- Source: {{ . }}
{{- end }}
{{- if and .CommitSHA .CommitURL }}
- Commit: [{{ $t }}{{ .CommitSHA }}{{ $t }}]
{{- if .CommitURL }}({{ .CommitURL }}){{ end }}
{{- else if and .CommitSHA }}
- Commit: {{ $t }}{{ .CommitSHA }}{{ $t }}
{{- end }}
{{- with .TarballURL }}
- Tarball: {{ . }}
{{- end }}
{{- with .BuildLogURL }}
- Build Log: {{ . }} (available for 90 days)
{{- end }}`,
))
type bodyData struct {
SourceURL string
CommitSHA string
CommitURL string
BuildLogURL string
TarballURL string
}
func releaseBody(opts *PublishOptions) (string, error) {
src := opts.Source
if src.Repository == nil || src.Commit == nil {
return "", nil
}
data := &bodyData{
SourceURL: src.Repository.TreeURL(src.Ref),
CommitSHA: src.Commit.SHA,
CommitURL: src.Repository.CommitURL(src.Commit.SHA),
TarballURL: src.Repository.TarballURL(src.Commit.SHA),
}
// If available, use the exact value from the build plan.
if src.Tarball != nil {
data.TarballURL = src.Tarball.URL
}
// If running within GitHub Actions, provide link to build log.
if opts.Repository != nil {
if id := os.Getenv("GITHUB_RUN_ID"); id != "" {
data.BuildLogURL = opts.Repository.ActionRunURL(id)
}
}
var buf bytes.Buffer
err := bodyTpl.Execute(&buf, data)
if err != nil {
return "", err
}
return buf.String(), nil
}

42
pkg/release/version.go Normal file
View File

@@ -0,0 +1,42 @@
package release
import (
"errors"
"fmt"
"regexp"
)
// Errors
var (
Err = errors.New("release")
ErrInvalidName = fmt.Errorf("%w: invalid name", Err)
ErrEmptyVersion = fmt.Errorf("%w: empty version", Err)
ErrNotStableRef = fmt.Errorf(
"%w: git ref is not stable tagged release", Err,
)
)
var (
stableVersion = regexp.MustCompile(`^\d+\.\d+(?:[a-z]+)?$`)
stableGetRef = regexp.MustCompile(`^emacs-(\d+\.\d+(?:[a-z]+)?)$`)
)
func VersionToName(version string) (string, error) {
if version == "" {
return "", ErrEmptyVersion
}
if stableVersion.MatchString(version) {
return "Emacs-" + version, nil
}
return "Emacs." + version, nil
}
func GitRefToStableVersion(ref string) (string, error) {
if m := stableGetRef.FindStringSubmatch(ref); len(m) > 1 {
return m[1], nil
}
return "", fmt.Errorf("%w: \"%s\"", ErrNotStableRef, ref)
}

138
pkg/release/version_test.go Normal file
View File

@@ -0,0 +1,138 @@
package release
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestVersionToName(t *testing.T) {
type args struct {
version string
}
tests := []struct {
name string
args args
want string
wantErr string
}{
{
name: "empty",
args: args{
version: "",
},
wantErr: "release: empty version",
},
{
name: "nightly",
args: args{
version: "2021-07-01.1b88404.master",
},
want: "Emacs.2021-07-01.1b88404.master",
},
{
name: "stable",
args: args{
version: "27.2",
},
want: "Emacs-27.2",
},
{
name: "stable with letter",
args: args{
version: "23.3b",
},
want: "Emacs-23.3b",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := VersionToName(tt.args.version)
assert.Equal(t, tt.want, got)
if tt.wantErr != "" {
assert.EqualError(t, err, tt.wantErr)
} else {
assert.NoError(t, err)
}
})
}
}
func TestGitRefToStableVersion(t *testing.T) {
type args struct {
version string
}
tests := []struct {
name string
args args
want string
wantErr string
}{
{
name: "empty",
args: args{
version: "",
},
wantErr: "release: git ref is not stable tagged release: \"\"",
},
{
name: "master",
args: args{
version: "master",
},
wantErr: "release: git ref is not stable tagged release: " +
"\"master\"",
},
{
name: "feature",
args: args{
version: "feature/native-comp",
},
wantErr: "release: git ref is not stable tagged release: " +
"\"feature/native-comp\"",
},
{
name: "stable",
args: args{
version: "emacs-27.2",
},
want: "27.2",
},
{
name: "stable with letter",
args: args{
version: "emacs-23.3b",
},
want: "23.3b",
},
{
name: "future stable",
args: args{
version: "emacs-239.33",
},
want: "239.33",
},
{
name: "future stable with letter",
args: args{
version: "emacs-239.33c",
},
want: "239.33c",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GitRefToStableVersion(tt.args.version)
assert.Equal(t, tt.want, got)
if tt.wantErr != "" {
assert.EqualError(t, err, tt.wantErr)
} else {
assert.NoError(t, err)
}
})
}
}

View File

@@ -22,8 +22,8 @@ const GitHub Type = "github"
// Repository represents basic information about a repository with helper // Repository represents basic information about a repository with helper
// methods to get various pieces of information from it. // methods to get various pieces of information from it.
type Repository struct { type Repository struct {
Type Type `yaml:"type,omitempty"` Type Type `yaml:"type,omitempty" json:"type,omitempty"`
Source string `yaml:"source,omitempty"` Source string `yaml:"source,omitempty" json:"source,omitempty"`
} }
func NewGitHub(ownerAndName string) (*Repository, error) { func NewGitHub(ownerAndName string) (*Repository, error) {
@@ -89,3 +89,42 @@ func (s *Repository) TarballURL(ref string) string {
return "" return ""
} }
} }
func (s *Repository) CommitURL(ref string) string {
if ref == "" {
return ""
}
switch s.Type {
case GitHub:
return GitHubBaseURL + s.Source + "/commit/" + ref
default:
return ""
}
}
func (s *Repository) TreeURL(ref string) string {
if ref == "" {
return ""
}
switch s.Type {
case GitHub:
return GitHubBaseURL + s.Source + "/tree/" + ref
default:
return ""
}
}
func (s *Repository) ActionRunURL(runID string) string {
if runID == "" {
return ""
}
switch s.Type {
case GitHub:
return GitHubBaseURL + s.Source + "/actions/runs/" + runID
default:
return ""
}
}

View File

@@ -117,42 +117,24 @@ func signCLIHelper(ctx context.Context, appBundle string, opts *Options) error {
} }
// elnFiles finds all native-compilation *.eln files within a Emacs.app bundle, // elnFiles finds all native-compilation *.eln files within a Emacs.app bundle,
// based on expected paths they might be stored in. // excluding any *.eln which should be automatically located by codesign when
// signing the Emacs.app bundle itself with the --deep flag. Essentially this
// only returns *.eln files which must be individually signed before signing the
// app bundle itself.
func elnFiles(emacsApp string) ([]string, error) { func elnFiles(emacsApp string) ([]string, error) {
dirs := []string{
// Current *.eln location.
filepath.Join(emacsApp, "Contents", "Resources", "native-lisp"),
// Legacy *.eln location.
filepath.Join(emacsApp, "Contents", "MacOS", "lib", "emacs"),
}
var files []string var files []string
walkDirFunc := func(path string, _d fs.DirEntry, _err error) error { walkDirFunc := func(path string, d fs.DirEntry, _err error) error {
if strings.HasSuffix(path, ".eln") { if d.Type().IsRegular() && strings.HasSuffix(path, ".eln") &&
!strings.Contains(path, ".app/Contents/Frameworks/") {
files = append(files, path) files = append(files, path)
} }
return nil return nil
} }
for _, dir := range dirs { err := filepath.WalkDir(filepath.Join(emacsApp, "Contents"), walkDirFunc)
fi, err := os.Stat(dir) if err != nil {
if err != nil { return nil, err
if os.IsNotExist(err) {
continue
}
return nil, err
}
if !fi.IsDir() {
continue
}
err = filepath.WalkDir(dir, walkDirFunc)
if err != nil {
return nil, err
}
} }
return files, nil return files, nil

17
pkg/source/source.go Normal file
View File

@@ -0,0 +1,17 @@
package source
import (
"github.com/jimeh/build-emacs-for-macos/pkg/commit"
"github.com/jimeh/build-emacs-for-macos/pkg/repository"
)
type Source struct {
Ref string `yaml:"ref,omitempty" json:"ref,omitempty"`
Repository *repository.Repository `yaml:"repository,omitempty" json:"repository,omitempty"`
Commit *commit.Commit `yaml:"commit,omitempty" json:"commit,omitempty"`
Tarball *Tarball `yaml:"tarball,omitempty" json:"tarball,omitempty"`
}
type Tarball struct {
URL string `yaml:"url,omitempty" json:"url,omitempty"`
}