diff options
author | Eric Seidel <gridaphobe@gmail.com> | 2015-02-08 16:29:48 -0800 |
---|---|---|
committer | Eric Seidel <gridaphobe@gmail.com> | 2015-02-17 13:21:12 -0800 |
commit | 39c647c218ee88e778aad33355dbc07dc614e841 (patch) | |
tree | f91e0f10685242e0b94b4cbdb980995936e79c67 /pkgs | |
parent | 2fe44b95d088067bb6a2a144300f3ae898185a1d (diff) | |
download | nixlib-39c647c218ee88e778aad33355dbc07dc614e841.tar nixlib-39c647c218ee88e778aad33355dbc07dc614e841.tar.gz nixlib-39c647c218ee88e778aad33355dbc07dc614e841.tar.bz2 nixlib-39c647c218ee88e778aad33355dbc07dc614e841.tar.lz nixlib-39c647c218ee88e778aad33355dbc07dc614e841.tar.xz nixlib-39c647c218ee88e778aad33355dbc07dc614e841.tar.zst nixlib-39c647c218ee88e778aad33355dbc07dc614e841.zip |
add package.el-based emacs package builder
Diffstat (limited to 'pkgs')
-rw-r--r-- | pkgs/applications/editors/emacs-24/packages/default.nix | 647 | ||||
-rw-r--r-- | pkgs/applications/editors/emacs-24/packages/melpa.nix | 99 | ||||
-rw-r--r-- | pkgs/applications/editors/emacs-24/packages/melpa2nix.el | 36 | ||||
-rw-r--r-- | pkgs/applications/editors/emacs-24/packages/package-build.el | 1333 | ||||
-rw-r--r-- | pkgs/applications/editors/emacs-24/packages/setup-hook.sh | 7 | ||||
-rw-r--r-- | pkgs/top-level/all-packages.nix | 7 |
6 files changed, 2129 insertions, 0 deletions
diff --git a/pkgs/applications/editors/emacs-24/packages/default.nix b/pkgs/applications/editors/emacs-24/packages/default.nix new file mode 100644 index 000000000000..e97250690cfe --- /dev/null +++ b/pkgs/applications/editors/emacs-24/packages/default.nix @@ -0,0 +1,647 @@ +{ pkgs, stdenv, fetchurl, fetchFromGitHub, fetchgit +, emacs, texinfo + +# non-emacs packages +, external +}: + +# package.el-based emacs packages + +## init.el +# (require 'package) +# (setq package-archives nil +# package-user-dir "~/.nix-profile/share/emacs/site-lisp/elpa") +# (package-initialize) + + +let + melpa = import ./melpa.nix { + inherit stdenv fetchurl emacs texinfo; + }; +in + +rec { + ace-jump-mode = melpa.mkDerivation (self: { + pname = "ace-jump-mode"; + version = "20140616"; + src = fetchFromGitHub { + owner = "winterTTr"; + repo = self.pname; + rev = "8351e2df4fbbeb2a4003f2fb39f46d33803f3dac"; + sha256 = "17axrgd99glnl6ma4ls3k01ysdqmiqr581wnrbsn3s4gp53mm2x6"; + }; + }); + + ag = melpa.mkDerivation (self: { + pname = "ag"; + version = "0.44"; + src = fetchFromGitHub { + owner = "Wilfred"; + repo = "${self.pname}.el"; + rev = self.version; + sha256 = "19y5w9m2flp4as54q8yfngrkri3kd7fdha9pf2xjgx6ryflqx61k"; + }; + packageRequires = [ dash s ]; + }); + + async = melpa.mkDerivation (self: { + pname = "async"; + version = "1.2"; + src = fetchFromGitHub { + owner = "jwiegley"; + repo = "emacs-async"; + rev = "v${self.version}"; + sha256 = "1j6mbvvbnm2m1gpsy9ipxiv76b684nn57yssbqdyiwyy499cma6q"; + }; + }); + + auctex = melpa.mkDerivation (self: { + pname = "auctex"; + version = "11.87.7"; + src = fetchurl { + url = "http://elpa.gnu.org/packages/${self.fname}.tar"; + sha256 = "07bhw8zc3d1f2basjy80njmxpsp4f70kg3ynkch9ghlai3mm2b7n"; + }; + buildPhase = '' + cp $src ${self.fname}.tar + ''; + }); + + auto-complete = melpa.mkDerivation (self: { + pname = "auto-complete-1.3.1"; + src = fetchurl { + url = "http://cx4a.org/pub/auto-complete/${self.pname}.tar.bz2"; + sha256 = "124qxfp0pcphwlmrasbfrci48brxnrzc38h4wcf2sn20x1mvcrlj"; + }; + meta = { + description = "Auto-complete extension for Emacs"; + homepage = http://cx4a.org/software/auto-complete/; + license = stdenv.lib.licenses.gpl3Plus; + platforms = stdenv.lib.platforms.all; + }; + }); + + bind-key = melpa.mkDerivation (self: { + pname = "bind-key"; + version = "20141013"; + src = fetchFromGitHub { + owner = "jwiegley"; + repo = "use-package"; + rev = "d43af5e0769a92f77e01dea229e376d9006722ef"; + sha256 = "1m4v5h52brg2g9rpbqfq9m3m8fv520vg5mjwppnbw6099d17msqd"; + }; + files = [ "bind-key.el" ]; + }); + + change-inner = melpa.mkDerivation (self: { + pname = "change-inner"; + version = "20130208"; + src = fetchFromGitHub { + owner = "magnars"; + repo = "${self.pname}.el"; + rev = "6374b745ee1fd0302ad8596cdb7aca1bef33a730"; + sha256 = "1fv8630bqbmfr56zai08f1q4dywksmghhm70084bz4vbs6rzdsbq"; + }; + packageRequires = [ expand-region ]; + }); + + circe = melpa.mkDerivation (self: { + pname = "circe"; + version = "1.5"; + src = fetchFromGitHub { + owner = "jorgenschaefer"; + repo = "circe"; + rev = "v${self.version}"; + sha256 = "08dsv1dzgb9jx076ia7xbpyjpaxn1w87h6rzlb349spaydq7ih24"; + }; + packageRequires = [ lcs lui ]; + fileSpecs = [ "lisp/circe*.el" ]; + }); + + company-mode = melpa.mkDerivation (self: { + pname = "company-mode"; + version = "0.8.6"; + src = fetchFromGitHub { + owner = "company-mode"; + repo = "company-mode"; + rev = self.version; + sha256 = "1xwxyqg5dan8m1qkdxyzm066ryf24h07karpdlm3s09izfdny33f"; + }; + }); + + dash = melpa.mkDerivation (self: { + pname = "dash"; + version = "2.9.0"; + src = fetchFromGitHub { + owner = "magnars"; + repo = "${self.pname}.el"; + rev = self.version; + sha256 = "1lg31s8y6ljsz6ps765ia5px39wim626xy8fbc4jpk8fym1jh7ay"; + }; + }); + + diminish = melpa.mkDerivation (self: { + pname = "diminish"; + version = "0.44"; + src = fetchFromGitHub { + owner = "emacsmirror"; + repo = self.pname; + rev = self.version; + sha256 = "0hshw7z5f8pqxvgxw74kbj6nvprsgfvy45fl854xarnkvqcara09"; + }; + }); + + epl = melpa.mkDerivation (self: { + pname = "epl"; + version = "20140823"; + src = fetchFromGitHub { + owner = "cask"; + repo = self.pname; + rev = "63c78c08e345455f3d4daa844fdc551a2c18024e"; + sha256 = "04a2aq8dj2cmy77vw142wcmnjvqdbdsp6z0psrzz2qw0b0am03li"; + }; + }); + + evil-god-state = melpa.mkDerivation (self: { + pname = "evil-god-state"; + version = "20140830"; + src = fetchFromGitHub { + owner = "gridaphobe"; + repo = self.pname; + rev = "234a9b6f500ece89c3dfb5c1df5baef6963e4566"; + sha256 = "16v6dpw1hibrkf9hga88gv5axvp1pajd67brnh5h4wpdy9qvwgyy"; + }; + packageRequires = [ evil god-mode ]; + }); + + evil-surround = melpa.mkDerivation (self: { + pname = "evil-surround"; + version = "20140616"; + src = fetchFromGitHub { + owner = "timcharper"; + repo = self.pname; + rev = "71f380b6b6ed38f739c0a4740b3d6de0c52f915a"; + sha256 = "0wrmlmgr4mwxlmmh8blplddri2lpk4g8k3l1vpb5c6a975420qvn"; + }; + packageRequires = [ evil ]; + }); + + evil = melpa.mkDerivation (self: { + pname = "evil"; + version = "20141020"; + src = fetchgit { + url = "git://gitorious.org/evil/evil"; + rev = "999ec15587f85100311c031aa8efb5d50c35afe4"; + sha256 = "0yiqpzsm5sr7xdkixdvfg312dk9vsdcmj69gizk744d334yn8rsz"; + }; + packageRequires = [ goto-chg undo-tree ]; + }); + + exec-path-from-shell = melpa.mkDerivation (self: { + pname = "exec-path-from-shell"; + version = "20141022"; + src = fetchFromGitHub { + owner = "purcell"; + repo = self.pname; + rev = "e4af0e9b44738e7474c89ed895200b42e6541515"; + sha256 = "0lxikiqf1jik88lf889q4f4f8kdgg3npciz298x605nhbfd5snbd"; + }; + }); + + expand-region = melpa.mkDerivation (self: { + pname = "expand-region"; + version = "20141012"; + src = fetchFromGitHub { + owner = "magnars"; + repo = "${self.pname}.el"; + rev = "fa413e07c97997d950c92d6012f5442b5c3cee78"; + sha256 = "04k0518wfy72wpzsswmncnhd372fxa0r8nbfhmbyfmns8n7sr045"; + }; + }); + + flycheck-pos-tip = melpa.mkDerivation (self: { + pname = "flycheck-pos-tip"; + version = "20140813"; + src = fetchFromGitHub { + owner = "flycheck"; + repo = self.pname; + rev = "5b3a203bbdb03e4f48d1654efecd71f44376e199"; + sha256 = "0b4x24aq0jh4j4bjv0fqyaz6hzh3gqf57k9763jj9rl32cc3dpnp"; + }; + packageRequires = [ flycheck popup ]; + }); + + flycheck = melpa.mkDerivation (self: { + pname = "flycheck"; + version = "0.20"; + src = fetchFromGitHub { + owner = self.pname; + repo = self.pname; + rev = self.version; + sha256 = "0cq7y7ssm6phvx5pfv2yqq4j0yqmm0lhjav7v4a8ql7094cd790a"; + }; + packageRequires = [ dash pkg-info ]; + }); + + ghc-mod = melpa.mkDerivation (self: { + pname = "ghc"; + version = external.ghc-mod.version; + src = external.ghc-mod.src; + fileSpecs = [ "elisp/*.el" ]; + }); + + git-commit-mode = melpa.mkDerivation (self: { + pname = "git-commit-mode"; + version = "0.15.0"; + src = fetchFromGitHub { + owner = "magit"; + repo = "git-modes"; + rev = self.version; + sha256 = "1x03276yq63cddc89n8i47k1f6p26b7a5la4hz66fdf15gmr8496"; + }; + files = [ "git-commit-mode.el" ]; + }); + + git-rebase-mode = melpa.mkDerivation (self: { + pname = "git-rebase-mode"; + version = "0.15.0"; + src = fetchFromGitHub { + owner = "magit"; + repo = "git-modes"; + rev = self.version; + sha256 = "1x03276yq63cddc89n8i47k1f6p26b7a5la4hz66fdf15gmr8496"; + }; + files = [ "git-rebase-mode.el" ]; + }); + + gitattributes-mode = melpa.mkDerivation (self: { + pname = "gitattributes-mode"; + version = "0.15.0"; + src = fetchFromGitHub { + owner = "magit"; + repo = "git-modes"; + rev = self.version; + sha256 = "1x03276yq63cddc89n8i47k1f6p26b7a5la4hz66fdf15gmr8496"; + }; + files = [ "gitattributes-mode.el" ]; + }); + + gitconfig-mode = melpa.mkDerivation (self: { + pname = "gitconfig-mode"; + version = "0.15.0"; + src = fetchFromGitHub { + owner = "magit"; + repo = "git-modes"; + rev = self.version; + sha256 = "1x03276yq63cddc89n8i47k1f6p26b7a5la4hz66fdf15gmr8496"; + }; + files = [ "gitconfig-mode.el" ]; + }); + + gitignore-mode = melpa.mkDerivation (self: { + pname = "gitignore-mode"; + version = "0.15.0"; + src = fetchFromGitHub { + owner = "magit"; + repo = "git-modes"; + rev = self.version; + sha256 = "1x03276yq63cddc89n8i47k1f6p26b7a5la4hz66fdf15gmr8496"; + }; + files = [ "gitignore-mode.el" ]; + }); + + gnus = melpa.mkDerivation (self: { + pname = "gnus"; + version = "20140501"; + src = fetchgit { + url = "http://git.gnus.org/gnus.git"; + rev = "4228cffcb7afb77cf39678e4a8988a57753502a5"; + sha256 = "0qd0wpxkz47irxghmdpa524c9626164p8vgqs26wlpbdwyvm64a0"; + }; + fileSpecs = [ "lisp/*.el" "texi/*.texi" ]; + preBuild = '' + (cd lisp && make gnus-load.el) + ''; + }); + + god-mode = melpa.mkDerivation (self: { + pname = "god-mode"; + version = "20140811"; + src = fetchFromGitHub { + owner = "chrisdone"; + repo = self.pname; + rev = "6b7ae259a58ca1d7776aa4eca9f1092e4c0033e6"; + sha256 = "1amr98nq82g2d3f3f5wlqm9g38j64avygnsi9rrlbfqz4f71vq7x"; + }; + }); + + goto-chg = melpa.mkDerivation (self: { + pname = "goto-chg"; + version = "1.6"; + src = fetchgit { + url = "git://gitorious.org/evil/evil"; + rev = "999ec15587f85100311c031aa8efb5d50c35afe4"; + sha256 = "0yiqpzsm5sr7xdkixdvfg312dk9vsdcmj69gizk744d334yn8rsz"; + }; + files = [ "lib/goto-chg.el" ]; + }); + + haskell-mode = melpa.mkDerivation (self: { + pname = "haskell-mode"; + version = "20150101"; + src = fetchFromGitHub { + owner = "haskell"; + repo = self.pname; + rev = "0db5efaaeb3b22e5a3fdafa600729e14c1716ee2"; + sha256 = "0d63cgzj579cr8zbrnl0inyy35b26sxinqxr7bgrjsngpmhm52an"; + }; + }); + + helm-swoop = melpa.mkDerivation (self: { + pname = "helm-swoop"; + version = "20141224"; + src = fetchFromGitHub { + owner = "ShingoFukuyama"; + repo = self.pname; + rev = "06a251f7d7fce2a5719e0862e5855972cd8ab1ae"; + sha256 = "0nq33ldhbvfbm6jnsxqdf3vwaqrsr2gprkzll081gcyl2s1x0l2m"; + }; + packageRequires = [ helm ]; + }); + + helm = melpa.mkDerivation (self: { + pname = "helm"; + version = "20150105"; + src = fetchFromGitHub { + owner = "emacs-helm"; + repo = self.pname; + rev = "e5608ad86e7ca72446a4b1aa0faf604200ffe895"; + sha256 = "0n2kr6pyzcsi8pq6faxz2y8kicz1gmvj98fzzlq3a107dqqp25ay"; + }; + packageRequires = [ async ]; + }); + + idris-mode = melpa.mkDerivation (self: { + pname = "idris-mode"; + version = "0.9.15"; + src = fetchFromGitHub { + owner = "idris-hackers"; + repo = "idris-mode"; + rev = self.version; + sha256 = "00pkgk1zxan89i8alsa2dpa9ls7imqk5zb1kbjwzrlbr0gk4smdb"; + }; + packageRequires = [ flycheck ]; + }); + + lcs = melpa.mkDerivation (self: { + pname = "lcs"; + version = "1.5"; + src = fetchFromGitHub { + owner = "jorgenschaefer"; + repo = "circe"; + rev = "v${self.version}"; + sha256 = "08dsv1dzgb9jx076ia7xbpyjpaxn1w87h6rzlb349spaydq7ih24"; + }; + fileSpecs = [ "lisp/lcs*.el" ]; + }); + + lui = melpa.mkDerivation (self: { + pname = "lui"; + version = "1.5"; + src = fetchFromGitHub { + owner = "jorgenschaefer"; + repo = "circe"; + rev = "v${self.version}"; + sha256 = "08dsv1dzgb9jx076ia7xbpyjpaxn1w87h6rzlb349spaydq7ih24"; + }; + packageRequires = [ tracking ]; + fileSpecs = [ "lisp/lui*.el" ]; + }); + + magit = melpa.mkDerivation (self: { + pname = "magit"; + version = "20141025"; + src = fetchFromGitHub { + owner = "magit"; + repo = "magit"; + rev = "50c08522c8a3c67e0f3b821fe4df61e8bd456ff9"; + sha256 = "0mzyx72pidzvla1x2qszn3c60n2j0n8i5k875c4difvd1n4p0vsk"; + }; + packageRequires = [ git-commit-mode git-rebase-mode ]; + }); + + markdown-mode = melpa.mkDerivation (self: { + pname = "markdown-mode"; + version = "2.0"; + src = fetchFromGitHub { + owner = "defunkt"; + repo = self.pname; + rev = "v${self.version}"; + sha256 = "1l2w0j9xl8pipz61426s79jq2yns42vjvysc6yjc29kbsnhalj29"; + }; + }); + + org-plus-contrib = melpa.mkDerivation (self: { + pname = "org-plus-contrib"; + version = "20141020"; + src = fetchurl { + url = "http://orgmode.org/elpa/${self.fname}.tar"; + sha256 = "02njxmdbmias2f5psvwqc115dyakcwm2g381gfdv8qz4sqav0r77"; + }; + buildPhase = '' + cp $src ${self.fname}.tar + ''; + }); + + pkg-info = melpa.mkDerivation (self: { + pname = "pkg-info"; + version = "20140610"; + src = fetchFromGitHub { + owner = "lunaryorn"; + repo = "${self.pname}.el"; + rev = "475cdeb0b8d44f9854e506c429eeb445787014ec"; + sha256 = "0x4nz54f2shgcw3gx66d265vxwdpdirn64gzii8dpxhsi7v86n0p"; + }; + packageRequires = [ epl ]; + }); + + popup = melpa.mkDerivation (self: { + pname = "popup"; + version = "0.5.0"; + src = fetchFromGitHub { + owner = "auto-complete"; + repo = "${self.pname}-el"; + rev = "v${self.version}"; + sha256 = "0836ayyz1syvd9ry97ya06l8mpr88c6xbgb4d98szj6iwbypcj7b"; + }; + }); + + projectile = melpa.mkDerivation (self: { + pname = "projectile"; + version = "20141020"; + src = fetchFromGitHub { + owner = "bbatsov"; + repo = self.pname; + rev = "13580d83374e0c17c55b3a680b816dfae407657e"; + sha256 = "10c28h2g53sg68lwamhak0shdhh26h5xaipipz3n4281sr1fwg58"; + }; + packageRequires = [ dash helm s pkg-info epl ]; + }); + + rich-minority = melpa.mkDerivation (self: { + pname = "rich-minority"; + version = "0.1.1"; + src = fetchFromGitHub { + owner = "Bruce-Connor"; + repo = self.pname; + rev = self.version; + sha256 = "0kvhy4mgs9llihwsb1a9n5a85xzjiyiyawxnz0axy2bvwcxnp20k"; + }; + packageRequires = [ dash ]; + }); + + s = melpa.mkDerivation (self: { + pname = "s"; + version = "20140910"; + src = fetchFromGitHub { + owner = "magnars"; + repo = "${self.pname}.el"; + rev = "1f85b5112f3f68169ddaa2911fcfa030f979eb4d"; + sha256 = "9d871ea84f98c51099528a03eddf47218cf70f1431d4c35c19c977d9e73d421f"; + }; + }); + + shorten = melpa.mkDerivation (self: { + pname = "shorten"; + version = "1.5"; + src = fetchFromGitHub { + owner = "jorgenschaefer"; + repo = "circe"; + rev = "v${self.version}"; + sha256 = "08dsv1dzgb9jx076ia7xbpyjpaxn1w87h6rzlb349spaydq7ih24"; + }; + fileSpecs = [ "lisp/shorten*.el" ]; + }); + + smart-mode-line = melpa.mkDerivation (self: { + pname = "smart-mode-line"; + version = "2.6"; + src = fetchFromGitHub { + owner = "Bruce-Connor"; + repo = self.pname; + rev = self.version; + sha256 = "17nav2jbvbd13xzgp29x396mc617n2dh6whjk4wnyvsyv7r0s9f6"; + }; + packageRequires = [ dash rich-minority ]; + }); + + smartparens = melpa.mkDerivation (self: { + pname = "smartparens"; + version = "1.6.2"; + src = fetchFromGitHub { + owner = "Fuco1"; + repo = self.pname; + rev = self.version; + sha256 = "16pzd740vd1r3qfmxia2ibiarinm6xpja0mjv3nni5dis5s4r9gc"; + }; + packageRequires = [ dash ]; + }); + + structured-haskell-mode = melpa.mkDerivation (self: { + pname = "shm"; + version = external.structured-haskell-mode.version; + src = external.structured-haskell-mode.src; + packageRequires = [ haskell-mode ]; + fileSpecs = [ "elisp/*.el" ]; + meta = { + homepage = "https://github.com/chrisdone/structured-haskell-mode"; + description = "Structured editing Emacs mode for Haskell"; + license = self.stdenv.lib.licenses.bsd3; + platforms = self.ghc.meta.platforms; + }; + }); + + switch-window = melpa.mkDerivation (self: { + pname = "switch-window"; + version = "20140919"; + src = fetchFromGitHub { + owner = "dimitri"; + repo = self.pname; + rev = "3ffbe68e584f811e891f96afa1de15e0d9c1ebb5"; + sha256 = "09221128a0f55a575ed9addb3a435cfe01ab6bdd0cca5d589ccd37de61ceccbd"; + }; + }); + + tracking = melpa.mkDerivation (self: { + pname = "tracking"; + version = "1.5"; + src = fetchFromGitHub { + owner = "jorgenschaefer"; + repo = "circe"; + rev = "v${self.version}"; + sha256 = "08dsv1dzgb9jx076ia7xbpyjpaxn1w87h6rzlb349spaydq7ih24"; + }; + packageRequires = [ shorten ]; + fileSpecs = [ "lisp/tracking*.el" ]; + }); + + undo-tree = melpa.mkDerivation (self: { + pname = "undo-tree"; + version = "0.6.4"; + src = fetchgit { + url = "http://www.dr-qubit.org/git/${self.pname}.git"; + rev = "a3e81b682053a81e082139300ef0a913a7a610a2"; + sha256 = "1qla7njkb7gx5aj87i8x6ni8jfk1k78ivwfiiws3gpbnyiydpx8y"; + }; + }); + + use-package = melpa.mkDerivation (self: { + pname = "use-package"; + version = "20141013"; + src = fetchFromGitHub { + owner = "jwiegley"; + repo = self.pname; + rev = "d43af5e0769a92f77e01dea229e376d9006722ef"; + sha256 = "1m4v5h52brg2g9rpbqfq9m3m8fv520vg5mjwppnbw6099d17msqd"; + }; + packageRequires = [ bind-key diminish ]; + files = [ "use-package.el" ]; + }); + + volatile-highlights = melpa.mkDerivation (self: { + pname = "volatile-highlights"; + version = "1.11"; + src = fetchFromGitHub { + owner = "k-talo"; + repo = "${self.pname}.el"; + rev = "fb2abc2d4d4051a9a6b7c8de2fe7564161f01f24"; + sha256 = "1v0chqj5jir4685jd8ahw86g9zdmi6xd05wmzhyw20rbk924fcqf"; + }; + }); + + weechat = melpa.mkDerivation (self: { + pname = "weechat.el"; + version = "20141016"; + src = fetchFromGitHub { + owner = "the-kenny"; + repo = self.pname; + rev = "4cb2ced1eda5167ce774e04657d2cd077b63c706"; + sha256 = "003sihp7irm0qqba778dx0gf8xhkxd1xk7ig5kgkryvl2jyirk28"; + }; + postPatch = stdenv.lib.optionalString (!stdenv.isLinux) '' + rm weechat-sauron.el weechat-secrets.el + ''; + packageRequires = [ s ]; + }); + + wgrep = melpa.mkDerivation (self: { + pname = "wgrep"; + version = "20141017"; + src = fetchFromGitHub { + owner = "mhayashi1120"; + repo = "Emacs-wgrep"; + rev = "7ef26c51feaef8a5ec0929737130ab8ba326983c"; + sha256 = "075z0glain0dp56d0cp468y5y88wn82ab26aapsrdzq8hmlshwn4"; + }; + }); +} diff --git a/pkgs/applications/editors/emacs-24/packages/melpa.nix b/pkgs/applications/editors/emacs-24/packages/melpa.nix new file mode 100644 index 000000000000..45d97b74e517 --- /dev/null +++ b/pkgs/applications/editors/emacs-24/packages/melpa.nix @@ -0,0 +1,99 @@ +# generic builder for Emacs packages + +{ stdenv, fetchurl, emacs, texinfo +, extension ? (self : super : {}) +}: + +let + enableFeature = stdenv.lib.enableFeature; + versionOlder = stdenv.lib.versionOlder; + optional = stdenv.lib.optional; + optionals = stdenv.lib.optionals; + optionalString = stdenv.lib.optionalString; + filter = stdenv.lib.filter; + +in + +{ + mkDerivation = + args : # arguments for the individual package, can modify the defaults + let # Stuff happening after the user preferences have been processed. We remove + # internal attributes and strip null elements from the dependency lists, all + # in the interest of keeping hashes stable. + postprocess = + x : x // { + buildInputs = filter (y : ! (y == null)) x.buildInputs; + propagatedBuildInputs = filter (y : ! (y == null)) x.propagatedBuildInputs; + propagatedUserEnvPkgs = filter (y : ! (y == null)) x.propagatedUserEnvPkgs; + doCheck = x.doCheck; + }; + + defaults = + self : { # self is the final version of the attribute set + + # pname should be defined by the client to be the package basename + # version should be defined by the client to be the package version + + # fname is the internal full name of the package + fname = "${self.pname}-${self.version}"; + + # name is the external full name of the package; usually we prefix + # all packages with emacs- to avoid name clashes for libraries; + # if that is not desired (for applications), name can be set to + # fname. + name = "emacs-${self.pname}-${self.version}"; + + + buildInputs = [emacs texinfo] ++ self.packageRequires; + + propagatedBuildInputs = self.packageRequires; + + propagatedUserEnvPkgs = self.packageRequires; + + packageRequires = []; + + doCheck = false; + + files = []; + + setupHook = ./setup-hook.sh; + + fileSpecs = [ "*.el" "*.el.in" "dir" + "*.info" "*.texi" "*.texinfo" + "doc/dir" "doc/*.info" "doc/*.texi" "doc/*.texinfo" + ]; + + targets = stdenv.lib.concatStringsSep " " + (if self.files == [] + then self.fileSpecs + else self.files); + + buildPhase = '' + eval "$preBuild" + + emacs --batch -Q -l ${./package-build.el} -l ${./melpa2nix.el} \ + -f melpa2nix-build-package \ + ${self.pname} ${self.version} ${self.targets} + + eval "$postBuild" + ''; + + installPhase = '' + eval "$preInstall" + + emacs --batch -Q -l ${./package-build.el} -l ${./melpa2nix.el} \ + -f melpa2nix-install-package \ + ${self.fname}.* $out/share/emacs/site-lisp/elpa + + eval "$postInstall" + ''; + + # We inherit stdenv and emacs so that they can be used + # in melpa derivations. + inherit stdenv emacs texinfo; + }; + in + stdenv.mkDerivation (postprocess (let super = defaults self // args self; + self = super // extension self super; + in self)); +} diff --git a/pkgs/applications/editors/emacs-24/packages/melpa2nix.el b/pkgs/applications/editors/emacs-24/packages/melpa2nix.el new file mode 100644 index 000000000000..d54e9d21e25e --- /dev/null +++ b/pkgs/applications/editors/emacs-24/packages/melpa2nix.el @@ -0,0 +1,36 @@ +(require 'package) +(package-initialize) + +(require 'package-build) + +(setq package-build-working-dir (expand-file-name ".") + package-build-archive-dir (expand-file-name ".")) + +(defun melpa2nix-install-package () + (if (not noninteractive) + (error "`melpa2nix-install-package' is to be used only with -batch")) + (pcase command-line-args-left + (`(,archive ,elpa) + (progn (setq package-user-dir elpa) + (package-install-file archive))))) + +(defun melpa2nix-build-package () + (if (not noninteractive) + (error "`melpa2nix-build-package' is to be used only with -batch")) + (pcase command-line-args-left + (`(,package ,version . ,files) + (melpa2nix-package-build-archive package version files)))) + +(defun melpa2nix-package-build-archive (name version files) + "Build a package archive for package NAME." + (pb/message "\n;;; %s\n" name) + (let* ((start-time (current-time)) + (archive-entry (package-build-package name + version + files + package-build-working-dir + package-build-archive-dir))) + + (pb/message "Built in %.3fs, finished at %s" + (time-to-seconds (time-since start-time)) + (current-time-string)))) diff --git a/pkgs/applications/editors/emacs-24/packages/package-build.el b/pkgs/applications/editors/emacs-24/packages/package-build.el new file mode 100644 index 000000000000..c4554de83668 --- /dev/null +++ b/pkgs/applications/editors/emacs-24/packages/package-build.el @@ -0,0 +1,1333 @@ +;;; package-build.el --- Tools for assembling a package archive + +;; Copyright (C) 2011-2013 Donald Ephraim Curtis <dcurtis@milkbox.net> +;; Copyright (C) 2012-2014 Steve Purcell <steve@sanityinc.com> +;; Copyright (C) 2009 Phil Hagelberg <technomancy@gmail.com> + +;; Author: Donald Ephraim Curtis <dcurtis@milkbox.net> +;; Created: 2011-09-30 +;; Version: 0.1 +;; Keywords: tools +;; Package-Requires: ((cl-lib "0.2")) + +;; This file is not (yet) part of GNU Emacs. +;; However, it is distributed under the same license. + +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; This file allows a curator to publish an archive of Emacs packages. + +;; The archive is generated from a set of recipes which describe elisp +;; projects and repositories from which to get them. The term +;; "package" here is used to mean a specific version of a project that +;; is prepared for download and installation. + +;;; Code: + +(require 'cl-lib) + +(require 'package) +(require 'lisp-mnt) +(require 'json) + +(defconst pb/this-dir (file-name-directory (or load-file-name (buffer-file-name)))) + +(defgroup package-build nil + "Facilities for building package.el-compliant packages from upstream source code." + :group 'development) + +(defcustom package-build-working-dir (expand-file-name "working/" pb/this-dir) + "Directory in which to keep checkouts." + :group 'package-build + :type 'string) + +(defcustom package-build-archive-dir (expand-file-name "packages/" pb/this-dir) + "Directory in which to keep compiled archives." + :group 'package-build + :type 'string) + +(defcustom package-build-recipes-dir (expand-file-name "recipes/" pb/this-dir) + "Directory containing recipe files." + :group 'package-build + :type 'string) + +(defcustom package-build-verbose t + "When non-nil, `package-build' feels free to print information about its progress." + :group 'package-build + :type 'boolean) + +(defcustom package-build-stable nil + "When non-nil, `package-build' tries to build packages from versions-tagged code." + :group 'package-build + :type 'boolean) + +(defcustom package-build-timeout-executable + (let ((prog (or (executable-find "timeout") + (executable-find "gtimeout")))) + (when (and prog + (string-match-p "^ *-k" (shell-command-to-string (concat prog " --help")))) + prog)) + "Path to a GNU coreutils \"timeout\" command if available. +This must be a version which supports the \"-k\" option." + :group 'package-build + :type '(file :must-match t)) + +(defcustom package-build-tar-executable + (or (executable-find "gtar") + (executable-find "tar")) + "Path to a (preferably GNU) tar command. +Certain package names (e.g. \"@\") may not work properly with a BSD tar." + :group 'package-build + :type '(file :must-match t)) + +(defcustom package-build-write-melpa-badge-images nil + "When non-nil, write MELPA badge images alongside packages, for use on github pages etc." + :group 'package-build + :type 'boolean) + +;;; Internal Variables + +(defvar pb/recipe-alist nil + "Internal list of package build specs. + +Do not use this directly. Use `package-build-recipe-alist' +function.") + +(defvar pb/recipe-alist-initialized nil + "Determines if `pb/recipe-alist` has been initialized.") + +(defvar pb/archive-alist nil + "Internal list of already-built packages, in the standard package.el format. + +Do not use this directly. Use `package-build-archive-alist' +function for access to this function") + +(defvar pb/archive-alist-initialized nil + "Determines if pb/archive-alist has been initialized.") + +(defconst package-build-default-files-spec + '("*.el" "*.el.in" "dir" + "*.info" "*.texi" "*.texinfo" + "doc/dir" "doc/*.info" "doc/*.texi" "doc/*.texinfo" + (:exclude ".dir-locals.el" "tests.el" "*-test.el" "*-tests.el")) + "Default value for :files attribute in recipes.") + +(defun pb/message (format-string &rest args) + "Log a message using FORMAT-STRING and ARGS as per `message'." + (when package-build-verbose + (apply 'message format-string args))) + +(defun pb/slurp-file (file-name) + "Return the contents of FILE-NAME as a string, or nil if no such file exists." + (when (file-exists-p file-name) + (with-temp-buffer + (insert-file-contents file-name) + (buffer-substring-no-properties (point-min) (point-max))))) + +(defun pb/string-rtrim (str) + "Remove trailing whitespace from `STR'." + (replace-regexp-in-string "[ \t\n]*$" "" str)) + +(defun pb/parse-time (str) + "Parse STR as a time, and format as a YYYYMMDD.HHMM string." + ;; We remove zero-padding the HH portion, as it is lost + ;; when stored in the archive-contents + (let* ((s (substring-no-properties str)) + (time (date-to-time + (if (string-match "^\\([0-9]\\{4\\}\\)/\\([0-9]\\{2\\}\\)/\\([0-9]\\{2\\}\\) \\([0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\)$" str) + (concat (match-string 1 str) "-" (match-string 2 str) "-" + (match-string 3 str) " " (match-string 4 str)) + str)))) + (concat (format-time-string "%Y%m%d." time) + (format "%d" (or (string-to-number (format-time-string "%H%M" time)) 0))))) + +(defun pb/string-match-all (regex str &rest groups) + "Find every match for `REGEX' within `STR'. +Return a list containing the full match string and match for +groups `GROUPS'. The return list is of the form + ((FULL GROUP1 GROUP2 ...) ...) +where FULL is the complete regexp match and +GROUP1, GROUP2, ... are the regex groups specified by the +`GROUPS' argument. If `GROUPS' is nil then FULL and GROUP1 will +be identical." + (let (result + (pos 0) + (groups (or groups '(0)))) + (while (string-match regex str pos) + (push (cons (match-string 0 str) (mapcar + (lambda (group) + (match-string group str)) + groups)) + result) + (setq pos (match-end 0))) + result)) + +(defun pb/find-parse-time (regex &optional bound) + "Find REGEX in current buffer and format as a time version, optionally looking only as far as BOUND." + (pb/parse-time (progn (re-search-backward regex bound) + (match-string-no-properties 1)))) + +(defun pb/valid-version-string (str) + "Return true if STR is a valid version, otherwise return nil." + (ignore-errors (version-to-list str))) + +(defun pb/find-tag-version-newest (regex &optional bound &rest additional-groups) + "Find the newest version matching REGEX, optionally looking only as far as BOUND." + (let* ((text (buffer-substring-no-properties + (or bound (point-min)) (point))) + (tags (cl-remove-if-not + (lambda (tag-version) + (pb/valid-version-string (cadr tag-version))) + (apply 'pb/string-match-all regex text 1 additional-groups)))) + (car (nreverse (sort tags (lambda (v1 v2) + (version< (cadr v1) (cadr v2)))))))) + +(defun pb/find-parse-time-latest (regex &optional bound) + "Find the latest timestamp matching REGEX, optionally looking only as far as BOUND." + (let* ((text (buffer-substring-no-properties + (or bound (point-min)) (point))) + (times (mapcar 'pb/parse-time + (mapcar 'cadr (pb/string-match-all regex text 1))))) + (car (nreverse (sort times 'string<))))) + +(defun pb/run-process (dir command &rest args) + "In DIR (or `default-directory' if unset) run COMMAND with ARGS. +Output is written to the current buffer." + (let* ((default-directory (file-name-as-directory (or dir default-directory))) + (argv (if package-build-timeout-executable + (append (list package-build-timeout-executable "-k" "60" "600" command) args) + (cons command args)))) + (unless (file-directory-p default-directory) + (error "Can't run process in non-existent directory: %s" default-directory)) + (let ((exit-code (apply 'process-file (car argv) nil (current-buffer) t (cdr argv)))) + (or (zerop exit-code) + (error "Command '%s' exited with non-zero status %d: %s" + argv exit-code (buffer-string)))))) + +(defun pb/run-process-match (regex dir prog &rest args) + "Find match for REGEX when - in DIR, or `default-directory' if unset - we run PROG with ARGS." + (with-temp-buffer + (apply 'pb/run-process dir prog args) + (goto-char (point-min)) + (re-search-forward regex) + (match-string-no-properties 1))) + +(defun package-build-checkout (package-name config working-dir) + "Check out source for PACKAGE-NAME with CONFIG under WORKING-DIR. +In turn, this function uses the :fetcher option in the CONFIG to +choose a source-specific fetcher function, which it calls with +the same arguments. + +Returns a last-modification timestamp for the :files listed in +CONFIG, if any, or `package-build-default-files-spec' otherwise." + (let ((repo-type (plist-get config :fetcher))) + (pb/message "Fetcher: %s" repo-type) + (unless (eq 'wiki repo-type) + (pb/message "Source: %s\n" (or (plist-get config :repo) (plist-get config :url)))) + (funcall (intern (format "pb/checkout-%s" repo-type)) + package-name config (file-name-as-directory working-dir)))) + +(defvar pb/last-wiki-fetch-time 0 + "The time at which an emacswiki URL was last requested. +This is used to avoid exceeding the rate limit of 1 request per 2 +seconds; the server cuts off after 10 requests in 20 seconds.") + +(defvar pb/wiki-min-request-interval 3 + "The shortest permissible interval between successive requests for Emacswiki URLs.") + +(defmacro pb/with-wiki-rate-limit (&rest body) + "Rate-limit BODY code passed to this macro to match EmacsWiki's rate limiting." + (let ((now (cl-gensym)) + (elapsed (cl-gensym))) + `(let* ((,now (float-time)) + (,elapsed (- ,now pb/last-wiki-fetch-time))) + (when (< ,elapsed pb/wiki-min-request-interval) + (let ((wait (- pb/wiki-min-request-interval ,elapsed))) + (pb/message "Waiting %.2f secs before hitting Emacswiki again" wait) + (sleep-for wait))) + (unwind-protect + (progn ,@body) + (setq pb/last-wiki-fetch-time (float-time)))))) + +(defun pb/grab-wiki-file (filename) + "Download FILENAME from emacswiki, returning its last-modified time." + (let* ((download-url + (format "http://www.emacswiki.org/emacs/download/%s" filename)) + (wiki-url + (format "http://www.emacswiki.org/emacs/%s" filename))) + (pb/with-wiki-rate-limit + (url-copy-file download-url filename t)) + (when (zerop (nth 7 (file-attributes filename))) + (error "Wiki file %s was empty - has it been removed?" filename)) + ;; The Last-Modified response header for the download is actually + ;; correct for the file, but we have no access to that + ;; header. Instead, we must query the non-raw emacswiki page for + ;; the file. + ;; Since those Emacswiki lookups are time-consuming, we maintain a + ;; foo.el.stamp file containing ("SHA1" . "PARSED_TIME") + (let* ((new-content-hash (secure-hash 'sha1 (pb/slurp-file filename))) + (stamp-file (concat filename ".stamp")) + (stamp-info (pb/read-from-file stamp-file)) + (prev-content-hash (car stamp-info))) + (if (and prev-content-hash + (string-equal new-content-hash prev-content-hash)) + ;; File has not changed, so return old timestamp + (progn + (pb/message "%s is unchanged" filename) + (cdr stamp-info)) + (pb/message "%s has changed - checking mod time" filename) + (let ((new-timestamp + (with-current-buffer (pb/with-wiki-rate-limit + (url-retrieve-synchronously wiki-url)) + (unless (= 200 url-http-response-status) + (error "HTTP error %s fetching %s" url-http-response-status wiki-url)) + (goto-char (point-max)) + (pb/find-parse-time + "Last edited \\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\} [A-Z]\\{3\\}\\)" + url-http-end-of-headers)))) + (pb/dump (cons new-content-hash new-timestamp) stamp-file) + new-timestamp))))) + +(defun pb/checkout-wiki (name config dir) + "Checkout package NAME with config CONFIG from the EmacsWiki into DIR." + (unless package-build-stable + (with-current-buffer (get-buffer-create "*package-build-checkout*") + (unless (file-exists-p dir) + (make-directory dir)) + (let ((files (or (plist-get config :files) + (list (format "%s.el" name)))) + (default-directory dir)) + (car (nreverse (sort (mapcar 'pb/grab-wiki-file files) 'string-lessp))))))) + +(defun pb/darcs-repo (dir) + "Get the current darcs repo for DIR." + (pb/run-process-match "Default Remote: \\(.*\\)" dir "darcs" "show" "repo")) + +(defun pb/checkout-darcs (name config dir) + "Check package NAME with config CONFIG out of darcs into DIR." + (unless package-build-stable + (let ((repo (plist-get config :url))) + (with-current-buffer (get-buffer-create "*package-build-checkout*") + (cond + ((and (file-exists-p (expand-file-name "_darcs" dir)) + (string-equal (pb/darcs-repo dir) repo)) + (pb/princ-exists dir) + (pb/run-process dir "darcs" "pull")) + (t + (when (file-exists-p dir) + (delete-directory dir t)) + (pb/princ-checkout repo dir) + (pb/run-process nil "darcs" "get" repo dir))) + (apply 'pb/run-process dir "darcs" "changes" "--max-count" "1" + (pb/expand-source-file-list dir config)) + (pb/find-parse-time + "\\([a-zA-Z]\\{3\\} [a-zA-Z]\\{3\\} \\( \\|[0-9]\\)[0-9] [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\} [A-Za-z]\\{3\\} [0-9]\\{4\\}\\)"))))) + +(defun pb/fossil-repo (dir) + "Get the current fossil repo for DIR." + (pb/run-process-match "\\(.*\\)" dir "fossil" "remote-url")) + +(defun pb/checkout-fossil (name config dir) + "Check package NAME with config CONFIG out of fossil into DIR." + (unless package-build-stable + (let ((repo (plist-get config :url))) + (with-current-buffer (get-buffer-create "*package-build-checkout*") + (cond + ((and (or (file-exists-p (expand-file-name ".fslckout" dir)) + (file-exists-p (expand-file-name "_FOSSIL_" dir))) + (string-equal (pb/fossil-repo dir) repo)) + (pb/princ-exists dir) + (pb/run-process dir "fossil" "update")) + (t + (when (file-exists-p dir) + (delete-directory dir t)) + (pb/princ-checkout repo dir) + (make-directory dir) + (pb/run-process dir "fossil" "clone" repo "repo.fossil") + (pb/run-process dir "fossil" "open" "repo.fossil"))) + (pb/run-process dir "fossil" "timeline" "-n" "1" "-t" "ci") + (or (pb/find-parse-time + "=== \\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} ===\n[0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\) ") + (error "No valid timestamps found!")))))) + +(defun pb/svn-repo (dir) + "Get the current svn repo for DIR." + (pb/run-process-match "URL: \\(.*\\)" dir "svn" "info")) + +(defun pb/trim (str &optional chr) + "Return a copy of STR without any trailing CHR (or space if unspecified)." + (if (equal (elt str (1- (length str))) (or chr ? )) + (substring str 0 (1- (length str))) + str)) + +(defun pb/princ-exists (dir) + "Print a message that the contents of DIR will be updated." + (pb/message "Updating %s" dir)) + +(defun pb/princ-checkout (repo dir) + "Print a message that REPO will be checked out into DIR." + (pb/message "Cloning %s to %s" repo dir)) + +(defun pb/checkout-svn (name config dir) + "Check package NAME with config CONFIG out of svn into DIR." + (unless package-build-stable + (with-current-buffer (get-buffer-create "*package-build-checkout*") + (let ((repo (pb/trim (plist-get config :url) ?/)) + (bound (goto-char (point-max)))) + (cond + ((and (file-exists-p (expand-file-name ".svn" dir)) + (string-equal (pb/svn-repo dir) repo)) + (pb/princ-exists dir) + (pb/run-process dir "svn" "up")) + (t + (when (file-exists-p dir) + (delete-directory dir t)) + (pb/princ-checkout repo dir) + (pb/run-process nil "svn" "checkout" repo dir))) + (apply 'pb/run-process dir "svn" "info" + (pb/expand-source-file-list dir config)) + (or (pb/find-parse-time-latest "Last Changed Date: \\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\( [+-][0-9]\\{4\\}\\)?\\)" bound) + (error "No valid timestamps found!")))))) + + +(defun pb/cvs-repo (dir) + "Get the current CVS root and repository for DIR. + +Return a cons cell whose `car' is the root and whose `cdr' is the repository." + (apply 'cons + (mapcar (lambda (file) + (pb/string-rtrim (pb/slurp-file (expand-file-name file dir)))) + '("CVS/Root" "CVS/Repository")))) + +(defun pb/checkout-cvs (name config dir) + "Check package NAME with config CONFIG out of cvs into DIR." + (unless package-build-stable + (with-current-buffer (get-buffer-create "*package-build-checkout*") + (let ((root (pb/trim (plist-get config :url) ?/)) + (repo (or (plist-get config :module) (symbol-name name))) + (bound (goto-char (point-max)))) + (cond + ((and (file-exists-p (expand-file-name "CVS" dir)) + (equal (pb/cvs-repo dir) (cons root repo))) + (pb/princ-exists dir) + (pb/run-process dir "cvs" "update" "-dP")) + (t + (when (file-exists-p dir) + (delete-directory dir t)) + (pb/princ-checkout (format "%s from %s" repo root) dir) + ;; CVS insists on relative paths as target directory for checkout (for + ;; whatever reason), and puts "CVS" directories into every subdirectory + ;; of the current working directory given in the target path. To get CVS + ;; to just write to DIR, we need to execute CVS from the parent + ;; directory of DIR, and specific DIR as relative path. Hence all the + ;; following mucking around with paths. CVS is really horrid. + (let* ((dir (directory-file-name dir)) + (working-dir (file-name-directory dir)) + (target-dir (file-name-nondirectory dir))) + (pb/run-process working-dir "env" "TZ=UTC" "cvs" "-z3" "-d" root "checkout" + "-d" target-dir repo)))) + (apply 'pb/run-process dir "cvs" "log" + (pb/expand-source-file-list dir config)) + (or (pb/find-parse-time-latest "date: \\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\( [+-][0-9]\\{4\\}\\)?\\)" bound) + (pb/find-parse-time-latest "date: \\([0-9]\\{4\\}/[0-9]\\{2\\}/[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\);" bound) + (error "No valid timestamps found!")) + )))) + + +(defun pb/git-repo (dir) + "Get the current git repo for DIR." + (pb/run-process-match + "Fetch URL: \\(.*\\)" dir "git" "remote" "show" "-n" "origin")) + +(defun pb/git-head-branch (dir) + "Get the current git repo for DIR." + (or (ignore-errors + (pb/run-process-match + "HEAD branch: \\(.*\\)" dir "git" "remote" "show" "origin")) + "master")) + +(defun pb/checkout-git (name config dir) + "Check package NAME with config CONFIG out of git into DIR." + (let ((repo (plist-get config :url)) + (commit (or (plist-get config :commit) + (let ((branch (plist-get config :branch))) + (when branch + (concat "origin/" branch)))))) + (with-current-buffer (get-buffer-create "*package-build-checkout*") + (goto-char (point-max)) + (cond + ((and (file-exists-p (expand-file-name ".git" dir)) + (string-equal (pb/git-repo dir) repo)) + (pb/princ-exists dir) + (pb/run-process dir "git" "remote" "update")) + (t + (when (file-exists-p dir) + (delete-directory dir t)) + (pb/princ-checkout repo dir) + (pb/run-process nil "git" "clone" repo dir))) + (if package-build-stable + (let* ((bound (goto-char (point-max))) + (tag-version (and (pb/run-process dir "git" "tag") + (or (pb/find-tag-version-newest + "^\\(?:v[.-]?\\)?\\([0-9]+[^ \t\n]*\\)$" bound) + (error + "No valid stable versions found for %s" + name))))) + ;; Using reset --hard here to comply with what's used for + ;; unstable, but maybe this should be a checkout? + (pb/run-process dir "git" "reset" "--hard" (concat "tags/" (car tag-version))) + (pb/run-process dir "git" "submodule" "update" "--init" "--recursive") + (cadr tag-version)) + (pb/run-process dir "git" "reset" "--hard" + (or commit (concat "origin/" (pb/git-head-branch dir)))) + (pb/run-process dir "git" "submodule" "update" "--init" "--recursive") + (apply 'pb/run-process dir "git" "log" "--first-parent" "-n1" "--pretty=format:'\%ci'" + (pb/expand-source-file-list dir config)) + (pb/find-parse-time + "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\( [+-][0-9]\\{4\\}\\)?\\)"))))) + +(defun pb/checkout-github (name config dir) + "Check package NAME with config CONFIG out of github into DIR." + (let* ((url (format "git://github.com/%s.git" (plist-get config :repo)))) + (pb/checkout-git name (plist-put (copy-sequence config) :url url) dir))) + +(defun pb/bzr-expand-repo (repo) + "Get REPO expanded name." + (pb/run-process-match "\\(?:branch root\\|repository branch\\): \\(.*\\)" nil "bzr" "info" repo)) + +(defun pb/bzr-repo (dir) + "Get the current bzr repo for DIR." + (pb/run-process-match "parent branch: \\(.*\\)" dir "bzr" "info")) + +(defun pb/checkout-bzr (name config dir) + "Check package NAME with config CONFIG out of bzr into DIR." + (unless package-build-stable + (let ((repo (pb/bzr-expand-repo (plist-get config :url)))) + (with-current-buffer (get-buffer-create "*package-build-checkout*") + (goto-char (point-max)) + (cond + ((and (file-exists-p (expand-file-name ".bzr" dir)) + (string-equal (pb/bzr-repo dir) repo)) + (pb/princ-exists dir) + (pb/run-process dir "bzr" "merge")) + (t + (when (file-exists-p dir) + (delete-directory dir t)) + (pb/princ-checkout repo dir) + (pb/run-process nil "bzr" "branch" repo dir))) + (apply 'pb/run-process dir "bzr" "log" "-l1" + (pb/expand-source-file-list dir config)) + (pb/find-parse-time + "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\( [+-][0-9]\\{4\\}\\)?\\)"))))) + +(defun pb/hg-repo (dir) + "Get the current hg repo for DIR." + (pb/run-process-match "default = \\(.*\\)" dir "hg" "paths")) + +(defun pb/checkout-hg (name config dir) + "Check package NAME with config CONFIG out of hg into DIR." + (let ((repo (plist-get config :url))) + (with-current-buffer (get-buffer-create "*package-build-checkout*") + (goto-char (point-max)) + (cond + ((and (file-exists-p (expand-file-name ".hg" dir)) + (string-equal (pb/hg-repo dir) repo)) + (pb/princ-exists dir) + (pb/run-process dir "hg" "pull") + (pb/run-process dir "hg" "update")) + (t + (when (file-exists-p dir) + (delete-directory dir t)) + (pb/princ-checkout repo dir) + (pb/run-process nil "hg" "clone" repo dir))) + (if package-build-stable + (let* ((bound (goto-char (point-max))) + (tag-version (and (pb/run-process dir "hg" "tags") + (or (pb/find-tag-version-newest + "^\\(?:v[.-]?\\)?\\([0-9]+[^ \t\n]*\\)[ \t]*[0-9]+:\\([[:xdigit:]]+\\)$" + bound + 2) + (error + "No valid stable versions found for %s" + name))))) + (pb/run-process dir "hg" "update" (nth 2 tag-version)) + (cadr tag-version)) + (apply 'pb/run-process dir "hg" "log" "--style" "compact" "-l1" + (pb/expand-source-file-list dir config)) + (pb/find-parse-time + "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}\\( [+-][0-9]\\{4\\}\\)?\\)"))))) + +(defun pb/dump (data file &optional pretty-print) + "Write DATA to FILE as a Lisp sexp. +Optionally PRETTY-PRINT the data." + (with-temp-file file + (pb/message "File: %s" file) + (if pretty-print + (pp data (current-buffer)) + (print data (current-buffer))))) + +(defun pb/write-pkg-file (pkg-file pkg-info) + "Write PKG-FILE containing PKG-INFO." + (with-temp-file pkg-file + (pp + `(define-package + ,(aref pkg-info 0) + ,(aref pkg-info 3) + ,(aref pkg-info 2) + ',(mapcar + (lambda (elt) + (list (car elt) + (package-version-join (cadr elt)))) + (aref pkg-info 1)) + ;; Append our extra information + ,@(apply #'append (mapcar (lambda (entry) + (let ((value (cdr entry))) + (when (or (symbolp value) (listp value)) + ;; We must quote lists and symbols, + ;; because Emacs 24.3 and earlier evaluate + ;; the package information, which would + ;; break for unquoted symbols or lists + (setq value (list 'quote value))) + (list (car entry) value))) + (when (> (length pkg-info) 4) + (aref pkg-info 4))))) + (current-buffer)) + (princ ";; Local Variables:\n;; no-byte-compile: t\n;; End:\n" (current-buffer)))) + +(defun pb/read-from-file (file-name) + "Read and return the Lisp data stored in FILE-NAME, or nil if no such file exists." + (when (file-exists-p file-name) + (car (read-from-string (pb/slurp-file file-name))))) + +(defun pb/create-tar (file dir &optional files) + "Create a tar FILE containing the contents of DIR, or just FILES if non-nil." + (apply 'process-file + package-build-tar-executable nil + (get-buffer-create "*package-build-checkout*") + nil "-cvf" + file + "--exclude=.svn" + "--exclude=CVS" + "--exclude=.git*" + "--exclude=_darcs" + "--exclude=.fslckout" + "--exclude=_FOSSIL_" + "--exclude=.bzr" + "--exclude=.hg" + (or (mapcar (lambda (fn) (concat dir "/" fn)) files) (list dir)))) + + +(defun pb/find-package-commentary (file-path) + "Get commentary section from FILE-PATH." + (when (file-exists-p file-path) + (with-temp-buffer + (insert-file-contents file-path) + (lm-commentary)))) + +(defun pb/write-pkg-readme (target-dir commentary file-name) + "In TARGET-DIR, write COMMENTARY to a -readme.txt file prefixed with FILE-NAME." + (when commentary + (with-temp-buffer + (insert commentary) + ;; Adapted from `describe-package-1'. + (goto-char (point-min)) + (save-excursion + (when (re-search-forward "^;;; Commentary:\n" nil t) + (replace-match "")) + (while (re-search-forward "^\\(;+ ?\\)" nil t) + (replace-match "")) + (goto-char (point-min)) + (when (re-search-forward "\\`\\( *\n\\)+" nil t) + (replace-match ""))) + (delete-trailing-whitespace) + (let ((coding-system-for-write buffer-file-coding-system)) + (write-region nil nil + (pb/readme-file-name target-dir file-name)))))) + +(defun pb/readme-file-name (target-dir file-name) + "Name of the readme file in TARGET-DIR for the package FILE-NAME." + (expand-file-name (concat file-name "-readme.txt") + target-dir)) + +(defun pb/update-or-insert-version (version) + "Ensure current buffer has a \"Version: VERSION\" header." + (goto-char (point-min)) + (if (re-search-forward "^;;;* *Version: *" nil t) + (progn + (move-beginning-of-line nil) + (search-forward "V" nil t) + (backward-char) + (insert "X-Original-") + (move-beginning-of-line nil)) + (forward-line)) + (insert (format ";; Version: %s" version)) + (newline)) + +(defun pb/ensure-ends-here-line (file-path) + "Add a 'FILE-PATH ends here' trailing line if missing." + (save-excursion + (goto-char (point-min)) + (let* ((fname (file-name-nondirectory file-path)) + (trailer (concat ";;; " fname " ends here"))) + (unless (search-forward trailer nil t) + (goto-char (point-max)) + (newline) + (insert trailer) + (newline))))) + +(defun pb/get-package-info (file-path) + "Get a vector of package info from the docstrings in FILE-PATH." + (when (file-exists-p file-path) + (ignore-errors + (with-temp-buffer + (insert-file-contents file-path) + ;; next few lines are a hack for some packages that aren't + ;; commented properly. + (pb/update-or-insert-version "0") + (pb/ensure-ends-here-line file-path) + (cl-flet ((package-strip-rcs-id (str) "0")) + (pb/package-buffer-info-vec)))))) + +(defun pb/get-pkg-file-info (file-path) + "Get a vector of package info from \"-pkg.el\" file FILE-PATH." + (when (file-exists-p file-path) + (let ((package-def (pb/read-from-file file-path))) + (if (eq 'define-package (car package-def)) + (let ((pkgfile-info (cdr package-def))) + (vector + (nth 0 pkgfile-info) + (mapcar + (lambda (elt) + (list (car elt) (version-to-list (cadr elt)))) + (eval (nth 3 pkgfile-info))) + (nth 2 pkgfile-info) + (nth 1 pkgfile-info))) + (error "No define-package found in %s" file-path))))) + +(defun pb/merge-package-info (pkg-info name version) + "Return a version of PKG-INFO updated with NAME, VERSION and info from CONFIG. +If PKG-INFO is nil, an empty one is created." + (let* ((merged (or (copy-sequence pkg-info) + (vector name nil "No description available." version)))) + (aset merged 0 name) + (aset merged 3 version) + merged)) + +(defun pb/archive-entry (pkg-info type) + "Return the archive-contents cons cell for PKG-INFO and TYPE." + (let* ((name (intern (aref pkg-info 0))) + (requires (aref pkg-info 1)) + (desc (or (aref pkg-info 2) "No description available.")) + (version (aref pkg-info 3)) + (extras (when (> (length pkg-info) 4) + (aref pkg-info 4)))) + (cons name + (vector (version-to-list version) + requires + desc + type + extras)))) + +(defun pb/archive-file-name (archive-entry) + "Return the path of the file in which the package for ARCHIVE-ENTRY is stored." + (let* ((name (car archive-entry)) + (pkg-info (cdr archive-entry)) + (version (package-version-join (aref pkg-info 0))) + (flavour (aref pkg-info 3))) + (expand-file-name + (format "%s-%s.%s" name version (if (eq flavour 'single) "el" "tar")) + package-build-archive-dir))) + +(defun pb/entry-file-name (archive-entry) + "Return the path of the file in which the package for ARCHIVE-ENTRY is stored." + (let* ((name (car archive-entry)) + (pkg-info (cdr archive-entry)) + (version (package-version-join (aref pkg-info 0)))) + (expand-file-name + (format "%s-%s.entry" name version) + package-build-archive-dir))) + +(defun pb/delete-file-if-exists (file) + "Delete FILE if it exists." + (when (file-exists-p file) + (delete-file file))) + +(defun pb/remove-archive-files (archive-entry) + "Remove ARCHIVE-ENTRY from archive-contents, and delete associated file. +Note that the working directory (if present) is not deleted by +this function, since the archive list may contain another version +of the same-named package which is to be kept." + (pb/message "Removing archive: %s" archive-entry) + (mapcar 'pb/delete-file-if-exists + (list (pb/archive-file-name archive-entry) + (pb/entry-file-name archive-entry)))) + +(defun pb/read-recipe (file-name) + "Return the plist of recipe info for the package called FILE-NAME." + (let ((pkg-info (pb/read-from-file file-name))) + (if (string= (symbol-name (car pkg-info)) + (file-name-nondirectory file-name)) + pkg-info + (error "Recipe '%s' contains mismatched package name '%s'" + (file-name-nondirectory file-name) + (car pkg-info))))) + +(defun pb/read-recipes () + "Return a list of data structures for all recipes in `package-build-recipes-dir'." + (cl-loop for file-name in (directory-files package-build-recipes-dir t "^[^.]") + collect (pb/read-recipe file-name))) + +(defun pb/read-recipes-ignore-errors () + "Return a list of data structures for all recipes in `package-build-recipes-dir'." + (cl-loop for file-name in (directory-files package-build-recipes-dir t "^[^.]") + for pkg-info = (condition-case err (pb/read-recipe file-name) + (error (pb/message "Error reading recipe %s: %s" + file-name + (error-message-string err)) + nil)) + when pkg-info + collect pkg-info)) + + +(defun package-build-expand-file-specs (dir specs &optional subdir allow-empty) + "In DIR, expand SPECS, optionally under SUBDIR. +The result is a list of (SOURCE . DEST), where SOURCE is a source +file path and DEST is the relative path to which it should be copied. + +If the resulting list is empty, an error will be reported. Pass t +for ALLOW-EMPTY to prevent this error." + (let ((default-directory dir) + (prefix (if subdir (format "%s/" subdir) "")) + (lst)) + (dolist (entry specs lst) + (setq lst + (if (consp entry) + (if (eq :exclude (car entry)) + (cl-nset-difference lst + (package-build-expand-file-specs dir (cdr entry) nil t) + :key 'car + :test 'equal) + (nconc lst + (package-build-expand-file-specs + dir + (cdr entry) + (concat prefix (car entry)) + t))) + (nconc + lst (mapcar (lambda (f) + (let ((destname))) + (cons f + (concat prefix + (replace-regexp-in-string + "\\.in\\'" + "" + (file-name-nondirectory f))))) + (file-expand-wildcards entry)))))) + (when (and (null lst) (not allow-empty)) + (error "No matching file(s) found in %s: %s" dir specs)) + lst)) + + +(defun pb/config-file-list (config) + "Get the :files spec from CONFIG, or return `package-build-default-files-spec'." + (or (plist-get config :files) package-build-default-files-spec)) + +(defun pb/expand-source-file-list (dir config) + "Shorthand way to expand paths in DIR for source files listed in CONFIG." + (mapcar 'car (package-build-expand-file-specs dir (pb/config-file-list config)))) + +(defun pb/generate-info-files (files source-dir target-dir) + "Create .info files from any .texi files listed in FILES. + +The source and destination file paths are expanded in SOURCE-DIR +and TARGET-DIR respectively. + +Any of the original .texi(nfo) files found in TARGET-DIR are +deleted." + (dolist (spec files) + (let* ((source-file (car spec)) + (source-path (expand-file-name source-file source-dir)) + (dest-file (cdr spec)) + (info-path (expand-file-name + (concat (file-name-sans-extension dest-file) ".info") + target-dir))) + (when (string-match ".texi\\(nfo\\)?$" source-file) + (when (not (file-exists-p info-path)) + (with-current-buffer (get-buffer-create "*package-build-info*") + (ignore-errors + (pb/run-process + (file-name-directory source-path) + "makeinfo" + source-path + "-o" + info-path) + (pb/message "Created %s" info-path)))) + (pb/message "Removing %s" (expand-file-name dest-file target-dir)) + (delete-file (expand-file-name dest-file target-dir)))))) + +(defun pb/generate-dir-file (files target-dir) + "Create dir file from any .info files listed in FILES in TARGET-DIR." + (dolist (spec files) + (let* ((source-file (car spec)) + (dest-file (cdr spec)) + (info-path (expand-file-name + (concat (file-name-sans-extension dest-file) ".info") + target-dir))) + (when (and (or (string-match ".info$" source-file) + (string-match ".texi\\(nfo\\)?$" source-file)) + (file-exists-p info-path)) + (with-current-buffer (get-buffer-create "*package-build-info*") + (ignore-errors + (pb/run-process + nil + "install-info" + (concat "--dir=" (expand-file-name "dir" target-dir)) + info-path))))))) + +(defun pb/copy-package-files (files source-dir target-dir) + "Copy FILES from SOURCE-DIR to TARGET-DIR. +FILES is a list of (SOURCE . DEST) relative filepath pairs." + (cl-loop for (source-file . dest-file) in files + do (pb/copy-file + (expand-file-name source-file source-dir) + (expand-file-name dest-file target-dir)))) + +(defun pb/copy-file (file newname) + "Copy FILE to NEWNAME and create parent directories for NEWNAME if they don't exist." + (let ((newdir (file-name-directory newname))) + (unless (file-exists-p newdir) + (make-directory newdir t))) + (cond + ((file-regular-p file) + (pb/message "%s -> %s" file newname) + (copy-file file newname)) + ((file-directory-p file) + (pb/message "%s => %s" file newname) + (copy-directory file newname)))) + + +(defun pb/package-name-completing-read () + "Prompt for a package name, returning a symbol." + (intern (completing-read "Package: " (package-build-recipe-alist)))) + +(defun pb/find-source-file (target files) + "Search for source of TARGET in FILES." + (let* ((entry (rassoc target files))) + (when entry (car entry)))) + +(defun pb/find-package-file (name) + "Return the filename of the most recently built package of NAME." + (pb/archive-file-name (assoc name (package-build-archive-alist)))) + +(defun pb/package-buffer-info-vec () + "Return a vector of package info. +`package-buffer-info' returns a vector in older Emacs versions, +and a cl struct in Emacs HEAD. This wrapper normalises the results." + (let ((desc (package-buffer-info)) + (keywords (lm-keywords-list))) + (if (fboundp 'package-desc-create) + (let ((extras (package-desc-extras desc))) + (when (and keywords (not (assq :keywords extras))) + ;; Add keywords to package properties, if not already present + (push (cons :keywords keywords) extras)) + (vector (package-desc-name desc) + (package-desc-reqs desc) + (package-desc-summary desc) + (package-desc-version desc) + extras)) + ;; The regexp and the processing is taken from `lm-homepage' in Emacs 24.4 + (let* ((page (lm-header "\\(?:x-\\)?\\(?:homepage\\|url\\)")) + (homepage (if (and page (string-match "^<.+>$" page)) + (substring page 1 -1) + page)) + extras) + (when keywords (push (cons :keywords keywords) extras)) + (when homepage (push (cons :url homepage) extras)) + (vector (aref desc 0) + (aref desc 1) + (aref desc 2) + (aref desc 3) + extras))))) + + +;;; Public interface +;;;###autoload +(defun package-build-archive (name) + "Build a package archive for package NAME." + (interactive (list (pb/package-name-completing-read))) + (let* ((file-name (symbol-name name)) + (rcp (or (cdr (assoc name (package-build-recipe-alist))) + (error "Cannot find package %s" file-name))) + (pkg-working-dir + (file-name-as-directory + (expand-file-name file-name package-build-working-dir)))) + + (unless (file-exists-p package-build-archive-dir) + (pb/message "Creating directory %s" package-build-archive-dir) + (make-directory package-build-archive-dir)) + + (pb/message "\n;;; %s\n" file-name) + (let* ((version (package-version-join + (version-to-list + (or (package-build-checkout name rcp pkg-working-dir) + (error "No valid package version found!"))))) + (default-directory package-build-working-dir) + (start-time (current-time)) + (archive-entry (package-build-package (symbol-name name) + version + (pb/config-file-list rcp) + pkg-working-dir + package-build-archive-dir))) + (pb/dump archive-entry (pb/entry-file-name archive-entry)) + (when package-build-write-melpa-badge-images + (pb/write-melpa-badge-image (symbol-name name) version package-build-archive-dir)) + (pb/message "Built in %.3fs, finished at %s" + (time-to-seconds (time-since start-time)) + (current-time-string)) + file-name))) + +;;;###autoload +(defun package-build-package (package-name version file-specs source-dir target-dir) + "Create PACKAGE-NAME with VERSION. + +The information in FILE-SPECS is used to gather files from +SOURCE-DIR. + +The resulting package will be stored as a .el or .tar file in +TARGET-DIR, depending on whether there are multiple files. + +Argument FILE-SPECS is a list of specs for source files, which +should be relative to SOURCE-DIR. The specs can be wildcards, +and optionally specify different target paths. They extended +syntax is currently only documented in the MELPA README. You can +simply pass `package-build-default-files-spec' in most cases. + +Returns the archive entry for the package." + (when (symbolp package-name) + (setq package-name (symbol-name package-name))) + (let ((files (package-build-expand-file-specs source-dir file-specs))) + (unless (equal file-specs package-build-default-files-spec) + (when (equal files (package-build-expand-file-specs + source-dir package-build-default-files-spec nil t)) + (pb/message "Note: this :files spec is equivalent to the default."))) + (cond + ((not version) + (error "Unable to check out repository for %s" package-name)) + ((= 1 (length files)) + (pb/build-single-file-package package-name version (caar files) source-dir target-dir)) + ((< 1 (length files)) + (pb/build-multi-file-package package-name version files source-dir target-dir)) + (t (error "Unable to find files matching recipe patterns"))))) + +(defun pb/build-single-file-package (package-name version file source-dir target-dir) + (let* ((pkg-source (expand-file-name file source-dir)) + (pkg-target (expand-file-name + (concat package-name "-" version ".el") + target-dir)) + (pkg-info (pb/merge-package-info + (pb/get-package-info pkg-source) + package-name + version))) + (unless (string-equal (downcase (concat package-name ".el")) + (downcase (file-name-nondirectory pkg-source))) + (error "Single file %s does not match package name %s" + (file-name-nondirectory pkg-source) package-name)) + (when (file-exists-p pkg-target) + (delete-file pkg-target)) + (copy-file pkg-source pkg-target) + (let ((enable-local-variables nil) + (make-backup-files nil)) + (with-current-buffer (find-file pkg-target) + (pb/update-or-insert-version version) + (pb/ensure-ends-here-line pkg-source) + (write-file pkg-target nil) + (condition-case err + (pb/package-buffer-info-vec) + (error + (pb/message "Warning: %S" err))) + (kill-buffer))) + + (pb/write-pkg-readme target-dir + (pb/find-package-commentary pkg-source) + package-name) + (pb/archive-entry pkg-info 'single))) + +(defun pb/build-multi-file-package (package-name version files source-dir target-dir) + (let* ((tmp-dir (file-name-as-directory (make-temp-file package-name t))) + (pkg-dir-name (concat package-name "-" version)) + (pkg-tmp-dir (expand-file-name pkg-dir-name tmp-dir)) + (pkg-file (concat package-name "-pkg.el")) + (pkg-file-source (or (pb/find-source-file pkg-file files) + pkg-file)) + (file-source (concat package-name ".el")) + (pkg-source (or (pb/find-source-file file-source files) + file-source)) + (pkg-info (pb/merge-package-info + (let ((default-directory source-dir)) + (or (pb/get-pkg-file-info pkg-file-source) + ;; some packages (like magit) provide name-pkg.el.in + (pb/get-pkg-file-info + (expand-file-name (concat pkg-file ".in") + (file-name-directory pkg-source))) + (pb/get-package-info pkg-source))) + package-name + version))) + (pb/copy-package-files files source-dir pkg-tmp-dir) + (pb/write-pkg-file (expand-file-name pkg-file + (file-name-as-directory pkg-tmp-dir)) + pkg-info) + + (pb/generate-info-files files source-dir pkg-tmp-dir) + (pb/generate-dir-file files pkg-tmp-dir) + + (let ((default-directory tmp-dir)) + (pb/create-tar (expand-file-name (concat package-name "-" version ".tar") + target-dir) + pkg-dir-name)) + + (let ((default-directory source-dir)) + (pb/write-pkg-readme target-dir + (pb/find-package-commentary pkg-source) + package-name)) + + (delete-directory pkg-tmp-dir t nil) + (pb/archive-entry pkg-info 'tar))) + + +;; In future we should provide a hook, and perform this step in a separate package. +;; Note also that it would be straightforward to generate the SVG ourselves, which would +;; save the network overhead. +(defun pb/write-melpa-badge-image (package-name version target-dir) + (url-copy-file + (concat "http://img.shields.io/badge/" + (if package-build-stable "melpa stable" "melpa") + "-" + (url-hexify-string version) + "-" + (if package-build-stable "3e999f" "922793") + ".svg") + (expand-file-name (concat package-name "-badge.svg") target-dir) + t)) + + +;;; Helpers for recipe authors + +(defvar package-build-minor-mode-map + (let ((m (make-sparse-keymap))) + (define-key m (kbd "C-c C-c") 'package-build-current-recipe) + m) + "Keymap for `package-build-minor-mode'.") + +(define-minor-mode package-build-minor-mode + "Helpful functionality for building packages." + nil + " PBuild" + package-build-minor-mode-map) + +;;;###autoload +(defun package-build-create-recipe (name fetcher) + "Create a new recipe for package NAME using FETCHER." + (interactive + (list (intern (read-string "Package name: ")) + (intern + (let ((fetcher-types (mapcar #'symbol-name '(github git wiki bzr hg cvs svn)))) + (completing-read + "Fetcher: " + fetcher-types + nil t nil nil (car fetcher-types)))))) + (let ((recipe-file (expand-file-name (symbol-name name) package-build-recipes-dir))) + (when (file-exists-p recipe-file) + (error "Recipe already exists")) + (find-file recipe-file) + (let* ((extra-params + (cond + ((eq 'github fetcher) '(:repo "USER/REPO")) + ((eq 'wiki fetcher) '()) + (t '(:url "SCM_URL_HERE")))) + (template `(,name :fetcher ,fetcher ,@extra-params))) + (insert (pp-to-string template)) + (emacs-lisp-mode) + (package-build-minor-mode) + (goto-char (point-min))))) + +;;;###autoload +(defun package-build-current-recipe () + "Build archive for the recipe defined in the current buffer." + (interactive) + (unless (and (buffer-file-name) + (file-equal-p (file-name-directory (buffer-file-name)) + package-build-recipes-dir)) + (error "Buffer is not visiting a recipe")) + (when (buffer-modified-p) + (if (y-or-n-p (format "Save file %s? " buffer-file-name)) + (save-buffer) + (error "Aborting"))) + (check-parens) + (package-build-reinitialize) + (let ((pkg-name (intern (file-name-nondirectory (buffer-file-name))))) + (package-build-archive pkg-name) + (package-build-dump-archive-contents) + (let ((output-buffer-name "*package-build-result*")) + (with-output-to-temp-buffer output-buffer-name + (princ ";; Please check the following package descriptor.\n") + (princ ";; If the correct package description or dependencies are missing,\n") + (princ ";; then the source .el file is likely malformed, and should be fixed.\n") + (pp (assoc pkg-name (package-build-archive-alist)))) + (with-current-buffer output-buffer-name + (emacs-lisp-mode) + (view-mode))) + (when (yes-or-no-p "Install new package? ") + (package-install-file (pb/find-package-file pkg-name))))) + +(defun package-build-archive-ignore-errors (pkg) + "Build archive for package PKG, ignoring any errors." + (interactive (list (pb/package-name-completing-read))) + (let* ((debug-on-error t) + (debug-on-signal t) + (pb/debugger-return nil) + (debugger (lambda (&rest args) + (setq pb/debugger-return (with-output-to-string + (backtrace)))))) + (condition-case err + (package-build-archive pkg) + (error + (pb/message "%s" (error-message-string err)) + nil)))) + + + +;;;###autoload +(defun package-build-all () + "Build all packages in the `package-build-recipe-alist'." + (interactive) + (let ((failed (cl-loop for pkg in (mapcar 'car (package-build-recipe-alist)) + when (not (package-build-archive-ignore-errors pkg)) + collect pkg))) + (if (not failed) + (princ "\nSuccessfully Compiled All Packages\n") + (princ "\nFailed to Build the Following Packages\n") + (princ (mapconcat 'symbol-name failed "\n")) + (princ "\n"))) + (package-build-cleanup)) + +(defun package-build-cleanup () + "Remove previously-built packages that no longer have recipes." + (interactive) + (let* ((known-package-names (mapcar 'car (package-build-recipe-alist))) + (stale-archives (cl-loop for built in (pb/archive-entries) + when (not (memq (car built) known-package-names)) + collect built))) + (mapc 'pb/remove-archive-files stale-archives) + (package-build-dump-archive-contents))) + +(defun package-build-recipe-alist () + "Retun the list of avalailable packages." + (unless pb/recipe-alist-initialized + (setq pb/recipe-alist (pb/read-recipes-ignore-errors) + pb/recipe-alist-initialized t)) + pb/recipe-alist) + +(defun package-build-archive-alist () + "Return the archive list." + (cdr (pb/read-from-file + (expand-file-name "archive-contents" + package-build-archive-dir)))) + +(defun package-build-reinitialize () + "Forget any information about packages which have already been built." + (interactive) + (setq pb/recipe-alist-initialized nil)) + +(defun package-build-dump-archive-contents (&optional file-name) + "Dump the list of built packages to FILE-NAME. + +If FILE-NAME is not specified, the default archive-contents file is used." + (pb/dump (cons 1 (pb/archive-entries)) + (or file-name + (expand-file-name "archive-contents" package-build-archive-dir)))) + +(defun pb/archive-entries () + "Read all .entry files from the archive directory and return a list of all entries." + (let ((entries '())) + (dolist (new (mapcar 'pb/read-from-file + (directory-files package-build-archive-dir t + ".*\.entry$")) + entries) + (let ((old (assq (car new) entries))) + (when old + (when (version-list-< (elt (cdr new) 0) + (elt (cdr old) 0)) + ;; swap old and new + (cl-rotatef old new)) + (pb/remove-archive-files old) + (setq entries (remove old entries))) + (add-to-list 'entries new))))) + + + +;;; Exporting data as json + +(defun package-build-recipe-alist-as-json (file-name) + "Dump the recipe list to FILE-NAME as json." + (interactive) + (with-temp-file file-name + (insert (json-encode (package-build-recipe-alist))))) + +(defun pb/sym-to-keyword (s) + "Return a version of symbol S as a :keyword." + (intern (concat ":" (symbol-name s)))) + +(defun pb/pkg-info-for-json (info) + "Convert INFO into a data structure which will serialize to JSON in the desired shape." + (let* ((ver (elt info 0)) + (deps (elt info 1)) + (desc (elt info 2)) + (type (elt info 3)) + (props (when (> (length info) 4) (elt info 4)))) + (list :ver ver + :deps (apply 'append + (mapcar (lambda (dep) + (list (pb/sym-to-keyword (car dep)) + (cadr dep))) + deps)) + :desc desc + :type type + :props props))) + +(defun pb/archive-alist-for-json () + "Return the archive alist in a form suitable for JSON encoding." + (apply 'append + (mapcar (lambda (entry) + (list (pb/sym-to-keyword (car entry)) + (pb/pkg-info-for-json (cdr entry)))) + (package-build-archive-alist)))) + +(defun package-build-archive-alist-as-json (file-name) + "Dump the build packages list to FILE-NAME as json." + (interactive) + (with-temp-file file-name + (insert (json-encode (pb/archive-alist-for-json))))) + +(provide 'package-build) + +;; Local Variables: +;; coding: utf-8 +;; eval: (checkdoc-minor-mode 1) +;; End: + +;;; package-build.el ends here diff --git a/pkgs/applications/editors/emacs-24/packages/setup-hook.sh b/pkgs/applications/editors/emacs-24/packages/setup-hook.sh new file mode 100644 index 000000000000..defef45b55f5 --- /dev/null +++ b/pkgs/applications/editors/emacs-24/packages/setup-hook.sh @@ -0,0 +1,7 @@ +addEmacsVars () { + if test -d $1/share/emacs/site-lisp; then + export EMACSLOADPATH="$1/share/emacs/site-lisp:$EMACSLOADPATH" + fi +} + +envHooks+=(addEmacsVars) diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index f0c98ce6c2ff..6d3c87299563 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -9908,6 +9908,13 @@ let }); emacs24Macport = self.emacs24Macport_24_4; + emacsMelpa = import ../applications/editors/emacs-24/packages { + inherit stdenv pkgs fetchurl fetchgit fetchFromGitHub emacs texinfo; + external = { + inherit (haskellngPackages) ghc-mod structured-haskell-mode; + }; + }; + emacsPackages = emacs: self: let callPackage = newScope self; in rec { inherit emacs; |