Is there an import_path environment variable when importing?

Looking at the documentation for @import, it states:

path can be a relative path or it can be the name of a package. If it is a relative path, it is relative to the file that contains the @import function call.

For the case where it is the name of a package, is there a search path environment variable that specifies a series of folders to look for the package in? (I’m thinking along the lines of INCLUDE_PATH in C)

No, there’s no environment variable. In general, in order to provide path as the name of the package you have to add the dependent package’s module as an import in your build script.

See points 3) and 5) in Basic Usage section for an example:

Namely, the minimal example would be:

// Declare package dependency called "johny"
const johny_dep = b.dependency("johny", .{
    .target = target,
    .optimize = optimize,
});

// Declare dependency's module with the name from its build script
const johny_mod = johny_dep.module("johny");

// Add dependency module as the root module's import
exe.root_module.addImport("my_johny_import", johny_mod);

Then, in your code you would write:

const johny = @import("my_johny_import");

@tensorush – a few follow-up questions just to increase my understanding…

normally b.dependency includes a .root_source_file which both “roots” the module’s source tree somewhere, as well as designates a .zig file which will be available through the @import

but what about the case where my module is actually a full tree of many .zig files??? can i then reference "my_johny_import/folder/file.zig" ???

thinking (say) of johny.zig as the root source file of this module, presumably it can internally reference “./folder/file.zig” which is relative to the root tree ???

and correct me if i’m wrong, but these “module root trees” cannot overlap each other… said another way, each .zig file within some program can only belong to ONE named module???

I’m not sure what you mean by:

b.dependency accepts the name of the dependent package declared in your build.zig.zon as well as user input options exposed by that package’s owner, such as target and optimize.

You might be thinking of importing a private module:

for sure i’m thinking about a private (local??) module not fetch via build.zig.zon

but my question still stands about accessing other .zig files within this local module – especially from outside the module… for example, @import("johny/folder/file.zig")

i think the “spirit” of the original question was looking to mimic the C include-path, which enables me to select individual files #include <lib/folder/file.h> without knowing exactly where lib is root in the filesystem…

Yes. As a corollary, in C there is an INCLUDE_PATH (which contains a list of folder paths) such that when I say, #include <foo.bar>. It traverses the INCLUDE_PATH folders looking for a file named foo.bar.

In that way, I can make use of software from other places – without having to hard code a relative or absolute path to that file within my code. Thus my code is portable – with the only need being to set INCLUDE_PATH before building.

To use @biosbob 's code as an example, he uses a line like this to bring in generated code: pub const em = @import("../../zigem/em.zig").

I’m looking for a way to simply say, @import("zigem/em") without having to know its relative position to me.

There is a mechanism for this in zig; it is called modules.
The author of the module em declares where is the module and the consumer just uses it with const em = @import("em");.

There are two type of modules, local and packaged.
Local modules are usable by entering a path to a zig file and exposing it as a local module.
Packaged modules are exported from packages that are imported as dependencies; these packages of modules are either local paths of your system or url downloads.

3 Likes

Here’s an example of how this works. This repo is a module, but it makes a bunch of executable artifacts for benchmarking purposes. The module itself lives in ./src and all the executables are in ./demo. In Zig 0.13 this is fine, but on the master branch you can’t have an import like @import("../src/runerip.zig"), because a root file isn’t allowed to reach out of its directory like that.

So build.zig makes the module (in this case it was going to do that anyway), and installs it for the executables which need it. Which can then just call @import("runerip"). Easy.

1 Like

OK. We can look into this as an option. We have an added twist in that the included file may not exist at the time of zig build. Thanks much for all your advice.

there is still something subtle we’re missing here… but rather than referring to C, let’s use Java as our “import” model…

while the “unit of import” in java is an individual class (a single source file not unlike a zig module), my import statement identifies the class within some “package scope” whose name trivially maps to a (relative) file-system directory path… when i import java.util.HashMap, all i really know is that somewhere there existed a file named java/util/HashMap.java which became java/util/HashMap.class… what i don’t know is the full path to these files – which is fine…

said another way, the “class name” java.util.HashMap is a globally-unique moniker that stands apart from its physical location on my disk… but even MORE important, java.util is a namespace that contains dozens and dozens of individual classes (not unlike a library)…

so, can i deliver a collection of zig modules (organized in some sort of named hierarchy that easily maps onto filesystem paths) which i can refer to with a SINGLE logical name ("somepkg) that enables me to further select individual modules (SomeMod) ???

obviously i could follow the pattern of "std" and export a “tree” of library namespaces… but then i’m faced with tediously reflecting my package’s filesystem hiearchy in some special somepkg.zig file at the root of the tree…

bottom line: is it possible to say @import("somepkg/SomeMod.zig") where i’ve specified the root of somepkg at build-time??? if so, i can easily emulate the java import paradigm AND the C include path…

Yes. A build.zig.zon dependency looks like this:

        .fun_package = .{
            .url = "https://github.com/example/different_name/archive/refs/tags/v0.10.4.tar.gz",
            .hash = "1220db3fa6510f1686587aab46ac92a882d4f5a287a20d7b687f654a7b8ce3a0e8d6",
        },

That gets used in a build.zig like so:

    if (b.lazyDependency("fun_package", .{
        .target = target,
        .optimize = optimize,
    })) |fun_dep| {
        awesome_module.addImport("great_fun", fun_dep.module("lots_o_fun"));
    }

Note a common factor here: the canonical name is what you’ll find in the URL, but Zig doesn’t care: what you call the package in build.zig.zon is how you refer to it in build.zig. Similarly, the string argument to addImport is anything you want, and it maps to the module named in fun_dep.module. In this case, the awesome_module source code can call @import("great_fun") and it gets the module "lots_o_fun" from a repo which happens to be named "different_name" by the author.

A package can export as many modules as it would like, each with any name it chooses. Those modules can have overlapping files, but this means that importing two modules with a common file will fail. As we’ll see, with the right code organization in the build file, this doesn’t need to be a problem.

If you want to follow a Java-esque one-file-one-module practice (respectable) this actually makes things easier. These modules can depend on each other as modules (@import("dependency")) but not as files (@import("dependency.zig"); if you file-import the same file into two modules, and other code tries to import both of those modules, compiling will fail. So any inner code file which you don’t intend to export, which is just a helper for one other module, that can be imported by file name: but it has to be reachable from the root directory of that module. This is all fairly new, and makes the language much more module-oriented.

Other than a certain amount of boilerplate, I consider this a good development. Exporting every file in a library is less common than having a single entry point which exports the public namespace of the entire package, but the former is decidedly possible, it’s just a bit more verbose.

Note that the module names are just strings. So if you want to export one module per file, and organize them according to a familiar directory structure, you can. This can be done when exporting, or when importing, modules from a package.

So this:

// build.zig
awesome_module.addImport("great/fun", fun_dep.module("lots/o/fun"));

// elsewhere 
const fun_stuff = @import("great/fun");

Is perfectly legal.

The price paid is that even if you want to follow the naming convention of the original module, you do have to repeat yourself in the build.zig. It would be a nice affordance to have something a bit more compact when the import name ("great_fun" here) and the export name ("lots_o_fun") happen to be identical.

2 Likes

very informative…

so when a (zig) package exports multiple (zig) modules, how/where/when are the individual module names declared by the supplier of the package… i’m clear how the package consumer can use whatever local names they wish through declarations in their own build.zig and build.zig.zon files… said another way, where does the module name "lots_o_fun" actually get declared???

also, i’m thinking of a (zig) module as a single file – not unlike a .java class… if all of my modules are single files (but may included other, similarly named modules), do i really have an overlapping file issue??? said another way, my modules are atoms in this universe…

In the build.zig for the package, looks something like this:

    // Export as module to be available for @import("lots_of_fun") on user site
    const fun_module = b.addModule("lots_of_fun", .{
        .root_source_file = b.path("src/lots_of_fun.zig"),
        .target = target,
        .optimize = optimize,
    });

You can give this dependency to other modules in the same build file also:

other_module_same_file.addImport("lots_of_fun", fun_module);

For a not-module artifact you can find the module part with some_exe.root_module, after that it works the same way.

Like I said, verbose. :slight_smile: But very flexible, it makes no assumptions about how you’d like to organize code, and imposes no such requirements on other code which wants to use the package.

In the context of Zig that terminology/use of the word module is confusing/wrong and makes communication difficult. You can create a module for every single file, but files aren’t modules, source files belong to some module (possibly multiple which then prevents those modules from being used together).

Personally I think if you take these ideas about how you want to organize things first and then apply those to Zig, you are going against the grain of what is idiomatic/more usual in the language. I think you would be better off letting go of previous notions of how things should be organized and instead try to see what kind of organization “emerges”/aligns better with how the language is designed.

Try to let the language change how you think about solving your problems, so that you can adapt to use the language like it was intended, instead of emulating another language causing you to have to generate lots of boilerplate that bridges the misalignment in how the language is typical to use vs your own view of how you would want to use it.

Basically my 2 cents are, try to use the language in a more typical way, before you try to use it in an untypical way, because chances are you might actually learn to prefer the typical way and no longer want to use the untypical way which comes with more friction, because it is unusual.

I agree with this quote:

“A language that doesn’t affect the way you think about programming, is not worth knowing.”

Alan Perlis.

While Java style affects you in one way, have you allowed Zig to affect yourself where you could reconsider your organization style and see whether the Zig style has its own benefits?

1 Like

i agree that i should i try map my own concepts of source-file organization onto the zig nomenclature – if for no other reason than to avoid using terms like ‘module’ and ‘package’ with meanings different from zig…

and let’s remove any specific references to java (which clearly uses the term ‘package’ to mean something very different from a zig ‘package’)…

to that end, let me present a source-file organization that is “neutral” to this terminology… what i’m looking for here is the best way to realize this (conceptual) three-level hierarchy within zig…

- bundle
  - bucket
    - Unit
    - Unit
      ...
  - bucket
    ...
- bundle
  ...

the ‘bundle’ is my unit of delivery and seems to easily map onto a zig ‘package’… and correct me if i’m wrong, but one zig ‘package’ can depend on other zig ‘packages’… from my perspective, this “depends” relation should be acyclic…

this is essentially the domain of build.zig.zon, not unlike the package.json file in a npm package… i release that the contents of the zig ‘package’ are invariably fetched into the cache; but for clarity, let’s imagine that the “top-level” of my current project workspace contains copies of (or links to) each package’s content… and yes, i realize that i can chose a “local” name for each package distinct from the name given by its provider…

at the lowest level of my hierarchy, assume that each ‘Unit’ is a source-file named Unit.zig… and while Unit.zig can (obviously) use "std" and other zig libraries, my units will mostly import other units found somewhere in my project workspace… the Unit import relationship is also acyclic by design.

the middle layer of my hiearchy – the ‘bucket’ – really exists as a way to further aggregate Unit source files into a logical collection that is somehow distinct from the physical ‘bundle’ in which it is contained… as a “best practice”, bucket names strive to be globally unique by following a convention such as git.biosbob.utils or com.txn.cc23xx… (a similar “prefix” discipline could be applied to bucket names as well…)

for one unit to “import” another unit, it will use a canonical identifier of the form bucket-name/Unit-name… and obviously, zig provides multiple opportunities to create a “local” alias for this canonical identifier… in zig, i would want to say something like:

const Timer = @import("git.biosbob.utils/Timer");

so now let me ask about realizing this in zig… presumably, git.biosbob could create a top-level bundle (ie, a zig package) that aggregates multiple Units with names of the form git.biosbob.*… and i could specifically create a zig module for each – with a name like "git.biosbob.utils/Timer" that is associated with a single root source file… since the “tree” associated with each such module contain just one file, presumably i have no issues with overlapping root trees…

alternatively, i could place a special bucket-root.zig file in each of my bucket folders and declare this file as the root of a zig module named "git.biosbob.utils"… internally, this file would look something like:

pub const Timer = @import("./Timer.zig");
    ...

the client would then import this unit by saying:

const Timer = @import("git.biosbob.utils").Timer;

personally, i prefer the "git.biosbob.utils/Timer" approach; one less set of bucket-root.zig files to maintain and clutter up my workspace… but clearly the number of zig modules named in each zig package will be much larger…

which then leads to my final observation and question… by following what some might see as a “rigid” folder/file organization, i can VERY EASILY generate the requisite build.zig and build.zig.zon files to realize my source code organization paradigm within zig…

i already name my bundles/buckets/Units in my project workspace… why not simply harvest that information as a specification for information that is ultimately codified in build.zig and build.zig.zon??? is there any precendence for generating these files in the zig ecosystem???

There is precedence for manually written build.zig files that use some logic to for example loop over a bunch of examples and define the build steps from them using some data/description of what each example needs.

I am not aware of any projects that generate the build.zig or build.zig.zon files, you could write a generator that generates this code/these files according to some pattern, but because the build.zig already allows you to write code that for example creates multiple modules with specific names with arbitrary domain specific logic, I think it is rare for people to have a need to generate that file.

One way that would be more usual, would be to create a package of helper functions that can be used from a build.zig and then import that package in your build.zig use its functions from that import and call them with some data to create whatever modules / compile steps you would want to create.

I think you are thinking more in terms of a weak build system that requires a rigid code organization, Zig tends to be more flexible with code organization and invites you to see the build system more like something that could be extended with custom build steps, to avoid having to pre-generate stuff and instead generate stuff on demand when it is actually needed.

It seems you want to create one rigid structure that works for everything.
While that may work, my suspicion would be that this could make it more difficult to use the Zig build system to its full strength and potential, because it probably would mean that you have to use weird escape hatches or work-arounds, when you want to use Zig build system things, that weren’t anticipated in the rigid structure. (So it might make sense to also think about where the boundary between your structure and the Zig build system is and how that boundary can be crossed)

Maybe you could try to create a helper function that iterates over the source directories and verifies whether they conform to your structure and then optionally also generates the modules and in a later step adds the imports. But I guess to add the imports you would have to find the imports in the source files, which may mean that you need to parse them.

Personally I would use a more manual approach of just creating things manually in a more ziggy way, but I think discovering imports and automatically adding the addImport calls to the auto created modules could work in theory, but could be slow, unless you find a way to make caching work for that.

It also would be a little bit like a dog chasing its own tail, the benefit of a more manual approach is that it is simpler and more straightforward and that you can actually read the code and understand what it does directly, instead of first having to learn how some other specific structure results in a bunch of things being generated implicitly.

I think your way of wanting to do things is a different philosophy, in this case these 2 points from the zig zen aren’t followed:

  • Favor reading code over writing code.
  • Focus on code rather than style.

I think your approach favors writing code. The way I interpret the second bullet, I think it wants us to ditch the top-down beautifully imagined architectures and instead just write code that works bottom up and then incrementally refine it (which is another bullet), at least that is how I read it.

I guess in some way that is to be expected, because your project essentially maps a language to Zig, thus mapping a different philosophy to Zig constructs.

If you want to experiment with creating and using helper functions in your build.zig I think the zine project may have some interesting code/examples in its build.zig to read and understand. (I still have to take a closer look at that and play around with it)
Ziglings might be another projects you could take some ideas from / learn specific techniques.

3 Likes

Let’s assume that “git.biosbob” is the bucket.

Step one: we’re going to just call it “biosbob”. Because it will look like this in the build.zig.zon:

.dependencies = .{
    .biosbob = .{
        .url = "https:/this-is-fully-qualified-already-we-do-not-need-com-names",
       .hash = "will uniquely identify the release",
},

You could call .biosbob @"git.biosbob" but that’s fugly and far from idiomatic.

Step two, import like this:

pub const Timer = @import("biosbob").Timer;

The key here is Zig’s lazy compilation model. You can have a huge “god file” which imports everything else and exports them by name, if user code only wants Timer, that’s all that even gets compiled. No tree-shaking necessary, and minimal benefit to splitting everything up in to small pieces (your "Unit"s). Those can just be files.

Last time I wanted to illustrate that, if you want to follow a one-file-one-module model, you can do it: but if you try it, you won’t want to, because you end up recreating the entire dependency graph inside the build.zig, it’s a lot of extra work and verbosity. There might be reasons anyway: perhaps this makes more sense to your users, for example. The build system is very flexible and you can use it that way if you would like.

But you’re going to have a better time if you take advantage of laziness, as well as the fact that (again very much unlike Java) nothing actually has a name, not in a way which can’t be changed at the point of use, so com hierarchies don’t buy us anything here. You’ll end up with the same sort of layout, just less work, and easier to use.

2 Likes