Moving from Homebrew to Nix Package Manager
前言
2012年我入手了人生第一台MacBook Pro,也正式开启了macOS(以前是 os x)之旅。在很长一段时间里我并不习惯macOS的应用安装方式。应用下载完后,打开dmg,然后拖放到 /Applications
目录下。以及macOS根本就不存在包管理器这种东西,我甚至在MacBook上直接安装了Archlinux。出于某些什么的原因,我又重新返回到了macOS,然后在用了一两个月的MacPorts之后转投了Homebrew。然后在最近重拾了haskell的学习进程之后,又开始折腾起了nix。
安装nix
因为 macOS catalin
的关系,安装 nix 的过程有些许曲折,可以参考我之前的文章 Learning Haskell with nix and Emacs 。
卸载Homebrew安装的包
列出已经安装的包
brew list
看看都安装了些什么软件包,如果有必要就在 nix 里面重新安装一遍。
brew uninstall `brew list`
安装nix包
nix-env -i coreutils
问题
coreutils包中的命令覆盖了macOS本身的命令
coreutils包中的命令有好几个与macOS命令冲突,并且其行为也不一样,例如 df
命令
coreutils的df
$ df -h Filesystem Size Used Avail Use% Mounted on /dev/disk1s1 932G 11G 695G 2% / /dev/disk1s5 932G 4.1G 695G 1% /private/var/vm /dev/disk1s6 932G 17G 695G 3% /nix
macOS 的 df
$ df -h Filesystem Size Used Avail Capacity iused ifree %iused Mounted on /dev/disk1s1 932Gi 10Gi 694Gi 2% 484150 9767494010 0% / devfs 205Ki 205Ki 0Bi 100% 712 0 100% /dev /dev/disk1s2 932Gi 206Gi 694Gi 23% 2334824 9765643336 0% /System/Volumes/Data /dev/disk1s5 932Gi 4.0Gi 694Gi 1% 4 9767978156 0% /private/var/vm map auto_home 0Bi 0Bi 0Bi 100% 0 0 100% /System/Volumes/Data/home /dev/disk1s6 932Gi 16Gi 694Gi 3% 436832 9767541328 0% /nix
于是参考homebrew的coreutils自己改了下 coreutils
nix表达式:
1: { stdenv, lib, buildPackages 2: , autoreconfHook, bison, texinfo, fetchurl, perl, xz, libiconv, gmp ? null 3: , aclSupport ? stdenv.isLinux, acl ? null 4: , attrSupport ? stdenv.isLinux, attr ? null 5: , selinuxSupport? false, libselinux ? null, libsepol ? null 6: # No openssl in default version, so openssl-induced rebuilds aren't too big. 7: # It makes *sum functions significantly faster. 8: , minimal ? true, withOpenssl ? !minimal, openssl ? null 9: , withPrefix ? false 10: , singleBinary ? "symlinks" # you can also pass "shebangs" or false 11: , unprefixNoConflict ? false 12: }: 13: 14: assert aclSupport -> acl != null; 15: assert selinuxSupport -> libselinux != null && libsepol != null; 16: 17: with lib; 18: 19: stdenv.mkDerivation rec { 20: pname = "coreutils"; 21: version = "8.31"; 22: 23: src = fetchurl { 24: url = "mirror://gnu/${pname}/${pname}-${version}.tar.xz"; 25: sha256 = "1zg9m79x1i2nifj4kb0waf9x3i5h6ydkypkjnbsb9rnwis8rqypz"; 26: }; 27: 28: patches = optional stdenv.hostPlatform.isCygwin ./coreutils-8.23-4.cygwin.patch 29: # Fix failing test with musl. See https://lists.gnu.org/r/coreutils/2019-05/msg00031.html 30: # To be removed in coreutils-8.32. 31: ++ optional stdenv.hostPlatform.isMusl ./avoid-false-positive-in-date-debug-test.patch 32: # Fix compilation in musl-cross environments. To be removed in coreutils-8.32. 33: ++ optional stdenv.hostPlatform.isMusl ./coreutils-8.31-musl-cross.patch; 34: 35: postPatch = '' 36: # The test tends to fail on btrfs,f2fs and maybe other unusual filesystems. 37: sed '2i echo Skipping dd sparse test && exit 77' -i ./tests/dd/sparse.sh 38: sed '2i echo Skipping du threshold test && exit 77' -i ./tests/du/threshold.sh 39: sed '2i echo Skipping cp sparse test && exit 77' -i ./tests/cp/sparse.sh 40: sed '2i echo Skipping rm deep-2 test && exit 77' -i ./tests/rm/deep-2.sh 41: sed '2i echo Skipping du long-from-unreadable test && exit 77' -i ./tests/du/long-from-unreadable.sh 42: 43: # Some target platforms, especially when building inside a container have 44: # issues with the inotify test. 45: sed '2i echo Skipping tail inotify dir recreate test && exit 77' -i ./tests/tail-2/inotify-dir-recreate.sh 46: 47: # sandbox does not allow setgid 48: sed '2i echo Skipping chmod setgid test && exit 77' -i ./tests/chmod/setgid.sh 49: substituteInPlace ./tests/install/install-C.sh \ 50: --replace 'mode3=2755' 'mode3=1755' 51: 52: sed '2i print "Skipping env -S test"; exit 77;' -i ./tests/misc/env-S.pl 53: 54: # these tests fail in the unprivileged nix sandbox (without nix-daemon) as we break posix assumptions 55: for f in ./tests/chgrp/{basic.sh,recurse.sh,default-no-deref.sh,no-x.sh,posix-H.sh}; do 56: sed '2i echo Skipping chgrp && exit 77' -i "$f" 57: done 58: for f in gnulib-tests/{test-chown.c,test-fchownat.c,test-lchown.c}; do 59: echo "int main() { return 77; }" > "$f" 60: done 61: '' + optionalString (stdenv.hostPlatform.libc == "musl") (lib.concatStringsSep "\n" [ 62: '' 63: echo "int main() { return 77; }" > gnulib-tests/test-parse-datetime.c 64: echo "int main() { return 77; }" > gnulib-tests/test-getlogin.c 65: '' 66: ]); 67: 68: outputs = [ "out" "info" ]; 69: 70: nativeBuildInputs = [ perl xz.bin ] 71: ++ optionals stdenv.hostPlatform.isCygwin [ autoreconfHook texinfo ] # due to patch 72: ++ optionals stdenv.hostPlatform.isMusl [ autoreconfHook bison ]; # due to patch 73: configureFlags = [ "--with-packager=https://NixOS.org" ] 74: ++ optional (singleBinary != false) 75: ("--enable-single-binary" + optionalString (isString singleBinary) "=${singleBinary}") 76: ++ optional withOpenssl "--with-openssl" 77: ++ optional stdenv.hostPlatform.isSunOS "ac_cv_func_inotify_init=no" 78: ++ optional withPrefix "--program-prefix=g" 79: ++ optionals (stdenv.hostPlatform != stdenv.buildPlatform && stdenv.hostPlatform.libc == "glibc") [ 80: # TODO(19b98110126fde7cbb1127af7e3fe1568eacad3d): Needed for fstatfs() I 81: # don't know why it is not properly detected cross building with glibc. 82: "fu_cv_sys_stat_statfs2_bsize=yes" 83: ]; 84: 85: 86: buildInputs = [ gmp ] 87: ++ optional aclSupport acl 88: ++ optional attrSupport attr 89: ++ optional withOpenssl openssl 90: ++ optionals selinuxSupport [ libselinux libsepol ] 91: # TODO(@Ericson2314): Investigate whether Darwin could benefit too 92: ++ optional (stdenv.hostPlatform != stdenv.buildPlatform && stdenv.hostPlatform.libc != "glibc") libiconv; 93: 94: # The tests are known broken on Cygwin 95: # (http://article.gmane.org/gmane.comp.gnu.core-utils.bugs/19025), 96: # Darwin (http://article.gmane.org/gmane.comp.gnu.core-utils.bugs/19351), 97: # and {Open,Free}BSD. 98: # With non-standard storeDir: https://github.com/NixOS/nix/issues/512 99: doCheck = stdenv.hostPlatform == stdenv.buildPlatform 100: && (stdenv.hostPlatform.libc == "glibc" || stdenv.hostPlatform.isMusl) 101: && builtins.storeDir == "/nix/store"; 102: 103: # Prevents attempts of running 'help2man' on cross-built binaries. 104: PERL = if stdenv.hostPlatform == stdenv.buildPlatform then null else "missing"; 105: 106: # Saw random failures like ‘help2man: can't get '--help' info from 107: # man/sha512sum.td/sha512sum’. 108: enableParallelBuilding = false; 109: 110: NIX_LDFLAGS = optionalString selinuxSupport "-lsepol"; 111: FORCE_UNSAFE_CONFIGURE = optionalString stdenv.hostPlatform.isSunOS "1"; 112: 113: # Works around a bug with 8.26: 114: # Makefile:3440: *** Recursive variable 'INSTALL' references itself (eventually). Stop. 115: preInstall = optionalString (stdenv.hostPlatform != stdenv.buildPlatform) '' 116: sed -i Makefile -e 's|^INSTALL =.*|INSTALL = ${buildPackages.coreutils}/bin/install -c|' 117: ''; 118: 119: postInstall = optionalString (stdenv.hostPlatform != stdenv.buildPlatform && !minimal) '' 120: rm $out/share/man/man1/* 121: cp ${buildPackages.coreutils-full}/share/man/man1/* $out/share/man/man1 122: '' 123: # du: 8.7 M locale + 0.4 M man pages 124: + optionalString minimal '' 125: rm -r "$out/share" 126: '' 127: + optionalString (stdenv.isDarwin && withPrefix && unprefixNoConflict) '' 128: cd $out/bin 129: ${concatStringsSep "\n" (builtins.map (x: "ln -s g${x} ${x}") (splitString " " "b2sum base32 chcon hostid md5sum nproc numfmt pinky ptx realpath runcon sha1sum sha224sum sha256sum sha384sum sha512sum shred shuf stdbuf tac timeout truncate"))} 130: ''; 131: 132: meta = { 133: homepage = https://www.gnu.org/software/coreutils/; 134: description = "The basic file, shell and text manipulation utilities of the GNU operating system"; 135: 136: longDescription = '' 137: The GNU Core Utilities are the basic file, shell and text 138: manipulation utilities of the GNU operating system. These are 139: the core utilities which are expected to exist on every 140: operating system. 141: ''; 142: 143: license = licenses.gpl3Plus; 144: 145: platforms = platforms.unix ++ platforms.windows; 146: 147: priority = 10; 148: 149: maintainers = [ maintainers.eelco ]; 150: }; 151: 152: } // optionalAttrs stdenv.hostPlatform.isMusl { 153: # Work around a bogus warning in conjunction with musl. 154: NIX_CFLAGS_COMPILE = "-Wno-error"; 155: }
emacs-libvterm 编译问题
emacs-libvterm 前段时间开始使用这个包,可以说这个包是目前为止,我用过的在emacs下表现最好的终端模拟器了。然后在换成nix之后遇到了些问题。
glibtool 命令找不到
emacs-libvterm在macOS下,回去寻找glibtool这个命令,而glibtool这个命令实际上就是libtool命令加了前缀 g
。nix里面对应的包名是libtool。需要修改nix增加前缀。
libtool 静态编译问题
nix下的包默认都是非静态编译,而emacs-libvterm又需要静态编译。这里又要改glibtool的nix表达式。
综上,最后修改完的 nix 文件
1: { stdenv, fetchurl, m4, perl, help2man 2: , withPrefix ? false 3: , static ? false 4: }: 5: 6: stdenv.mkDerivation rec { 7: pname = "libtool"; 8: version = "2.4.6"; 9: 10: src = fetchurl { 11: url = "mirror://gnu/libtool/${pname}-${version}.tar.gz"; 12: sha256 = "1qq61k6lp1fp75xs398yzi6wvbx232l7xbyn3p13cnh27mflvgg3"; 13: }; 14: 15: outputs = [ "out" "lib" ]; 16: 17: nativeBuildInputs = [ perl help2man m4 ]; 18: propagatedBuildInputs = [ m4 ]; 19: configureFlags = if withPrefix then [ "--program-prefix=g" ] else []; 20: dontDisableStatic = static; 21: 22: # Don't fixup "#! /bin/sh" in Libtool, otherwise it will use the 23: # "fixed" path in generated files! 24: dontPatchShebangs = true; 25: 26: # XXX: The GNU ld wrapper does all sorts of nasty things wrt. RPATH, which 27: # leads to the failure of a number of tests. 28: doCheck = false; 29: doInstallCheck = false; 30: 31: enableParallelBuilding = true; 32: 33: # Don't run the native `strip' when cross-compiling. This breaks at least 34: # with `.a' files for MinGW. 35: dontStrip = stdenv.hostPlatform != stdenv.buildPlatform; 36: 37: meta = with stdenv.lib; { 38: description = "GNU Libtool, a generic library support script"; 39: longDescription = '' 40: GNU libtool is a generic library support script. Libtool hides 41: the complexity of using shared libraries behind a consistent, 42: portable interface. 43: 44: To use libtool, add the new generic library building commands to 45: your Makefile, Makefile.in, or Makefile.am. See the 46: documentation for details. 47: ''; 48: homepage = https://www.gnu.org/software/libtool/; 49: license = licenses.gpl2Plus; 50: maintainers = [ ]; 51: platforms = platforms.unix; 52: }; 53: } 54:
jenv 包不存在问题
jenv包是为了解决java环境问题的。而nix本身就是为了解决开发环境问题的“终极”方案。理论上根本就不需要jenv这样的工具。但是所有开发都使用 nix-shell
去定制开发环境,又稍显太重。所以有时候还是需要jenv这样的工具的。于是自己写个 nix表达式。
1: { stdenv, fetchurl }: 2: 3: stdenv.mkDerivation rec { 4: version = "0.5.2"; 5: pname = "jenv"; 6: src = fetchurl { 7: url = "https://github.com/jenv/jenv/archive/${version}.tar.gz"; 8: sha256 = "4cdce828bfaeb6561733bab641ed2912107a8bc24758a17f2387ee78403afb9a"; 9: }; 10: buildPhase = 11: '' 12: outdir=$out/libexec 13: mkdir -p $outdir 14: cp -r * $outdir 15: ''; 16: installPhase = 17: '' 18: mkdir $out/bin 19: ln -s $outdir/libexec/jenv $out/bin/jenv; 20: ''; 21: dontFixup = true; 22: meta = with stdenv.lib; { 23: description = "jEnv is an updated fork of jenv, a beloved Java environment manager adapted from rbenv."; 24: longDescription = 25: ''This is an updated fork of jenv, a beloved Java environment manager adapted from rbenv. 26: 27: jenv gives you a few critical affordances for using java on development machines: 28: 29: * It lets you switch between java versions. This is useful when developing Android applications, which generally require Java 8 for its tools, versus server applications, which use later versions like Java 11. 30: * It sets JAVA_HOME inside your shell, in a way that can be set globally, local to the current working directory or per shell. 31: 32: However, this project does not: 33: 34: * Install java for you. Use your platform appropriate package manager to install java. On macOS, brew is recommended. 35: * This document will show you how to install jenv, review its most common commands, show example workflows and identify known issues. 36: ''; 37: homepage = http://www.jenv.be; 38: license = licenses.mit; 39: platforms = platforms.all; 40: maintainers = [ ]; 41: }; 42: }
mit-scheme 安装问题
nix的channel里有mit-scheme包,但是不支持darwin平台。使用mit-scheme-c的包来安装的话,又有编译问题。nix编译darwin包用的stdenv中clang的版本比较低。
$ nix-shell --pure -p stdenv --command "clang --version" clang version 7.1.0 (tags/RELEASE_710/final) Target: x86_64-apple-darwin19.2.0 Thread model: posix InstalledDir: /nix/store/jdmg20b8rgvs1s4fxb585lffz07vv52a-clang-7.1.0/bin
解决clang版本低问题,可以直接使用xcode command line tools编译
1: { stdenv, requireFile, lib }: 2: 3: let requireCLTool = version: sha256: 4: let 5: version' = lib.replaceStrings ["."] ["_"] version; 6: dmg = "Command_Line_Tools_for_Xcode_${version'}.dmg"; 7: app = requireFile rec { 8: name = "CommandLineTools"; 9: url = "https://download.developer.apple.com/Developer_Tools/Command_Line_Tools_for_Xcode_${version'}/Command_Line_Tools_for_Xcode_${version'}.dmg"; 10: hashMode = "recursive"; 11: inherit sha256; 12: message = '' 13: Unfortunately, we cannot download ${name} automatically. 14: Please go to ${url} 15: to download it yourself, and add it to the Nix store by running the following commands. 16: Note: download (~ 5GB), extraction and storing of Xcode will take a while 17: 18: nix-store --add-fixed --recursive sha256 /Library/Developer/CommandLineTools 19: ''; 20: }; 21: meta = with stdenv.lib; { 22: homepage = https://developer.apple.com/downloads/; 23: description = "Apple's Command Line Tools for Xcode"; 24: license = licenses.unfree; 25: platforms = platforms.darwin; 26: }; 27: in app.overrideAttrs (oldAttrs : oldAttrs // { inherit meta; }); 28: 29: in lib.makeExtensible (self: { 30: Command_Line_Tools_for_Xcode_11_2 = requireCLTool "11.2" "76ec9816dc26955c0d3d05cbd39b9500d18842ddd33a448c98fb896f1a917dc5"; 31: Command_Line_Tools_for_Xcode = self."Command_Line_Tools_for_Xcode_${lib.replaceStrings ["."] ["_"] (if stdenv.targetPlatform.useiOSPrebuilt then stdenv.targetPlatform.xcodeVer else "11.2")}"; 32: })
修改 mit-scheme nix表达式,使用xcode command line tools编译
1: { pkgs, stdenvNoCC, fetchurl, makeWrapper, gnum4, texinfo, texLive, automake, lib, macosVersion, xcodeVersion }: 2: 3: let 4: version = "9.2"; 5: xcode = pkgs.darwin."Command_Line_Tools_for_Xcode_${lib.replaceStrings ["."] ["_"] xcodeVersion}"; 6: in 7: stdenvNoCC.mkDerivation { 8: name = "mit-scheme-macos${lib.replaceStrings ["."] ["_"] macosVersion}-xcode${lib.replaceStrings ["."] ["_"] xcodeVersion}-${version}"; 9: 10: # MIT/GNU Scheme is not bootstrappable, so it's recommended to compile from 11: # the platform-specific tarballs, which contain pre-built binaries. It 12: # leads to more efficient code than when building the tarball that contains 13: # generated C code instead of those binaries. 14: src = fetchurl { 15: url = "mirror://gnu/mit-scheme/stable.pkg/${version}/mit-scheme-c-${version}.tar.gz"; 16: sha256 = "0w5ib5vsidihb4hb6fma3sp596ykr8izagm57axvgd6lqzwicsjg"; 17: }; 18: 19: buildInputs = [ xcode ]; 20: 21: configurePhase = "(cd doc && ./configure)"; 22: 23: buildPhase = 24: '' export PATH=${xcode}/usr/bin:$PATH 25: export CPATH=${xcode}/SDKs/MacOSX${macosVersion}.sdk/usr/include 26: 27: cd src 28: for i in 6001/edextra.scm \ 29: 6001/floppy.scm \ 30: compiler/etc/disload.scm \ 31: edwin/techinfo.scm \ 32: edwin/unix.scm \ 33: swat/c/tk3.2-custom/Makefile \ 34: swat/c/tk3.2-custom/tcl/Makefile \ 35: swat/scheme/other/btest.scm \ 36: microcode/configure 37: do 38: sed -i "s~/usr/local~$out~g" $i 39: done 40: sed -i 's/run_configure/run_configure --without-x --with-macosx-version=10.15/g' ./etc/make-liarc.sh 41: ./etc/make-liarc.sh --prefix=$out 42: 43: cd ../doc 44: 45: # Provide a `texinfo.tex'. 46: export TEXINPUTS="$(echo ${automake}/share/automake-*)" 47: echo "\$TEXINPUTS is \`$TEXINPUTS'" 48: make 49: 50: cd .. 51: ''; 52: 53: installPhase = 54: '' make prefix=$out install -C src 55: make prefix=$out install -C doc 56: ''; 57: 58: fixupPhase = 59: '' wrapProgram $out/bin/mit-scheme-c --set MITSCHEME_LIBRARY_PATH \ 60: $out/lib/mit-scheme-c 61: ''; 62: 63: nativeBuildInputs = [ makeWrapper gnum4 texinfo texLive automake ]; 64: 65: # XXX: The `check' target doesn't exist. 66: doCheck = false; 67: 68: meta = with stdenvNoCC.lib; { 69: description = "MIT/GNU Scheme, a native code Scheme compiler"; 70: 71: longDescription = 72: '' MIT/GNU Scheme is an implementation of the Scheme programming 73: language, providing an interpreter, compiler, source-code debugger, 74: integrated Emacs-like editor, and a large runtime library. MIT/GNU 75: Scheme is best suited to programming large applications with a rapid 76: development cycle. 77: ''; 78: 79: homepage = https://www.gnu.org/software/mit-scheme/; 80: 81: license = licenses.gpl2Plus; 82: 83: maintainers = [ ]; 84: 85: # Build fails on Cygwin and Darwin: 86: # <http://article.gmane.org/gmane.lisp.scheme.mit-scheme.devel/489>. 87: platforms = platforms.darwin; 88: }; 89: }
我的自定义nix-channels
后记
nix是个好工具,对于构建应用的开发环境来说是一个杀手级的工具,但是对于macOS这种系统的日常使用来说还是有些不方便。最后我给自己定下了一些使用策略。
- 使用homebrew cask安装桌面应用,比如firefox、chrome等。
- 使用homebrew安装日常命令行工具。
- 对一些依赖比较复杂的应用,使用nix构造开发环境