Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions docs/packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ libbar = { path = "../libbar", version = "^1.0.0" }
gitlib = { git = "https://example.com/gitlib.git", rev = "v1.0.0" }
```

- `[package]` carries `name` and `version`.
- `[package]` carries `name` and `version`. Optional `lib = "src/api.fl"`
selects the package's public module entry point, relative to the package root.
The path must name a `.fl` file inside the package. When `lib` is set, a
missing or invalid target is an error at import time; Flint does not silently
choose `main.fl` instead. Without `lib`, the legacy `main.fl`, same-named,
and flat-file layouts continue to work.
- `[dependencies]` maps a name to a path string, a `{ path, version }`
table, or a `{ git, rev, version }` table. `rev` may be empty, which
tracks the default branch; the lock always pins the commit installed.
Expand Down Expand Up @@ -81,9 +86,17 @@ installs; the next install records the hashes. `pkg list` output is
unchanged.

Each dependency lands in `flint_modules/<name>/` with its sources and
its own `flint.toml`. A package can expose its default module as
`main.fl`, as a same-named file such as `levenshtein/levenshtein.fl`,
or as a flat `flint_modules/<name>.fl` file.
its own `flint.toml`. A package may declare its public module explicitly:

```toml
[package]
name = "levenshtein"
version = "1.0.0"
lib = "src/levenshtein.fl"
```

Otherwise, Flint keeps the compatibility layouts: `main.fl`, a same-named
file such as `levenshtein/levenshtein.fl`, or a flat `flint_modules/<name>.fl` file.

```text
flint_modules/
Expand All @@ -108,8 +121,9 @@ import "gitlib/g.fl"
print(g.greet())
```

A bare name first checks installed packages in `flint_modules/`:
`<name>/main.fl`, then `<name>/<name>.fl`, then the flat
A bare name first checks installed packages in `flint_modules/`. If the
package manifest declares `lib`, that path is the sole entry point. Otherwise,
resolution checks `<name>/main.fl`, then `<name>/<name>.fl`, then the flat
`<name>.fl` layout. If no package matches, the standard library is
checked (so `import math` still imports the library). A quoted path with
a `/` or a `.fl` extension can address a file relative to the importing
Expand Down
169 changes: 132 additions & 37 deletions rust/flintlang/c-src/src/runtime/sys.c
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,102 @@ static char *pkg_candidate(const char *path)
return NULL;
}

/*
* An installed package may explicitly choose its public module with
* [package] lib = "src/api.fl". The path is relative to the package root;
* it must stay inside that root and name a .fl file. A declared but invalid
* or missing entry point does not silently fall back to a different file.
*/
static char *pkg_declared_lib(const char *package_dir, bool *declared)
{
*declared = false;
char manifest[8192];
if (snprintf(manifest, sizeof(manifest), "%s/flint.toml", package_dir) >=
(int)sizeof(manifest))
return NULL;
FILE *file = fopen(manifest, "r");
if (file == NULL)
return NULL;

bool in_package = false;
char line[4096];
char *entry = NULL;
while (fgets(line, sizeof(line), file) != NULL) {
char *p = line;
while (*p == ' ' || *p == '\t')
p++;
if (*p == '\0' || *p == '\n' || *p == '#')
continue;
if (*p == '[') {
char *end = strchr(p, ']');
if (end == NULL)
continue;
end[1] = '\0';
in_package = strcmp(p, "[package]") == 0;
continue;
}
if (!in_package || strncmp(p, "lib", 3) != 0 ||
(p[3] != ' ' && p[3] != '\t' && p[3] != '='))
continue;
p += 3;
while (*p == ' ' || *p == '\t')
p++;
if (*p++ != '=')
continue;
while (*p == ' ' || *p == '\t')
p++;
*declared = true;
if (*p++ != '"')
break;
char *end = strchr(p, '"');
if (end == NULL)
break;
char *tail = end + 1;
while (*tail == ' ' || *tail == '\t' || *tail == '\r' ||
*tail == '\n')
tail++;
if (*tail != '\0' && *tail != '#')
break;
*end = '\0';
size_t len = strlen(p);
if (len < 4 || strcmp(p + len - 3, ".fl") != 0 ||
p[0] == '/' || strchr(p, '\\') != NULL)
break;
bool unsafe = false;
const char *part = p;
for (const char *q = p;; q++) {
if (*q == '/' || *q == '\0') {
size_t part_len = (size_t)(q - part);
if (part_len == 0 ||
(part_len == 1 && part[0] == '.') ||
(part_len == 2 && part[0] == '.' &&
part[1] == '.')) {
unsafe = true;
break;
}
if (*q == '\0')
break;
part = q + 1;
}
}
if (unsafe)
break;
size_t dir_len = strlen(package_dir);
if (dir_len > (size_t)-1 - len - 2)
break;
entry = malloc(dir_len + len + 2);
if (entry != NULL)
snprintf(entry, dir_len + len + 2, "%s/%s", package_dir, p);
break;
}
fclose(file);
if (entry == NULL)
return NULL;
char *found = pkg_candidate(entry);
free(entry);
return found;
}

char *sys_resolve_module(const char *path)
{
size_t pathlen = strlen(path);
Expand Down Expand Up @@ -471,44 +567,43 @@ char *sys_resolve_module(const char *path)
pkg_join3(root, "flint_modules", path);
char *found = NULL;
if (dir_main != NULL) {
size_t need = strlen(dir_main) + 9;
char *main_file = malloc(need);
if (main_file != NULL) {
snprintf(main_file,
need,
"%s/main.fl",
dir_main);
found = pkg_candidate(
main_file);
free(main_file);
}
/*
* Also support a single-file package named after
* itself, e.g. levenshtein/levenshtein.fl.
*/
if (found == NULL) {
size_t need_named = strlen(dir_main) + 1 +
pathlen + 3 + 1;
char *named = malloc(need_named);
if (named != NULL) {
snprintf(named, need_named, "%s/%s.fl",
dir_main, path);
found = pkg_candidate(named);
free(named);
}
}
if (found == NULL) {
size_t need2 =
strlen(dir_main) + 4;
char *flat = malloc(need2);
if (flat != NULL) {
snprintf(flat,
need2,
"%s.fl",
bool has_declared_lib = false;
found = pkg_declared_lib(
dir_main, &has_declared_lib);
if (!has_declared_lib) {
size_t need = strlen(dir_main) + 9;
char *main_file = malloc(need);
if (main_file != NULL) {
snprintf(main_file,
need,
"%s/main.fl",
dir_main);
found = pkg_candidate(
flat);
free(flat);
found = pkg_candidate(main_file);
free(main_file);
}
/*
* Also support a single-file package named after
* itself, e.g. levenshtein/levenshtein.fl.
*/
if (found == NULL) {
size_t need_named = strlen(dir_main) + 1 +
pathlen + 3 + 1;
char *named = malloc(need_named);
if (named != NULL) {
snprintf(named, need_named, "%s/%s.fl",
dir_main, path);
found = pkg_candidate(named);
free(named);
}
}
if (found == NULL) {
size_t need2 = strlen(dir_main) + 4;
char *flat = malloc(need2);
if (flat != NULL) {
snprintf(flat, need2, "%s.fl", dir_main);
found = pkg_candidate(flat);
free(flat);
}
}
}
free(dir_main);
Expand Down
Loading
Loading