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

a zig build mode intended to be used when packaging an application or library for a system distribution #14281

Closed
Tracked by #14265
andrewrk opened this issue Jan 12, 2023 · 12 comments · Fixed by #18778
Labels
accepted This proposal is planned. enhancement Solving this issue will likely involve adding new logic or components to the codebase. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. zig build system
Milestone

Comments

@andrewrk
Copy link
Member

andrewrk commented Jan 12, 2023

This proposal is accepted; please see these comments for the criteria for closing the issue:


Extracted from #14265.

The use case is as follows:

Someone wants to take an upstream application, and package it for a Linux distribution, or another kind of distribution such as Homebrew or Nix.

The application knows exactly what versions of each dependency it wants to build against, but distributions only provide a limited set of versions. Furthermore, the system distribution wants to be in charge of providing all dependencies, building the application from source without access to any files outside of those provided by the system itself, via its own dependency management system.

For this use case, instead of fetching dependencies from the URLs provided by zig build.zig.zon files, instead there should be some kind of override that makes the dependencies fetched from the system package manager.

Related: Stay Together For The Kids: Why System Package Managers and Language Package Managers Struggle to Cooperate

@andrewrk andrewrk added enhancement Solving this issue will likely involve adding new logic or components to the codebase. zig build system use case Describes a real use case that is difficult or impossible, but does not propose a solution. labels Jan 12, 2023
@andrewrk andrewrk added this to the 0.11.0 milestone Jan 12, 2023
@andrewrk andrewrk mentioned this issue Jan 13, 2023
32 tasks
@daurnimator
Copy link
Collaborator

For this use case, instead of fetching dependencies from the URLs provided by zig build.zig.toml files, instead there should be some kind of override that makes the dependencies fetched from the system package manager. Perhaps Zig can provide tooling that makes an application support being built in both contexts.

As long as zig can be pointed at a local directory for each dependency instead of ever fetching itself, then the external packaging systems (and/or the tooling around it) can be made to work.

@andrewrk
Copy link
Member Author

I suspect that, in this mode, it will be necessary for zig to shell out to the system C/C++ compiler toolchain instead of relying on its own embedded clang library code. This is a very large can of worms, but, if we accept the foundational premise that zig build is intended to be truly used as a competitor to other build systems, this point is non-negotiable.

@motiejus
Copy link
Contributor

motiejus commented Jan 17, 2023

Edit: moved to #14283 (comment)

@nektro
Copy link
Contributor

nektro commented Jan 30, 2023

i think something akin to CMAKE_PREFIX_PATH could help a lot here. in a system package manager environment, it could be populated by them, and if its empty zig could fill the value itself

https://cmake.org/cmake/help/latest/envvar/CMAKE_PREFIX_PATH.html

@andrewrk
Copy link
Member Author

The equivalent parameter in zig build is --search-prefix

@nektro
Copy link
Contributor

nektro commented Jan 30, 2023

yes but i mean a variable different but similar for finding zig packages instead of cmake packages

@andrewrk
Copy link
Member Author

andrewrk commented Jul 6, 2023

Note what's happening over at NixOS/nixpkgs#241741.

They are creating common logic for packaging Zig applications on NixOS and have this line:

zig build -Drelease-safe -Dcpu=baseline $zigBuildFlags

This is a bit of a problem because -Drelease-safe, while commonly exposed,

  • Is not always exposed because the build script might build multiple things and a single choice like this might not make sense
  • The upstream author might expose only -Drelease because they want to choose the safety/size tradeoff.

Furthermore, -Dcpu is also not necessarily exposed, for similar reasons. There might be different things being built, with different CPUs. Anything could happen.

What we need is a way for nix and other systems to be able to specify their true intent, which is this:

  • Make a release build, not a debug build.
  • Any binary artifacts that otherwise would be compiled for the native host, compile them for this target/cpu instead.

We should make both of these things easy to specify, regardless of what is in the upstream application's build.zig script.

Here is my concrete proposal:

  • Add a --release flag to zig build. So they can change -Drelease-safe to --release. In build.zig, upstream authors can observe this boolean, and pick a release mode, or, if they don't pick, an error will be emitted, something like, "upstream package supports fast, safe, and small. Please choose: --prefer-fast, --prefer-safe, or --prefer-small." Those options are also options that zig build will unconditionally support. So, probably Nix will have this: zig build --release --prefer-safe.
  • Add --override-host-target <target> and --override-host-cpu <cpu> flags to zig build. These are always supported, and do what they say on the tin: any time the host target or cpu would be used, the provided one is used instead.

Finally, we need to also tackle one more problem, which is that zig packages need to know when they are being built in a mode that means they should try to depend on system libraries rather than fetching dependencies via the zig build system. For this I think we will need one or more flags, but I haven't quite thought through all the ramifications and how exactly this will be integrated into systems like Debian or Nix, but that concern should not block the implementation of these other flags, which would help Nix right now.

@andrewrk andrewrk added proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. and removed use case Describes a real use case that is difficult or impossible, but does not propose a solution. labels Jul 6, 2023
@andrewrk
Copy link
Member Author

andrewrk commented Jul 6, 2023

Accepting the proposal now. When closing the issue, there should be 2 follow-up issues opened:

  • ability to provide package dependencies by system package manager rather than downloaded by the zig build system
    • related: reconciliation of dependency versions
  • ability to build C/C++ source files by the system toolchain rather than by zig's C compiler.

@andrewrk andrewrk added the accepted This proposal is planned. label Jul 6, 2023
@andrewrk andrewrk modified the milestones: 0.11.0, 0.12.0 Jul 20, 2023
@andrewrk
Copy link
Member Author

In addition to those two flags specified in #14281 (comment), this proposal also introduces a --system <zig-package-dir> flag that does the following:

  • makes zig build entirely skip fetching dependency packages to the global zig cache.
  • the build runner exposes system mode to build scripts
  • build scripts observe system mode and, where appropriate, avoid calling dependency(), instead linking directly to system libraries.
  • for dependencies on zig modules, dependency() is called like normal, and those packages are found in <zig-package-dir>. Note that only these dependencies, and not the previous bullet point, need to be populated inside <zig-package-dir>. It is the responsibility of the system package manager to ensure this directory is populated with the necessary packages before invoking zig build in system mode.

As for reconciliation of dependency versions, it will be the responsibility of build scripts to probe the system and decide if it is acceptable to proceed. Zig build system API will be provided for common tasks to aid this endeavor, such as checking the major version of a shared library.

As for ability to build C/C++ source files by the system toolchain rather than by zig's toolchain, the decision to do this can be done automatically when in system mode, and the logic for being a system toolchain driver can go directly into the zig build system.

@andrewrk andrewrk modified the milestones: 0.12.0, 0.11.0 Jul 21, 2023
@ifreund
Copy link
Member

ifreund commented Jul 21, 2023

Overall the proposed --system flag and proposed behavior sounds like a straightforward and effective way of solving this that is relatively easy to use and understand for zig developers and distro packagers alike.

* build scripts observe system mode and, where appropriate, avoid calling `dependency()`, instead linking directly to system libraries.

My only concern with this proposal is that the default and path of least resistance here for zig projects seems to be calling dependency and doing nothing further to handle the possibility of that dependency being provided by the system. I see this resulting in distro maintainers needing to patch the build.zig files of many projects to fix this.

Perhaps it would be possible to handle the simple case of this automatically by adding more declarative metadata to zig packages providing system libraries (e.g. the ffmpeg zig package could declare that it is equivalent to linkSystemLibrary("ffmpeg") in some way). I'm not clear on the details here though and such an approach may not be worth the complexity.

As for reconciliation of dependency versions, it will be the responsibility of build scripts to probe the system and decide if it is acceptable to proceed. Zig build system API will be provided for common tasks to aid this endeavor, such as checking the major version of a shared library.

A build.zig API for requiring a specific shared library soversion would be very much appreciated.

As for ability to build C/C++ source files by the system toolchain rather than by zig's toolchain, the decision to do this can be done automatically when in system mode, and the logic for being a system toolchain driver can go directly into the zig build system.

I assume this also covers linking the system libc version instead of the zig provided one?

@BratishkaErik
Copy link
Contributor

build scripts observe system mode and, where appropriate, avoid calling dependency(), instead linking directly to system libraries.

Perhaps related #3671 . There can be additional field like zig_artifact which will point to artifact of some dependency, and this field can be used with other fields from that proposal, like this:

const mach_glfw_dep = b.dependency("mach_glfw", .{});

exe.linkSomeLibrary(.{
    .system_artifact = .{
        .so_name = "glfw",
        .pkgconf_name = "glfw3",
        .vcpkg_name = "glfw3",
    },
    .zig_artifact = mach_glfw_dep.artifact("glfw"),
});

Then it uses either system_artifact or zig_artifact, depending on current mode.

@mitchellh
Copy link
Sponsor Contributor

mitchellh commented Oct 2, 2023

The ability for a library link to specify a dynamic counterpart (if any) to source from the system would be very welcome indeed, amongst the bigger picture proposed here.

As a bullet point, the way Ghostty does this today is with a flag -Dstatic=false (default true). This defaults to true so that for a new dev cloning out the project, all they need to build Ghostty is zig and git and we'll statically compile the world (except for stuff that can't be, like OpenGL, X11, etc.). But that's not a healthy way to build a release so we provide -Dstatic=false which will link most dependencies dynamically.

This is done very manually with a giant if in build.zig. I'd be super happy with the proposals above to standardize this on the library itself as well as stanardize modes and CPU targets and so on.

Looking forward to it.

andrewrk added a commit that referenced this issue Oct 4, 2023
Organize everything around a Fetch task which does a bunch of stuff in a
worker thread without touching any shared state, and then queues up
Fetch tasks for its dependencies.

This isn't the theoretical optimal package fetching performance because
CPU cores don't necessarily map 1:1 with I/O tasks, and each fetch task
contains a mixture of computations and I/O. However, it is expected for
this to significantly outperform master branch, which fetches everything
recursively with only one thread.

The logic is now a lot more linear and easy to follow. Everything that
is embarassingly parallel is done on the thread pool, and then after
everything is fetched, the worker threads are joined and the main thread
does the finishing touches of stitching together the dependencies.zig
import files. There is only one tiny little critical section and it does
not even have any error handling in it.

This also lays the groundwork for #14281 because in system mode, all
this fetching logic will be skipped, but the "finishing touches"
mentioned above still need to be done. With this branch, that logic is
separated out and no longer recursively tangled with fetching stuff.

Additionally, this branch:
 * Implements inclusion directives in `build.zig.zon` for deciding which
   files belong the package (#14311).
 * Adds basic documentation for `build.zig.zon` files.
 * Adds support for fetching dependencies with the `file://` protocol
   scheme (#17364).
 * Adds a workaround for a Linux/btrfs file system bug (#17282).

This commit is a work-in-progress. Still todo:

1. Hook up the CLI to the new system.
2. Restore the module table creation logic after all the fetching is
   done.
3. Fix compilation errors, get the tests passing, and regression test
   against real world projects.
@andrewrk andrewrk mentioned this issue Oct 4, 2023
11 tasks
andrewrk added a commit that referenced this issue Oct 7, 2023
Organize everything around a Fetch task which does a bunch of stuff in a
worker thread without touching any shared state, and then queues up
Fetch tasks for its dependencies.

This isn't the theoretical optimal package fetching performance because
CPU cores don't necessarily map 1:1 with I/O tasks, and each fetch task
contains a mixture of computations and I/O. However, it is expected for
this to significantly outperform master branch, which fetches everything
recursively with only one thread.

The logic is now a lot more linear and easy to follow. Everything that
is embarassingly parallel is done on the thread pool, and then after
everything is fetched, the worker threads are joined and the main thread
does the finishing touches of stitching together the dependencies.zig
import files. There is only one tiny little critical section and it does
not even have any error handling in it.

This also lays the groundwork for #14281 because in system mode, all
this fetching logic will be skipped, but the "finishing touches"
mentioned above still need to be done. With this branch, that logic is
separated out and no longer recursively tangled with fetching stuff.

Additionally, this branch:
 * Implements inclusion directives in `build.zig.zon` for deciding which
   files belong the package (#14311).
 * Adds basic documentation for `build.zig.zon` files.
 * Adds support for fetching dependencies with the `file://` protocol
   scheme (#17364).
 * Adds a workaround for a Linux/btrfs file system bug (#17282).

This commit is a work-in-progress. Still todo:

1. Hook up the CLI to the new system.
2. Restore the module table creation logic after all the fetching is
   done.
3. Fix compilation errors, get the tests passing, and regression test
   against real world projects.
andrewrk added a commit that referenced this issue Oct 8, 2023
Organize everything around a Fetch task which does a bunch of stuff in a
worker thread without touching any shared state, and then queues up
Fetch tasks for its dependencies.

This isn't the theoretical optimal package fetching performance because
CPU cores don't necessarily map 1:1 with I/O tasks, and each fetch task
contains a mixture of computations and I/O. However, it is expected for
this to significantly outperform master branch, which fetches everything
recursively with only one thread.

The logic is now a lot more linear and easy to follow. Everything that
is embarassingly parallel is done on the thread pool, and then after
everything is fetched, the worker threads are joined and the main thread
does the finishing touches of stitching together the dependencies.zig
import files. There is only one tiny little critical section and it does
not even have any error handling in it.

This also lays the groundwork for #14281 because in system mode, all
this fetching logic will be skipped, but the "finishing touches"
mentioned above still need to be done. With this branch, that logic is
separated out and no longer recursively tangled with fetching stuff.

Additionally, this branch:
 * Implements inclusion directives in `build.zig.zon` for deciding which
   files belong the package (#14311).
 * Adds basic documentation for `build.zig.zon` files.
 * Adds support for fetching dependencies with the `file://` protocol
   scheme (#17364).
 * Adds a workaround for a Linux/btrfs file system bug (#17282).

This commit is a work-in-progress. Still todo:

1. Hook up the CLI to the new system.
2. Restore the module table creation logic after all the fetching is
   done.
3. Fix compilation errors, get the tests passing, and regression test
   against real world projects.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted This proposal is planned. enhancement Solving this issue will likely involve adding new logic or components to the codebase. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. zig build system
Projects
Package Manager
  
Uncategorized
Development

Successfully merging a pull request may close this issue.

7 participants