mirror of
https://github.com/jimeh/build-emacs-for-macos.git
synced 2026-02-19 13:06:38 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
97178bf77a
|
|||
|
591c39e629
|
|||
|
d6c99f8c60
|
|||
|
bcbd01778d
|
|||
|
5602475542
|
|||
|
67b8c5f397
|
|||
|
4dad5812fa
|
|||
|
65bdff0b6d
|
|||
|
a956dc1b42
|
|||
|
af23b63518
|
|||
| 9c20f77fe3 | |||
|
2e2f9bc98a
|
|||
|
d63cd545aa
|
|||
|
1bbfe5d3ea
|
|||
|
b4c5184cef
|
|||
|
e7a991ef92
|
|||
|
7118ed8560
|
|||
|
a71cbda511
|
|||
|
4c0eb37353
|
|||
|
b81e101ca7
|
|||
| c760ffa25e | |||
|
|
931e6ddf9e | ||
|
f17f485b9f
|
|||
|
3622df550c
|
|||
|
2a9707ec89
|
|||
|
1fc7faac1f
|
|||
|
786d253df6
|
@@ -75,6 +75,9 @@ issues:
|
||||
- source: "`json:"
|
||||
linters:
|
||||
- lll
|
||||
- source: "`yaml:"
|
||||
linters:
|
||||
- lll
|
||||
|
||||
run:
|
||||
skip-dirs:
|
||||
|
||||
63
CHANGELOG.md
63
CHANGELOG.md
@@ -2,6 +2,69 @@
|
||||
|
||||
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.20](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.19...v0.6.20) (2021-10-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **notarization:** explicitly only copy *.c and *.h C source files ([591c39e](https://github.com/jimeh/build-emacs-for-macos/commit/591c39e629c9556adcf296cd5c15dd0b17c4d986))
|
||||
|
||||
### [0.6.19](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.18...v0.6.19) (2021-10-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **patch:** resolve emacs-29 symlink patches to their real URL ([bcbd017](https://github.com/jimeh/build-emacs-for-macos/commit/bcbd01778d416b99205c51f348a543489889f66d))
|
||||
|
||||
### [0.6.18](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.17...v0.6.18) (2021-10-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **docs:** embed C source files and set source-directory accordingly ([67b8c5f](https://github.com/jimeh/build-emacs-for-macos/commit/67b8c5f3974e178e31519846d46af82d9770ad6e))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **patches:** correctly use emacs 28.x and 29.x patches ([4dad581](https://github.com/jimeh/build-emacs-for-macos/commit/4dad5812fa2b67adc7262a778829013995a904bc))
|
||||
|
||||
### [0.6.17](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.16...v0.6.17) (2021-10-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **release:** tweak GitHub release description ([a956dc1](https://github.com/jimeh/build-emacs-for-macos/commit/a956dc1b42b2648e2663f5b48c72d7428fe75f19))
|
||||
|
||||
### [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)
|
||||
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class OSVersion
|
||||
end
|
||||
|
||||
def to_s
|
||||
@to_s ||= "#{major}.#{minor}"
|
||||
@to_s ||= major >= 11 ? major.to_s : "#{major}.#{minor}"
|
||||
end
|
||||
|
||||
def major
|
||||
@@ -107,7 +107,8 @@ class Build
|
||||
handle_native_lisp(app)
|
||||
add_cli_helper(app)
|
||||
|
||||
LibEmbedder.new(app, brew_dir, extra_libs, optsion[:relink_eln]).embed
|
||||
CSourcesEmbedder.new(app, @source_dir).embed
|
||||
LibEmbedder.new(app, brew_dir, extra_libs, options[:relink_eln]).embed
|
||||
GccLibEmbedder.new(app, gcc_info).embed if options[:native_comp]
|
||||
|
||||
archive_build(build_dir) if options[:archive]
|
||||
@@ -133,10 +134,6 @@ class Build
|
||||
@archive_filename = plan.dig('output', 'archive')
|
||||
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')
|
||||
end
|
||||
|
||||
@@ -555,7 +552,7 @@ class Build
|
||||
system('tar', '-cjf', archive_filename, build)
|
||||
|
||||
if options[:archive_keep] == false
|
||||
info "Removeing \"#{build}\" directory from #{parent_dir}"
|
||||
info "Removing \"#{build}\" directory from #{parent_dir}"
|
||||
FileUtils.rm_rf(build_dir)
|
||||
end
|
||||
end
|
||||
@@ -644,8 +641,10 @@ class Build
|
||||
'emacs-26'
|
||||
when /^emacs-27.*/
|
||||
'emacs-27'
|
||||
else
|
||||
when /^emacs-28.*/
|
||||
'emacs-28'
|
||||
else
|
||||
'emacs-29'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -653,14 +652,14 @@ class Build
|
||||
def patches(opts = {})
|
||||
p = []
|
||||
|
||||
if %w[emacs-26 emacs-27 emacs-28].include?(effective_version)
|
||||
if %w[emacs-26 emacs-27 emacs-28 emacs-29].include?(effective_version)
|
||||
p << {
|
||||
url: 'https://github.com/d12frosted/homebrew-emacs-plus/raw/master/' \
|
||||
"patches/#{effective_version}/fix-window-role.patch"
|
||||
}
|
||||
end
|
||||
|
||||
if %w[emacs-27 emacs-28].include?(effective_version)
|
||||
if %w[emacs-27 emacs-28 emacs-29].include?(effective_version)
|
||||
p << {
|
||||
url: 'https://github.com/d12frosted/homebrew-emacs-plus/raw/master/' \
|
||||
"patches/#{effective_version}/system-appearance.patch"
|
||||
@@ -718,7 +717,14 @@ class Build
|
||||
info "Downloading patch: #{patch[:url]}"
|
||||
run_cmd('curl', '-L#', patch[:url], '-o', patch_file)
|
||||
|
||||
apply_patch({ file: patch_file }, target)
|
||||
real_patch_url = detect_github_symlink_patch(patch[:url], patch_file)
|
||||
if real_patch_url
|
||||
FileUtils.rm(patch_file)
|
||||
apply_patch({ url: real_patch_url }, target)
|
||||
else
|
||||
apply_patch({ file: patch_file }, target)
|
||||
end
|
||||
|
||||
elsif patch[:replace]
|
||||
err 'Patch replace input error' unless patch[:replace].size == 3
|
||||
|
||||
@@ -736,6 +742,30 @@ class Build
|
||||
f.close
|
||||
end
|
||||
end
|
||||
|
||||
# When downloading raw files from GitHub, if the target file is a symlink, it
|
||||
# will return the actual target path of the symlink instead of the content of
|
||||
# the target file. Hence we have to check if the patch file we have downloaded
|
||||
# contains one and only one line, and if so, assume it's a symlink.
|
||||
def detect_github_symlink_patch(original_url, patch_file)
|
||||
lines = []
|
||||
|
||||
# read first two lines
|
||||
File.open(patch_file) do |f|
|
||||
lines << f.gets
|
||||
lines << f.gets
|
||||
end
|
||||
|
||||
# if the file contains more than one line of text, it's not a symlink.
|
||||
return unless lines[1].nil?
|
||||
|
||||
symlink_target = lines[0].strip
|
||||
# Assume patch file content is something along the lines of
|
||||
# "../emacs-28/fix-window-role.patch", hence we resolve it relative to the
|
||||
# original url.
|
||||
info "patch is symlink to #{symlink_target}"
|
||||
URI.join(original_url, symlink_target).to_s
|
||||
end
|
||||
end
|
||||
|
||||
class AbstractEmbedder
|
||||
@@ -760,17 +790,59 @@ class AbstractEmbedder
|
||||
def lib_dir
|
||||
File.join(invocation_dir, 'lib')
|
||||
end
|
||||
|
||||
def resources_dir
|
||||
File.join(app, 'Contents', 'Resources')
|
||||
end
|
||||
end
|
||||
|
||||
class CSourcesEmbedder < AbstractEmbedder
|
||||
PATH_PATCH = '(setq source-directory (expand-file-name ".."))'
|
||||
|
||||
attr_reader :source_dir
|
||||
|
||||
def initialize(app, source_dir)
|
||||
super(app)
|
||||
|
||||
@source_dir = source_dir
|
||||
end
|
||||
|
||||
def embed
|
||||
info 'Embedding C source files into Emacs.app for documentation purposes'
|
||||
|
||||
src_dir = File.join(source_dir, 'src')
|
||||
Dir[File.join(src_dir, '**', '*.{c,h}')].each do |f|
|
||||
rel = f[src_dir.size + 1..-1]
|
||||
target = File.join(resources_dir, 'src', rel)
|
||||
FileUtils.mkdir_p(File.dirname(target))
|
||||
FileUtils.cp(f, target)
|
||||
end
|
||||
|
||||
return if File.read(subdirs_el_file).include?(PATH_PATCH)
|
||||
|
||||
File.open(subdirs_el_file, 'a') do |f|
|
||||
f.puts("\n#{PATH_PATCH}")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def subdirs_el_file
|
||||
@subdirs_el_file ||= File.join(resources_dir, 'lisp', 'subdirs.el')
|
||||
end
|
||||
end
|
||||
|
||||
class LibEmbedder < AbstractEmbedder
|
||||
attr_reader :lib_source
|
||||
attr_reader :extra_libs
|
||||
attr_reader :embed_eln_files
|
||||
|
||||
def initialize(app, lib_source, extra_libs = [], embed_eln_files = true)
|
||||
super(app)
|
||||
|
||||
@lib_source = lib_source
|
||||
@extra_libs = extra_libs
|
||||
@embed_eln_files = embed_eln_files
|
||||
end
|
||||
|
||||
def embed
|
||||
@@ -1040,6 +1112,7 @@ if __FILE__ == $PROGRAM_NAME
|
||||
cli_options = {
|
||||
work_dir: File.expand_path(__dir__),
|
||||
native_full_aot: false,
|
||||
relink_eln: true,
|
||||
native_march: false,
|
||||
parallel: Etc.nprocessors,
|
||||
rsvg: true,
|
||||
|
||||
@@ -11,9 +11,11 @@ type ReleaseInfo struct {
|
||||
Assets map[string]*ReleaseAsset
|
||||
}
|
||||
|
||||
func (s *ReleaseInfo) Asset(nameMatch string) *ReleaseAsset {
|
||||
if a, ok := s.Assets[nameMatch]; ok {
|
||||
return a
|
||||
func (s *ReleaseInfo) Asset(needles ...string) *ReleaseAsset {
|
||||
if len(needles) == 1 {
|
||||
if a, ok := s.Assets[needles[0]]; ok {
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if strings.Contains(a.Filename, nameMatch) {
|
||||
return a
|
||||
for _, needle := range needles {
|
||||
if !strings.Contains(a.Filename, needle) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ReleaseInfo) DownloadURL(nameMatch string) string {
|
||||
a := s.Asset(nameMatch)
|
||||
func (s *ReleaseInfo) DownloadURL(needles ...string) string {
|
||||
a := s.Asset(needles...)
|
||||
if a == nil {
|
||||
return ""
|
||||
}
|
||||
@@ -44,8 +50,8 @@ func (s *ReleaseInfo) DownloadURL(nameMatch string) string {
|
||||
return a.DownloadURL
|
||||
}
|
||||
|
||||
func (s *ReleaseInfo) SHA256(nameMatch string) string {
|
||||
a := s.Asset(nameMatch)
|
||||
func (s *ReleaseInfo) SHA256(needles ...string) string {
|
||||
a := s.Asset(needles...)
|
||||
if a == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ func caskUpdateCmd() *cli2.Command {
|
||||
|
||||
func caskUpdateAction(
|
||||
c *cli2.Context,
|
||||
opts *Options,
|
||||
_ *Options,
|
||||
cOpts *caskOptions,
|
||||
) error {
|
||||
updateOpts := &cask.UpdateOptions{
|
||||
|
||||
@@ -52,7 +52,7 @@ func notarizeCmd() *cli2.Command {
|
||||
}
|
||||
}
|
||||
|
||||
func notarizeAction(c *cli2.Context, opts *Options) error {
|
||||
func notarizeAction(c *cli2.Context, _ *Options) error {
|
||||
options := ¬arize.Options{
|
||||
File: c.Args().Get(0),
|
||||
BundleID: c.String("bundle-id"),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@@ -37,6 +38,12 @@ func planCmd() *cli2.Command {
|
||||
Name: "sha",
|
||||
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{
|
||||
Name: "output",
|
||||
Usage: "output filename to write plan to instead of printing " +
|
||||
@@ -102,7 +109,18 @@ func planAction(c *cli2.Context, opts *Options) error {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@@ -111,7 +129,7 @@ func planAction(c *cli2.Context, opts *Options) error {
|
||||
out = os.Stdout
|
||||
if f := c.String("output"); f != "" {
|
||||
logger.Info("writing plan", "file", f)
|
||||
logger.Debug("content", "yaml", planYAML)
|
||||
logger.Debug("content", format, plan)
|
||||
out, err = os.Create(f)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -119,7 +137,7 @@ func planAction(c *cli2.Context, opts *Options) error {
|
||||
defer out.Close()
|
||||
}
|
||||
|
||||
_, err = out.WriteString(planYAML)
|
||||
_, err = out.WriteString(plan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func releaseCmd() *cli2.Command {
|
||||
Usage: "owner/name of GitHub repo to check for release, " +
|
||||
"ignored if a plan is provided",
|
||||
EnvVars: []string{"GITHUB_REPOSITORY"},
|
||||
Value: "jimeh/emacs-builds",
|
||||
Value: "",
|
||||
},
|
||||
&cli2.StringFlag{
|
||||
Name: "name",
|
||||
@@ -108,7 +108,7 @@ func releaseCheckCmd() *cli2.Command {
|
||||
|
||||
func releaseCheckAction(
|
||||
c *cli2.Context,
|
||||
opts *Options,
|
||||
_ *Options,
|
||||
rOpts *releaseOptions,
|
||||
) error {
|
||||
rlsOpts := &release.CheckOptions{
|
||||
@@ -153,6 +153,12 @@ func releasePublishCmd() *cli2.Command {
|
||||
"specified",
|
||||
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),
|
||||
}
|
||||
@@ -160,16 +166,17 @@ func releasePublishCmd() *cli2.Command {
|
||||
|
||||
func releasePublishAction(
|
||||
c *cli2.Context,
|
||||
opts *Options,
|
||||
_ *Options,
|
||||
rOpts *releaseOptions,
|
||||
) error {
|
||||
rlsOpts := &release.PublishOptions{
|
||||
Repository: rOpts.Repository,
|
||||
CommitRef: c.String("release-sha"),
|
||||
ReleaseName: rOpts.Name,
|
||||
ReleaseTitle: c.String("title"),
|
||||
AssetFiles: c.Args().Slice(),
|
||||
GithubToken: rOpts.GithubToken,
|
||||
Repository: rOpts.Repository,
|
||||
CommitRef: c.String("release-sha"),
|
||||
ReleaseName: rOpts.Name,
|
||||
ReleaseTitle: c.String("title"),
|
||||
AssetFiles: c.Args().Slice(),
|
||||
AssetSizeCheck: c.Bool("asset-size-check"),
|
||||
GithubToken: rOpts.GithubToken,
|
||||
}
|
||||
|
||||
rlsType := c.String("type")
|
||||
@@ -184,7 +191,13 @@ func releasePublishAction(
|
||||
return fmt.Errorf("invalid --type \"%s\"", rlsType)
|
||||
}
|
||||
|
||||
if c.Args().Len() > 0 {
|
||||
rlsOpts.AssetFiles = c.Args().Slice()
|
||||
}
|
||||
|
||||
if rOpts.Plan != nil {
|
||||
rlsOpts.Source = rOpts.Plan.Source
|
||||
|
||||
if rOpts.Plan.Release != nil {
|
||||
rlsOpts.ReleaseName = rOpts.Plan.Release.Name
|
||||
rlsOpts.ReleaseTitle = rOpts.Plan.Release.Title
|
||||
@@ -196,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{
|
||||
filepath.Join(
|
||||
rOpts.Plan.Output.Directory,
|
||||
@@ -235,7 +249,7 @@ func releaseBulkCmd() *cli2.Command {
|
||||
|
||||
func releaseBulkAction(
|
||||
c *cli2.Context,
|
||||
opts *Options,
|
||||
_ *Options,
|
||||
rOpts *releaseOptions,
|
||||
) error {
|
||||
bulkOpts := &release.BulkOptions{
|
||||
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
)
|
||||
|
||||
type Commit struct {
|
||||
SHA string `yaml:"sha"`
|
||||
Date *time.Time `yaml:"date"`
|
||||
Author string `yaml:"author"`
|
||||
Committer string `yaml:"committer"`
|
||||
Message string `yaml:"message"`
|
||||
SHA string `yaml:"sha" json:"sha"`
|
||||
Date *time.Time `yaml:"date" json:"date"`
|
||||
Author string `yaml:"author" json:"author"`
|
||||
Committer string `yaml:"committer" json:"committer"`
|
||||
Message string `yaml:"message" json:"message"`
|
||||
}
|
||||
|
||||
func New(rc *github.RepositoryCommit) *Commit {
|
||||
|
||||
@@ -2,13 +2,14 @@ package osinfo
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type OSInfo struct {
|
||||
Name string `yaml:"name"`
|
||||
Version string `yaml:"version"`
|
||||
Arch string `yaml:"arch"`
|
||||
Name string `yaml:"name" json:"name"`
|
||||
Version string `yaml:"version" json:"version"`
|
||||
Arch string `yaml:"arch" json:"arch"`
|
||||
}
|
||||
|
||||
func New() (*OSInfo, error) {
|
||||
@@ -29,8 +30,17 @@ func New() (*OSInfo, error) {
|
||||
}, 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, ".")
|
||||
|
||||
if n, _ := strconv.Atoi(parts[0]); n >= 11 {
|
||||
return parts[0]
|
||||
}
|
||||
|
||||
max := len(parts)
|
||||
if max > 2 {
|
||||
max = 2
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"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/source"
|
||||
)
|
||||
|
||||
var nonAlphaNum = regexp.MustCompile(`[^\w_-]+`)
|
||||
@@ -76,7 +77,7 @@ func Create(ctx context.Context, opts *Options) (*Plan, error) {
|
||||
buildName := fmt.Sprintf(
|
||||
"Emacs.%s.%s.%s",
|
||||
version,
|
||||
sanitizeString(osInfo.Name+"-"+osInfo.MajorMinor()),
|
||||
sanitizeString(osInfo.Name+"-"+osInfo.DistinctVersion()),
|
||||
sanitizeString(osInfo.Arch),
|
||||
)
|
||||
diskImage := buildName + ".dmg"
|
||||
@@ -85,11 +86,11 @@ func Create(ctx context.Context, opts *Options) (*Plan, error) {
|
||||
Build: &Build{
|
||||
Name: buildName,
|
||||
},
|
||||
Source: &Source{
|
||||
Source: &source.Source{
|
||||
Ref: opts.Ref,
|
||||
Repository: repo,
|
||||
Commit: commitInfo,
|
||||
Tarball: &Tarball{
|
||||
Tarball: &source.Tarball{
|
||||
URL: repo.TarballURL(commitInfo.SHA),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,21 +2,21 @@ package plan
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"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/repository"
|
||||
"github.com/jimeh/build-emacs-for-macos/pkg/source"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Plan struct {
|
||||
Build *Build `yaml:"build,omitempty"`
|
||||
Source *Source `yaml:"source,omitempty"`
|
||||
OS *osinfo.OSInfo `yaml:"os,omitempty"`
|
||||
Release *Release `yaml:"release,omitempty"`
|
||||
Output *Output `yaml:"output,omitempty"`
|
||||
Build *Build `yaml:"build,omitempty" json:"build,omitempty"`
|
||||
Source *source.Source `yaml:"source,omitempty" json:"source,omitempty"`
|
||||
OS *osinfo.OSInfo `yaml:"os,omitempty" json:"os,omitempty"`
|
||||
Release *Release `yaml:"release,omitempty" json:"release,omitempty"`
|
||||
Output *Output `yaml:"output,omitempty" json:"output,omitempty"`
|
||||
}
|
||||
|
||||
// Load attempts to loads a plan YAML from given filename.
|
||||
@@ -54,29 +54,37 @@ func (s *Plan) YAML() (string, error) {
|
||||
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 {
|
||||
Name string `yaml:"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"`
|
||||
Name string `yaml:"name,omitempty" json:"name,omitempty"`
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
Name string `yaml:"name"`
|
||||
Title string `yaml:"title,omitempty"`
|
||||
Draft bool `yaml:"draft,omitempty"`
|
||||
Prerelease bool `yaml:"prerelease,omitempty"`
|
||||
Name string `yaml:"name" json:"name"`
|
||||
Title string `yaml:"title,omitempty" json:"title,omitempty"`
|
||||
Draft bool `yaml:"draft,omitempty" json:"draft,omitempty"`
|
||||
Prerelease bool `yaml:"prerelease,omitempty" json:"prerelease,omitempty"`
|
||||
}
|
||||
|
||||
type Output struct {
|
||||
Directory string `yaml:"directory,omitempty"`
|
||||
DiskImage string `yaml:"disk_image,omitempty"`
|
||||
Directory string `yaml:"directory,omitempty" json:"directory,omitempty"`
|
||||
DiskImage string `yaml:"disk_image,omitempty" json:"disk_image,omitempty"`
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"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/source"
|
||||
)
|
||||
|
||||
type releaseType int
|
||||
@@ -40,15 +41,26 @@ type PublishOptions struct {
|
||||
// draft)
|
||||
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
|
||||
// the check to pass.
|
||||
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 string
|
||||
}
|
||||
|
||||
//nolint:funlen,gocyclo
|
||||
// Publish creates and publishes a GitHub release.
|
||||
func Publish(ctx context.Context, opts *PublishOptions) error {
|
||||
logger := hclog.FromContext(ctx).Named("release")
|
||||
@@ -68,6 +80,16 @@ func Publish(ctx context.Context, opts *PublishOptions) error {
|
||||
prerelease := opts.ReleaseType == Prerelease
|
||||
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)
|
||||
release, resp, err := gh.Repositories.GetReleaseByTag(
|
||||
ctx, opts.Repository.Owner(), opts.Repository.Name(), tagName,
|
||||
@@ -77,6 +99,7 @@ func Publish(ctx context.Context, opts *PublishOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
created = true
|
||||
logger.Info("creating release", "tag", tagName, "name", name)
|
||||
|
||||
release, _, err = gh.Repositories.CreateRelease(
|
||||
@@ -87,6 +110,7 @@ func Publish(ctx context.Context, opts *PublishOptions) error {
|
||||
TargetCommitish: &opts.CommitRef,
|
||||
Prerelease: boolPtr(false),
|
||||
Draft: boolPtr(true),
|
||||
Body: &body,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -94,62 +118,9 @@ func Publish(ctx context.Context, opts *PublishOptions) error {
|
||||
}
|
||||
}
|
||||
|
||||
for _, fileName := range files {
|
||||
fileIO, err2 := os.Open(fileName)
|
||||
if err2 != nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
err = uploadReleaseAssets(ctx, gh, release, files, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
changed := false
|
||||
@@ -158,6 +129,11 @@ func Publish(ctx context.Context, opts *PublishOptions) error {
|
||||
changed = true
|
||||
}
|
||||
|
||||
if body != "" && release.GetBody() != body {
|
||||
release.Body = &body
|
||||
changed = true
|
||||
}
|
||||
|
||||
if release.GetDraft() != draft {
|
||||
release.Draft = &draft
|
||||
changed = true
|
||||
@@ -169,6 +145,7 @@ func Publish(ctx context.Context, opts *PublishOptions) error {
|
||||
}
|
||||
|
||||
if changed {
|
||||
logger.Info("updating release attributes", "url", release.GetHTMLURL())
|
||||
release, _, err = gh.Repositories.EditRelease(
|
||||
ctx, opts.Repository.Owner(), opts.Repository.Name(),
|
||||
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
|
||||
}
|
||||
|
||||
func publishFileList(files []string) ([]string, error) {
|
||||
var output []string
|
||||
results := map[string]struct{}{}
|
||||
for _, file := range files {
|
||||
var err error
|
||||
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)
|
||||
}
|
||||
|
||||
output = append(output, file)
|
||||
results[file] = struct{}{}
|
||||
sumFile := file + ".sha256"
|
||||
|
||||
_, err = os.Stat(sumFile)
|
||||
fmt.Printf("err: %+v\n", err)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
@@ -212,7 +264,12 @@ func publishFileList(files []string) ([]string, error) {
|
||||
|
||||
return nil, err
|
||||
}
|
||||
output = append(output, sumFile)
|
||||
results[sumFile] = struct{}{}
|
||||
}
|
||||
|
||||
var output []string
|
||||
for f := range results {
|
||||
output = append(output, f)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
|
||||
78
pkg/release/release_body.go
Normal file
78
pkg/release/release_body.go
Normal file
@@ -0,0 +1,78 @@
|
||||
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 .CommitURL }}
|
||||
- Commit: {{ .CommitURL }}
|
||||
{{- if .CommitSHA }} ({{ $t }}{{ .CommitSHA }}{{ $t }}){{ end }}
|
||||
{{- 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
|
||||
}
|
||||
@@ -22,8 +22,8 @@ const GitHub Type = "github"
|
||||
// Repository represents basic information about a repository with helper
|
||||
// methods to get various pieces of information from it.
|
||||
type Repository struct {
|
||||
Type Type `yaml:"type,omitempty"`
|
||||
Source string `yaml:"source,omitempty"`
|
||||
Type Type `yaml:"type,omitempty" json:"type,omitempty"`
|
||||
Source string `yaml:"source,omitempty" json:"source,omitempty"`
|
||||
}
|
||||
|
||||
func NewGitHub(ownerAndName string) (*Repository, error) {
|
||||
@@ -89,3 +89,42 @@ func (s *Repository) TarballURL(ref string) string {
|
||||
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 ""
|
||||
}
|
||||
}
|
||||
|
||||
17
pkg/source/source.go
Normal file
17
pkg/source/source.go
Normal 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"`
|
||||
}
|
||||
Reference in New Issue
Block a user