Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

darwin: installBinaryPackage init and refactoring of darwin apps #293498

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

afh
Copy link
Contributor

@afh afh commented Mar 5, 2024

Description of changes

  • Add darwin.installBinaryPackage unifying and simplifying how binary darwin applications are installed.
  • Refactor a few packages to use darwin.installBinaryPackage
  • Clean-up of packages, e.g. ensure phase pre- and post-hooks are run

NOTA BENE: This is a draft PR to get the conversation going on whether this is headed into the right direction and folks see this as beneficial and helpful.

💭 A Few Thoughts

  • There are several ../os-specific/darwin/ packages declared in pkgs/top-level/all-packages.nix do folks agree that these are better declared in pkgs/top-level/darwin-packages.nix?
  • TODO: Migrate os-specific/darwin packages from all-packages.nix to darwin-packages.nix if the general sentiment is positive on the previous question.
grep os-specific/darwin pkgs/top-level/all-packages.nix | awk -F'[=]' '/ *#/{next} {print $1}'
  • asitop
  • goku
  • grandperspective
  • hexfiend
  • kwm
  • khd
  • m-cli
  • pam-reattach
  • reattach-to-user-namespace
  • skhd
  • qes
  • pngpaste
  • sketchybar
  • spacebar
  • swiftbar
  • airbuddy
  • aldente
  • coconutbattery
  • defaultbrowser
  • karabiner-elements
  • osx-cpu-temp
  • macfuse-stubs
  • osxsnarf
  • plistwatch
  • noah
  • rectangle
  • shortcat
  • smimesign
  • swiftdefaultapps
  • sensible-side-buttons
  • raycast
  • dockutil
  • mas
  • mysides
  • yabai
  • ghc-standalone-archive
  • duti
  • wifi-password
  • TODO: Refactor packages using undmg or _7zz as their nativeBuildInput to unpack a prebuilt darwin binary, if this PR is generally viewed positively.
nix run nixpkgs#silver-searcher -- -G '\.nix' -l '(undmg|_7zz)' | rev | cut -d/ -f2 | rev
  • prl-tools
  • install-binary-package
  • aldente
  • macfuse
  • airbuddy
  • rectangle
  • raycast
  • grandperspective
  • hexfiend
  • sensible-side-buttons
  • caffeine
  • archi
  • 7zz
  • undmg
  • peazip
  • realvnc-vnc-viewer
  • unnaturalscrollwheels
  • disk-inventory-x
  • jprofiler
  • composer
  • mkl
  • fpc
  • fpc
  • top-level
  • spotube
  • spacedrive
  • mos
  • iina
  • suspicious-package
  • rectangle-pro
  • warp-terminal
  • kbreakout
  • libkmahjongg
  • knights
  • killbots
  • bomber
  • knetwalk
  • kpat
  • kdiamond
  • granatier
  • libkdegames
  • kapman
  • kgoldrunner
  • kigo
  • notesnook
  • sequelpro
  • 1password-gui
  • joplin-desktop
  • tableplus
  • keeweb
  • obsidian
  • losslesscut-bin
  • roam-research
  • darwin
  • localsend
  • lens
  • discord
  • vk-messenger
  • slack
  • caprine-bin
  • breitbandmessung
  • synology-drive-client
  • bin
  • scilab-bin
  • yesplaymusic
  • spotify
  • reaper
  • p4v
  • radicle-upstream
  • pika
  • pkgs
  • zandronum
  • katawa-shoujo
  • anki
  • What needs to be documented where, so contributors can discover darwin.installBinaryPackage and learn about its details?
  • If initial review feedback is positive loop in maintainers of refactored packages and ask for their input
  • @reviewers Please see inline comments and questions, especially on pkgs/os-specific/install-binary-package/*

ℹ️ All treewide: refactor to use darwin.installBinaryPackage* commits will be squashed into one. For now it's easier for me to work on this with individual commits.

Things done

  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • For non-Linux: Is sandboxing enabled in nix.conf? (See Nix manual)
    • sandbox = relaxed
    • sandbox = true
  • Tested, as applicable:
  • Tested compilation of all packages that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD". Note: all changes have to be committed, also see nixpkgs-review usage
  • Tested basic functionality of all binary files (usually in ./result/bin/)
  • 24.05 Release Notes (or backporting 23.05 and 23.11 Release notes)
    • (Package updates) Added a release notes entry if the change is major or breaking
    • (Module updates) Added a release notes entry if the change is significant
    • (Module addition) Added a release notes entry if adding a new NixOS module
  • Fits CONTRIBUTING.md.

Add a 👍 reaction to pull requests you find important.

Comment on lines +24 to +23
dontMakeSourcesWritable = args.dontMakeSourcesWritable or true;
dontPatch = args.dontPatch or true;
dontConfigure = args.dontConfigure or true;
dontBuild = args.dontBuild or true;
dontStrip = args.dontStrip or true;
dontFixup = args.dontFixup or true;
dontCheck = args.dontCheck or true;
doInstallCheck = args.doInstallCheck or false;
Copy link
Contributor Author

@afh afh Mar 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Is this the best way to set sensible defaults, but allow customisation for packages that need it?
  • Are there any other dont* flags that are relevant here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this implementation allow for enough flexibility for uncommon use-cases (see karabiner-elements) while still making "good" assumptions about the general layout/content of DMGs and PKGs, e.g. unpack any Payload* using bsdtar contained in a .pkg file?

@ofborg ofborg bot added the 6.topic: darwin Running or building packages on Darwin label Mar 5, 2024
@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/proposal-for-installing-prebuilt-binaries-on-darwin/40843/1

@afh afh self-assigned this Mar 5, 2024
@afh afh requested review from emilytrau, Enzime and DataHearth and removed request for emilytrau and Enzime March 5, 2024 14:12
@DataHearth
Copy link
Contributor

I like the abstraction made by the dynamic unpack hook for DMG and PKG. Package complexity will drop (IMO) and package's lines reduced by a good margin.

But I think we should make sure every possible option is covered. As for some specific behavior, this might even add more complexity inside the package and possibly inside the hook.

If this solution is chosen, we should create a detailed documentation to avoid looking everywhere to find options, default behaviors, etc.

Love it 👍

@afh
Copy link
Contributor Author

afh commented Mar 5, 2024

Thank you for taking the time to have a look at this and your comment, @DataHearth, very much appreciated!

In your mind what is a good way to ensure that "every possible option is covered"? My first approach would be to refactor the packages in pkgs/os-specific/darwin and the ones that have undmg or _7zz as a build input.

Where would be a good place for such documentation? Somewhere in docs or rather pkgs/os-specific/darwin/README.md?

@afh
Copy link
Contributor Author

afh commented Mar 5, 2024

Looking at fetchzip I wonder if that could be changed or creating a similar fetcher, e.g. fetchdmgpkg would be more? helpful than the unpack-dmg-pkg hook? 🤔

@afh afh force-pushed the init-darwin-install-binary branch from 38620a5 to 9dd6490 Compare March 5, 2024 20:52
@ShamrockLee
Copy link
Contributor

I love the idea of having a unified way to package binaries for MacOS.

Regarding the treewide change,

  1. hello: refactor to use darwin.installBinaryPackage would be easier to read comparing to treewide: refactor to use darwin.installBinaryPackage (hello).
  2. It would make this PR easier to review if we change only some packages for demonstration and testing purpose, and split others into subsequent PRs.

@afh
Copy link
Contributor Author

afh commented Mar 6, 2024

Thanks for your input, @ShamrockLee, much appreciated!

  1. Yes, that is a working title and when this draft PR is promoted to a PR I'll squash all those commits into one with an updated commit message

  2. Agreed 🙂

Any opinion or advice you may have on the "💭 A Few Thoughts" I listed in the initial description?

@DataHearth
Copy link
Contributor

Hey! Regarding creating a fetcher, I'm not a big fan of. As the "universal" way seems to fetch then unpack for most scenarios, I would keep it as a new package helper.

I think the best way to find out if you missed something, it's to check other packages which are using an unpack like 7z or unzip, etc... list all available options, and see if they're applicable to dmg and pkg files. And of course, test it on packages ^^.

I agree with @ShamrockLee , I think this would me much easier to review for a code maintainer with only 1 or 2 packages as examples.

@afh
Copy link
Contributor Author

afh commented Mar 6, 2024

Thanks for the feedback, @DataHearth, that's helpful.

I'll do some code level analysis and prepare some notes. In addition to that I'll split this PR into two: one for the install binary package changes and one for the treewide package changes (with a possible follow-up).

@afh
Copy link
Contributor Author

afh commented Mar 7, 2024

As requested this PR now affects fewer files and the majority of the actual package rewrites to use the changes proposed in this PR will be in #293961.
Please allow for some more time for a more methodical analysis and summarising the findings in a few notes (posted here later).

@afh afh force-pushed the init-darwin-install-binary branch from c424494 to abbdb9a Compare March 7, 2024 17:21
}:

stdenvNoCC.mkDerivation (finalAttrs: {
darwin.installBinaryPackage rec {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought it was preferred to use the finalAttrs pattern over rec, why switch?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my understanding, mkDerivation takes either an attrset or a function (the finalAttrs pattern) as argument.

This PR introduces installBinaryPackage to simplify the process of packaging macOS apps and reduces the overuse of mkDerivation (code duplication in general). It takes an attrset as argument, i.e. the finalAttrs pattern is not applicable here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my understanding, mkDerivation takes either an attrset or a function (the finalAttrs pattern) as argument.

This PR introduces installBinaryPackage to simplify the process of packaging macOS apps and reduces the overuse of mkDerivation (code duplication in general). It takes an attrset as argument, i.e. the finalAttrs pattern is not applicable here.

The reason why most build helpers in Nixpkgs don't support fixed-point arguments (finalAttrs: { ... }) yet, is because of the challenge to handle technical debts inside existing build helpers -- arguments that cannot be passed directly to stdenv.mkDerivation.

The goal of #234651 is to introduce a way (lib.extendMkDerivation) to handle such arguments and bring fixed-point arguments support to existing build helpers. Even then, there are still efforts to bring fixed-point arguments support to build helpers like buildRustPackage (#194475) and buildGoModule (#225051).

As a new build helper, installBinaryPackage could avoid such technical debt as a whole and support fixed-point attributes out of the box without the need of the WIP lib.extendMkDerivation. We just need to make sure that everything is passed properly into stdenv.mkDerivation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update:

Sorry @stepbrobd for my misunderstanding your point. You're right about the current implementation of installBinaryPackage handling only a plain attribute set.

I just want to point out that such support is not impossible or unsuitable outside stdenv.mkDerivation. We only need to modify the implementation of installBinaryPackage a bit to support it.

Copy link
Contributor

@ShamrockLee ShamrockLee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To support fixed-point arguments, we can implement installBinaryPackage via <pkg>.overrideAttrs, and give the sourceRoot argument another name. This way, installBinaryPackage will support both plain attribute set and (finalAttrs: { ... })

Comment on lines +3 to +15
{ appName ? "."
, nativeBuildInputs ? []
, sourceRoot ? "."
, ...
}@args:

let
unpackDmgPkg = makeSetupHook {
name = "unpack-dmg-pkg";
propagatedBuildInputs = [ _7zz libarchive ];
} ./unpack-dmg-pkg.sh;
in
stdenvNoCC.mkDerivation (args // {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{ appName ? "."
, nativeBuildInputs ? []
, sourceRoot ? "."
, ...
}@args:
let
unpackDmgPkg = makeSetupHook {
name = "unpack-dmg-pkg";
propagatedBuildInputs = [ _7zz libarchive ];
} ./unpack-dmg-pkg.sh;
in
stdenvNoCC.mkDerivation (args // {
let
unpackDmgPkg = makeSetupHook {
name = "unpack-dmg-pkg";
propagatedBuildInputs = [ _7zz libarchive ];
} ./unpack-dmg-pkg.sh;
in
fpArgs:
(stdenvNoCC.mkDerivation fpArgs).overrideAttrs (finalAttrs:
{ appName ? "."
, nativeBuildInputs ? []
, sourceRoot ? "."
, ...
}@args:
{

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, @ShamrockLee, I've included your suggestion locally, but need to refactor the treewide changes before pushing an update on this.
Also I'd like wait until sourceRoot conversation (see below) provides guidance on a more acceptable solution.

dontBuild = args.dontBuild or true;
dontStrip = args.dontStrip or true;
dontFixup = args.dontFixup or true;
dontCheck = args.dontCheck or true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
dontCheck = args.dontCheck or true;

The attribute is called doCheck in stdenv.mkDerivation, and it's false by default.
We don't need to touch it, unless we need to use finalAttrs.doCheck.

If we do need finalAttrs.doCheck somewhere, though, we'll still have to do

  doCheck = args.doCheck or false;

to guarentee its availability. Attempt to determine if an attribute is available in finalAttrs will result in infinite recursion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason I thought both existed, @ShamrockLee. Grep'ing through the packages around 80 use dontCheck (likely including some false positives). Is this something that could and should be cleaned up too at some point?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@afh This comment slipped out of my radar. Thank you for your patience.

For some reason I thought both existed, @ShamrockLee. Grep'ing through the packages around 80 use dontCheck (likely including some false positives). Is this something that could and should be cleaned up too at some point?

After some keyword searching inside VSCode, it seems most of the dontCheck uses refer to a Haskell trivial helper (function) haskell.lib.compose.dontCheck, which takes a derivation and returns one with { doCheck = false; } overriding.

Searching with dontCheck = returns only 7 matches. Two of which is the definition of haskell.lib.compose.dontCheck, and the other 5 are Python packages. Since pkgs/development/interpreters/python/mk-python-derivation.nix doesn't provide such implementation, they are likely mistakes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once again an excellent analysis from you, @ShamrockLee 😃👍

pkgs/by-name/py/pyxel/package.nix seems to also use dontCheck and is built using buildPythonApplication (defined in pkgs/development/interpreters/python/python-packages-base.nix, which also does not seem to offer dontCheck).
Anything we can do to confirm that the dontCheck used in the python packages is indeed a mistake prior to creating a PR? Or should I (unless you prefer to do it yourself) create a PR to get more eyes on this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything we can do to confirm that the dontCheck used in the python packages is indeed a mistake prior to creating a PR?

The proof turns out to be quite simple:

nix-repl> pkgs = import ./. { }             
nix-repl> pkgs.pyxel
«derivation /nix/store/rr3d1w5j622sy9xw032wzp69dr8fz15j-pyxel-2.0.7.drv»

nix-repl> pkgs.pyxel.doInstallCheck
true

nix-repl> (pkgs.pyxel.overridePythonAttrs (_: { doCheck = false; }))
«derivation /nix/store/mycyaxdzwqgi6x0aylzmljh7zb0xr22w-pyxel-2.0.7.drv»

nix-repl> (pkgs.pyxel.overridePythonAttrs (_: { doCheck = false; })).doInstallCheck
false

Or should I (unless you prefer to do it yourself) create a PR to get more eyes on this?

It would be helpful for people who use and maintain these packages.

That said, I wonder whether those packages really need their install checks disabled if they build fine without. Feel free to tag the package maintainers in the PR description.

Copy link
Contributor Author

@afh afh Apr 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ Here are the three PRs #307823(merged), #307835(merged), #307838 (draft) that remove dontCheck from the packages mentioned earlier. Some PRs also update and/or modernize some of the packages.

Copy link
Contributor

@ShamrockLee ShamrockLee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sourceRoot corresponds to the shell variable/environment variable $sourceRoot inside the build script. It might be surprising if sourceRoot gets implicitly prefixed.

Developers who set sourceRoot is responsible to make sure it goes right, and we just need to document how it should be set. The documentation can be placed under the Languages and Frameworks chapter of Nixpkgs Manual.

Such input argument is also an example of "arguments that cannot be passed to stdenv.mkDeriaviton". Such arguments are not only the main obstacle of adding fixed-point arguments support, but also the motivation behind custom overriders like overridePythonAttrs, overrideLispAttrs and overrideNimAttrs, despite all the issues they have.

If it is necessary to specify the sub-path through an attribute, I hope it could be something like subSourceRoot. If so, we could specify

  inherit subSourceRoot; # Simpler version of subSourceRoot  = args.subSourceRoot or "."
  sourceRoot = finalAttrs.pname
    + lib.optionalString (finalAttrs.subSourceRoot != ".") "/${finalAttrs.subSourceRoot}";


{ appName ? "."
, nativeBuildInputs ? []
, sourceRoot ? "."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
, sourceRoot ? "."

If we do want one, it could be called subSourceRoot or something.

dontCheck = args.dontCheck or true;
doInstallCheck = args.doInstallCheck or false;

sourceRoot = "${args.pname}/${sourceRoot}";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
sourceRoot = "${args.pname}/${sourceRoot}";
sourceRoot = args.sourceRoot or args.pname;

Just give it a sensible default.

(Replace args.pname with finalAttrs.pname if you would like to adopt the fixed-point-arguments style.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be a bit more involved than it seems and possibly a more elegant way exists to solve what the current approach solves.

Most of the binary sources unpack multiple files, a directory, or a one or more archive files (often named Payload). Yet some packages unpack a single binary, which made¹ the unpackPhase fail for me. Given the variety of binary source packages (dmg with file(s), pkg with file(s), dmg with archive(s), pkgs with archive(s)) it seems easier to unpack each "layer" and take it from there instead of trying to analyse what the "best" unpack strategy for a particular package is. Not that some packages also need multiple levels of unpacking, e.g. karabiner-elements.

So my idea was to simple put all unpacked files into a directory named args.pname to satisfy the directory-"requirement"—in which a directory must be present afterwards—of the unpackPhase.
So introducing subSourceRoot might not provide the solution that is needed…

Have I been able to explain my thinking around this well?
What are alternative approaches for this challenge?

────────────
¹ Maybe this has been addressed as I cannot not reproduce the issue; possibly my quick tests for this comment have been too simple to not trigger the error. I'll keep my 👀 peeled.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So my idea was to simple put all unpacked files into a directory named args.pname to satisfy the directory-"requirement"—in which a directory must be present afterwards—of the unpackPhase.
So introducing subSourceRoot might not provide the solution that is needed…

That makes sense. subSourceRoot doesn't seem to scratch the itch.

Then, how about setting sourceRoot = "${args.pname}" by default, and tell people to always set sourceRoot to "${pname}" or its sub-directory inside Nixpkgs manual or comment block?

The current implementation will result in developer setting sourceRoot = "subdir" and see sourceRoot is hello-bin/subdir in the build log. Setting sourceRoot = "${pname}/subdir" avoids such inconsistency.

From the search result inside the Nixpkgs code base with regular expression sourceRoot =.*sourceRoot, no build helpers provided in Nixpkgs change sourceRoot transparently like this. The only exception is buildIntegration defined locally inside a let ... in block.

I could only express my point of view instead of requesting a change, as I'm nither a committer nor a Darwin user. The design choice will be made by you and the committers.

@afh
Copy link
Contributor Author

afh commented Mar 20, 2024

Wow! Thanks for your detailed comments and suggestions, @ShamrockLee, very much appreciated!! I'm a bit busy until the middle of calendar week 13, so please allow me some time to get back to you on this 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants