You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Currently, we only have a way to refer to Dart libraries in a canonical way (after we have a packages config) for libraries inside the lib/ directory.
This means we have to use relative imports in test/, bin/, tool/, samples/, examples/, and friends.
This means we have a way to uniquely point to public definitions inside a library e.g. package:my_package/src/foo.dart::class::Foo::method::bar, but no way to do the same for private definitions. (Useful for dart-lang/native#2891.)
Also it means we don't have a standardized way to point to samples or other data files in our tooling:
I like the idea of officially adopting a scheme, FWIW my current favorite bikeshed color is package-internal ;) avoiding "package private" because it already means "under lib/src"
Re: import statements
Is it allowed for a source under lib to import a non-lib from its own package; and/or is there a warning issued
Is it allowed for a source in one package to import a non-lib from another package? That add access to a lot of previously-inaccessible code :)
I guess @lrhn probably has some thoughts on this one :)
It's not impossible. We already have the root of the Pub package in package_config.json.
I'd prefer not repeating the mistake of package: URIs, and put the package name in the authority.
Maybe pub://packageName/path/to/file. Or pub-pkg: as scheme. No need to be long.
This is a reference to the Pub package, not the Dart package. The language doesn't know about Pub packages, it only knows that package:... URIs can (sometimes) be resolved to the content of a Dart package, which is only the code in the lib/ directory of the Pub package. Everything around that is historically Pub-specific, and only recently recognized at all (and only for language versioning).
Maybe we can generally ensure that files use the pub: URI as their identity when they are inside a Pub package, like we try to make references to lib/ files use package: URIs. That would avoid having a file: URI as base when using relative paths.
Relative paths will still work inside the same package, and you can use a rooted path as relative to the current pub package's root, like import '/test/test_helper.dart';. If the containing file's URI is pub://my_pkg/tool/test/my_test.dart, then /test/test_helper.dart will automatically resolve to pub://my_pkg/test/test_helper.dart, using the completely normal URI resolution rules.
(I had to hack the Uri class to preserve the package name in package:package_name/... from being treated as part of the path, while people are still doing packageUri.path.split('/').first to get the package name. Ugh.)
As @davidmorgan says, there may need to be some limits on which URIs you can use from where.
And maybe they need to be "you can't refer to other packages at all", or we might completely break dependencies!
(So the answers to those two questions are probably "no" and "no".)
The current separation is:
Code inside lib/ (and bin/?) depend only on non-dev-dependencies.
Code depending on another package inherits the other package's non-dev-dependencies transitively.
So referencing any code inside lib/ of the other package is safe, all its dependencies are available in your own package resolution config.
Code outside of lib/ can depend on dev-dependencies. Dev-dependencies are never inherited.
If you can do import 'pub://other_pkg/tool/helper.dart';, then that library should be compiled in your package configuration, which may not include the dev-dependencies of other_pkg. And helper.dart is allowed to refer to such.
If you do dart run other_pkg:binexeI think it runs in that code in its own package configuration.
That should be fine, you're not mixin code from different configurations in the same program.
Importing non-lib/ source from another package is probably completely and utterly unsafe, and can't be allowed.
(I tried coming up with some kind of new dependency relation, and a way for an author to opt-in to non-lib/ code
being available outside of the package, but none of it would be better than just having an extra package for that code, where the shared code is in lib/.)
So, as @eernstg said in the other issue, these are URIs for files "not intended to be imported by dart libraries in other packages".
(I'm not sure this needs to be a language issue, if it's mainly tooling.)
Doesn't stop other uses of the URI, like tools referring to specific files in specific packages. (Referring to files is a source concept and only makes sense while compiling, so it's probably mainly for tools.)
We can also allow files inside a pub package to refer to itself using pub://myname/path/file.dart in imports/exports/parts, but not to any other package. And we can try to canonicalize the URIs of entry points or other externally referenced libraries to such URIs when compiling inside a package config where they have such a Pub-package URI. Then you can treat the package root as the path root for imports.
And start allowing that format in Dart import statements?
So only to a very limited degree (self-package only). But then we should prefer and enforce that URI format, so you can depend on it.
Nit: Please use package:my_package/src/foo.dart#class::Foo::method::bar.
The part before the fragment identifies the file, the fragment part is not part of the path or file name, and has a meaning internal to the file.
(I don't care what the format is after the #, it can also use / and # if it wants to, ...foo.dart#class/Foo#method/bar, just that it's separated from what is actually a path.)
We already have a name for the place we want to resolve to, it is right in the package config, the "rootUri". Given that, I would like to propose the URI scheme package-root. That is what this is pointing to, the true "root" of the package (which may or may not also be the package URI root).
I do like the idea of using the authority for the package name, so something like package-root://foo/lib/foo.dart.
This is not a pub specific concept at all, it is baked right into the package config, so I do not think pub should be anywhere in the name. Internally we use the same package layout, because it just makes sense, even though there are no pubspecs for internal Dart packages. But it is irrelevant as this is a package config concept now not a pub concept.
Separately, I am interested in being able to distinguish between two identical URIs which actually resolve to different places, because multiple package config files exist within one context. This is a common occurrence for IDE environments and also command line AI environments, or really any development environment at all outside of google3 or pub workspaces. This is relevant not only to this proposal but also package: URIs. They are really only canonical when put in the context of a package config. So, I would like to use either the host or query portions of the URI to point at the package config which should be used to resolve a given URI.
I don't think we should use asset: as a general scheme as I think it is a little too generic. I kind of regret choosing that name originally, I just chose it because build_runner calls all files "assets", but that is sort of build_runner specific.
I do like that its short.
And since imports are of the format import 'uri', we could make a case for import 'asset:package_name/bin/main.dart';
I do not think these URIs should be valid in imports, if you want people to be able to import something put it under lib, that is what it's for. This would effectively make altering even your tests or examples a breaking change.
I really don't see a need for importing these files, and I do not want anybody to be able to import these from my packages, or tbh even to be able to try and reference them at runtime at all.
These URIs in general should only be a development time internal detail for certain tools, so they can have a canonical way to speak about files that are outside of lib. Similar to multi-root-scheme (and actually, could be a replacement for that potentially).
Note that build_runner even does not allow access to these files in general, unless the package opts in to exposing them.
+1 I don't think we should use the asset: scheme, it's very confusing in the context of Flutter where we have assets in the pubspec.
I really don't see a need for importing these files, and I do not want anybody to be able to import these from my packages, or tbh even to be able to try and reference them at runtime at all.
+1 I want a canonical way to refer to things, and for that way to be compatible across tools.
So, as @eernstg said in the other issue, these are URIs for files "not intended to be imported by dart libraries in other packages".
(I'm not sure this needs to be a language issue, if it's mainly tooling.)
We look to the language team for standardization 😉 🤷♂️
For reasons such as this! 👇 🙏
Nit: Please use package:my_package/src/foo.dart#class::Foo::method::bar.
The part before the fragment identifies the file, the fragment part is not part of the path or file name, and has a meaning internal to the file.
I love the addition of @version. To make it compliant with the URI spec, we should put version before the @ and use the "authority": package-root://<version>@<package-name>/<package-root-relative-path>.
If you do dart run other_pkg:binexeI think it runs in that code in its own package configuration.
Correct. @jonasfj@sigurdm and I have been toying withdart run other_pkg@<version>:binexe. (Which does not follow URI conventions either. <version> in such command can be arbitrary JSON...)
Putting the "version" into the package reference does make it Pub-specific, and may warrant a pub in the scheme name.
(Using package_name@version is tricky, because if parsed by the standard URI rules, that makes the package_name the user-info, and the version the host. Sadly, can't use //package_name:version/, because the "port" after : can only be a single number. Swapping them, as suggested, would be better, but only if the version is simple enough to fit into the authority to begin with.)
Everything else has only referred to package info available in a package_config.json, which means one version of each package.
Putting anything into the authority part of a package: URI is a bad idea. The format has no / at the front of the path, but a URI with an authority must start a non-empty path with /. So that's a no-go.
If we want to refer to the package-config file in the URI, a query is probably better: package:foo/path/to/file.dart?config=file:///absolute/path/package_config.json
The query is part of the resource identifier, a fragment is not, so
refers to the same resource (and whoever reads that resource may know what the fragment identifier means in relation to the content).
In standard URI interpretation at least. Any scheme can define what everything after it means, like data: and email: do, but you won't get any help from the Uri class if you don't follow the standard rules.
So use a query for that. It's also not easy to cram a full URI into the authority section of a URI, even if we wanted to. You'll have to escape everything, making it much harder to read.
And a query will also work the same for a package-root: URI. So, that could work:
If no specific config is given, it should occur in a context where a config is given. (And you can't give one in Dart imports, or where a config is otherwise implied!)
We could use package: for both, and say that if there is an authority, that's the package name and the path is root-relative, and if not, the first part segment is the package name and the rest is a lib/-relative path.
I just think it will confuse existing code that works with package: URIs, which there is far too much of. (Nobody should parse Package URIs themselves, and do var packageName = uri.pathSegments.first;. There should be abstractions for that. Alas, a little too late.)
So a new scheme is safer.
If we want to specify a version, then package-root://pkgname/path?version=1.2.25 could work. It doesn't work together with a config=, and whoever interprets the URI needs to understand what the query means.
If we want this, it could be integrated into package:package_config. That package already has the package config loading and resolution built in.
To support a ?config=... parameter, it probably has to provide a "context" object where it can cache package configurations, so it doesn't have to load a package-config file every time it resolves such a URI. And it should be a user-controlled context that can be cleared if a config file changes, which isn't something the package wants to be detecting itself. (So probably whoever creates the context can provide a callback to be notified every time a new configuration is added, and be given a way to invalidate a cached configuration.)
No version parameter, that's outside the scope of package configurations.
Putting the "version" into the package reference does make it Pub-specific, and may warrant a pub in the scheme name.
I like this distinction. Using a different scheme for whether pub is needed or not. package-root:// requires a package config, and doesn't support asking for a different version. And, pub:// could support versions.
It seems we are converging to package-root as scheme, I'll update the proposal title.
changed the title [-]Introduce `package-file:` or `package-private:` canonical (library) uris[/-][+]Introduce `package-root://` canonical uris[/+]on Feb 2, 2026
The ?config=<path> query param is anyways more accurate, even in the pub world you actually have a mixture of pub dependencies and other dependencies, so the version alone isn't actually enough. If you instead say to resolve a URI with a specific package config, that is unambiguous.
The only thing I don't love about it is in my case I plan to expose these URIs to AI agents as resources, and in that world shorter URIs are better - they are both surfaced to users sometimes and also consume tokens.
But, I think it makes sense, and it can in any case be optional so we can choose to use it or not.
Just came by this thread again. A config=... could be allowed to omit the .dart_tool/package_config.json, which isn't much, but it is some. Pointing to a directory would trigger the "find config for directory" logic, which would find .dart_tool/package_config.json as the first thing. (Or check the parent directory, but if brevity is the goal, might as well have the parent directory directly in the URL.)
Activity
davidmorgan commentedon Jan 23, 2026
I like the idea of officially adopting a scheme, FWIW my current favorite bikeshed color is
package-internal;) avoiding "package private" because it already means "under lib/src"Re: import statements
libto import a non-libfrom its own package; and/or is there a warning issuedlibfrom another package? That add access to a lot of previously-inaccessible code :)I guess @lrhn probably has some thoughts on this one :)
lrhn commentedon Jan 23, 2026
It's not impossible. We already have the root of the Pub package in
package_config.json.I'd prefer not repeating the mistake of
package:URIs, and put the package name in the authority.Maybe
pub://packageName/path/to/file. Orpub-pkg:as scheme. No need to be long.This is a reference to the Pub package, not the Dart package. The language doesn't know about Pub packages, it only knows that
package:...URIs can (sometimes) be resolved to the content of a Dart package, which is only the code in thelib/directory of the Pub package. Everything around that is historically Pub-specific, and only recently recognized at all (and only for language versioning).Maybe we can generally ensure that files use the
pub:URI as their identity when they are inside a Pub package, like we try to make references tolib/files usepackage:URIs. That would avoid having afile:URI as base when using relative paths.Relative paths will still work inside the same package, and you can use a rooted path as relative to the current pub package's root, like
import '/test/test_helper.dart';. If the containing file's URI ispub://my_pkg/tool/test/my_test.dart, then/test/test_helper.dartwill automatically resolve topub://my_pkg/test/test_helper.dart, using the completely normal URI resolution rules.(I had to hack the
Uriclass to preserve the package name inpackage:package_name/...from being treated as part of the path, while people are still doingpackageUri.path.split('/').firstto get the package name. Ugh.)As @davidmorgan says, there may need to be some limits on which URIs you can use from where.
And maybe they need to be "you can't refer to other packages at all", or we might completely break dependencies!
(So the answers to those two questions are probably "no" and "no".)
The current separation is:
lib/(andbin/?) depend only on non-dev-dependencies.lib/of the other package is safe, all its dependencies are available in your own package resolution config.lib/can depend on dev-dependencies. Dev-dependencies are never inherited.If you can do
import 'pub://other_pkg/tool/helper.dart';, then that library should be compiled in your package configuration, which may not include the dev-dependencies ofother_pkg. Andhelper.dartis allowed to refer to such.If you do
dart run other_pkg:binexeI think it runs in that code in its own package configuration.That should be fine, you're not mixin code from different configurations in the same program.
Importing non-
lib/source from another package is probably completely and utterly unsafe, and can't be allowed.(I tried coming up with some kind of new dependency relation, and a way for an author to opt-in to non-
lib/codebeing available outside of the package, but none of it would be better than just having an extra package for that code, where the shared code is in
lib/.)So, as @eernstg said in the other issue, these are URIs for files "not intended to be imported by dart libraries in other packages".
(I'm not sure this needs to be a language issue, if it's mainly tooling.)
Doesn't stop other uses of the URI, like tools referring to specific files in specific packages. (Referring to files is a source concept and only makes sense while compiling, so it's probably mainly for tools.)
We can also allow files inside a pub package to refer to itself using
pub://myname/path/file.dartin imports/exports/parts, but not to any other package. And we can try to canonicalize the URIs of entry points or other externally referenced libraries to such URIs when compiling inside a package config where they have such a Pub-package URI. Then you can treat the package root as the path root for imports.So only to a very limited degree (self-package only). But then we should prefer and enforce that URI format, so you can depend on it.
Nit: Please use
package:my_package/src/foo.dart#class::Foo::method::bar.The part before the fragment identifies the file, the fragment part is not part of the path or file name, and has a meaning internal to the file.
(I don't care what the format is after the
#, it can also use/and#if it wants to,...foo.dart#class/Foo#method/bar, just that it's separated from what is actually a path.)jakemac53 commentedon Jan 30, 2026
We already have a name for the place we want to resolve to, it is right in the package config, the "rootUri". Given that, I would like to propose the URI scheme
package-root. That is what this is pointing to, the true "root" of the package (which may or may not also be the package URI root).I do like the idea of using the authority for the package name, so something like
package-root://foo/lib/foo.dart.This is not a pub specific concept at all, it is baked right into the package config, so I do not think
pubshould be anywhere in the name. Internally we use the same package layout, because it just makes sense, even though there are no pubspecs for internal Dart packages. But it is irrelevant as this is a package config concept now not a pub concept.Separately, I am interested in being able to distinguish between two identical URIs which actually resolve to different places, because multiple package config files exist within one context. This is a common occurrence for IDE environments and also command line AI environments, or really any development environment at all outside of google3 or pub workspaces. This is relevant not only to this proposal but also
package:URIs. They are really only canonical when put in the context of a package config. So, I would like to use either the host or query portions of the URI to point at the package config which should be used to resolve a given URI.rrousselGit commentedon Jan 30, 2026
Adding to that, build_runner sends tools an Uri of the format
asset:package/path.dartAnd since imports are of the format
import 'uri', we could make a case forimport 'asset:package_name/bin/main.dart';jakemac53 commentedon Jan 30, 2026
I don't think we should use
asset:as a general scheme as I think it is a little too generic. I kind of regret choosing that name originally, I just chose it because build_runner calls all files "assets", but that is sort of build_runner specific.I do like that its short.
I do not think these URIs should be valid in imports, if you want people to be able to import something put it under
lib, that is what it's for. This would effectively make altering even your tests or examples a breaking change.rrousselGit commentedon Jan 30, 2026
It'd be like lib vs lib/src
You could import it, but it'd be considered private
jakemac53 commentedon Jan 30, 2026
I really don't see a need for importing these files, and I do not want anybody to be able to import these from my packages, or tbh even to be able to try and reference them at runtime at all.
These URIs in general should only be a development time internal detail for certain tools, so they can have a canonical way to speak about files that are outside of
lib. Similar tomulti-root-scheme(and actually, could be a replacement for that potentially).Note that build_runner even does not allow access to these files in general, unless the package opts in to exposing them.
dcharkes commentedon Jan 30, 2026
+1 I don't think we should use the
asset:scheme, it's very confusing in the context of Flutter where we have assets in the pubspec.+1 I want a canonical way to refer to things, and for that way to be compatible across tools.
We look to the language team for standardization 😉 🤷♂️
For reasons such as this! 👇 🙏
@jakemac53, but not surfaced in this thread yet:
I love the addition of
@version. To make it compliant with the URI spec, we should put version before the@and use the "authority":package-root://<version>@<package-name>/<package-root-relative-path>.Correct. @jonasfj @sigurdm and I have been toying with
dart run other_pkg@<version>:binexe. (Which does not follow URI conventions either.<version>in such command can be arbitrary JSON...)lrhn commentedon Jan 30, 2026
Putting the "version" into the package reference does make it Pub-specific, and may warrant a
pubin the scheme name.(Using
package_name@versionis tricky, because if parsed by the standard URI rules, that makes the package_name the user-info, and the version the host. Sadly, can't use//package_name:version/, because the "port" after:can only be a single number. Swapping them, as suggested, would be better, but only if the version is simple enough to fit into the authority to begin with.)Everything else has only referred to package info available in a
package_config.json, which means one version of each package.Putting anything into the authority part of a
package:URI is a bad idea. The format has no/at the front of the path, but a URI with an authority must start a non-empty path with/. So that's a no-go.If we want to refer to the package-config file in the URI, a query is probably better:
package:foo/path/to/file.dart?config=file:///absolute/path/package_config.jsonThe query is part of the resource identifier, a fragment is not, so
package:foo/path/to/file.dart?config=file:///absolute/path/package_config.jsonpackage:foo/path/to/file.dart?config=file:///absolute/other/path/package_config.jsonrefers to two different resources, whereas
package:foo/path/to/file.dart#config=file:///absolute/path/package_config.jsonpackage:foo/path/to/file.dart#config=file:///absolute/other/path/package_config.jsonrefers to the same resource (and whoever reads that resource may know what the fragment identifier means in relation to the content).
In standard URI interpretation at least. Any scheme can define what everything after it means, like
data:andemail:do, but you won't get any help from theUriclass if you don't follow the standard rules.So use a query for that. It's also not easy to cram a full URI into the authority section of a URI, even if we wanted to. You'll have to escape everything, making it much harder to read.
And a query will also work the same for a
package-root:URI. So, that could work:package:package_name/path/to/file.dart?config=file:///absolute/path/package_config.jsonpackage-root://package_name/test/some_test.dart?config=file:///absolute/path/package_config.jsonIf no specific config is given, it should occur in a context where a config is given. (And you can't give one in Dart imports, or where a config is otherwise implied!)
We could use
package:for both, and say that if there is an authority, that's the package name and the path is root-relative, and if not, the first part segment is the package name and the rest is alib/-relative path.I just think it will confuse existing code that works with
package:URIs, which there is far too much of. (Nobody should parse Package URIs themselves, and dovar packageName = uri.pathSegments.first;. There should be abstractions for that. Alas, a little too late.)So a new scheme is safer.
If we want to specify a version, then
package-root://pkgname/path?version=1.2.25could work. It doesn't work together with aconfig=, and whoever interprets the URI needs to understand what the query means.lrhn commentedon Feb 1, 2026
If we want this, it could be integrated into
package:package_config. That package already has the package config loading and resolution built in.To support a
?config=...parameter, it probably has to provide a "context" object where it can cache package configurations, so it doesn't have to load a package-config file every time it resolves such a URI. And it should be a user-controlled context that can be cleared if a config file changes, which isn't something the package wants to be detecting itself. (So probably whoever creates the context can provide a callback to be notified every time a new configuration is added, and be given a way to invalidate a cached configuration.)No
versionparameter, that's outside the scope of package configurations.dcharkes commentedon Feb 2, 2026
I like this distinction. Using a different scheme for whether pub is needed or not.
package-root://requires a package config, and doesn't support asking for a different version. And,pub://could support versions.It seems we are converging to
package-rootas scheme, I'll update the proposal title.[-]Introduce `package-file:` or `package-private:` canonical (library) uris[/-][+]Introduce `package-root://` canonical uris[/+]jakemac53 commentedon Feb 2, 2026
The
?config=<path>query param is anyways more accurate, even in the pub world you actually have a mixture of pub dependencies and other dependencies, so the version alone isn't actually enough. If you instead say to resolve a URI with a specific package config, that is unambiguous.The only thing I don't love about it is in my case I plan to expose these URIs to AI agents as resources, and in that world shorter URIs are better - they are both surfaced to users sometimes and also consume tokens.
But, I think it makes sense, and it can in any case be optional so we can choose to use it or not.
[record_use] Rework `Definition`s (#3075)
[record_use] Rework `Definition`s (dart-lang#3075)
[record_use] Rework `Definition`s (dart-lang#3075)
Squashed commit of the following:
lrhn commentedon May 26, 2026
Just came by this thread again. A
config=...could be allowed to omit the.dart_tool/package_config.json, which isn't much, but it is some. Pointing to a directory would trigger the "find config for directory" logic, which would find.dart_tool/package_config.jsonas the first thing. (Or check the parent directory, but if brevity is the goal, might as well have the parent directory directly in the URL.)jakemac53 commentedon May 26, 2026
Note that for the MCP server I ended up using
package-root:(without the//). This was just to retain consistency withpackage:for people and agents.