external-crates/move/crates/move-package-alt/DESIGN.md
In this proposal, the Move.lock file contains all the information about the published versions of
packages. It is updated when packages are published or upgraded. It should contain enough
information to consistently rebuild a package.
This document is the current version of the design in notion; See also overview for a higher level motivation and outline, and user stories for a walkthrough of usage scenarios.
Move.toml:
[package]
name = "example"
edition = "2024" # we do not bump the edition just for package system changes
...
[environments]
# the [environments] section contains entries mapping names to chain IDs
# mainnet = "35834a8a"
# testnet = "4c78adac"
# but these two environments (mainnet and testnet) are added implicitly, so in
# most cases the `[environments]` section is not needed
#
# one potential use for additional environments is if you want to maintain
# multiple deployments on the same network; you could then use
# [dep-replacements] to have different dependencies for the different deployments
testnet_alpha = "4c78adac"
testnet_beta = "4c78adac"
[dependencies]
foo_1 = {
rename-from = "foo", # needed for name consistency - see Validation section
override = true, # same as today - see Linking
git = "https://.../foo1.git", # resolver specific fields
rev = "releases/v4",
}
foo_2 = {
rename-from = "foo", # needed for name consistency - see Validation section
git = "https://.../foo2.git", # resolver specific fields
rev = "releases/v1",
}
bar = { r.mvr = "@protocol/bar" }
# dependencies can contain a list of modes; if present then the dependencies will be removed when
# compiling for any mode not listed. For example, the following is a test-only dependency:
baz = { ..., modes = ["test"] }
[dep-replacements]
# used to replace dependencies for specific environments
mainnet.foo = {
git = "...", # override the source of the dep
original-id = "....", # add an explicit address; see Backwards Compatibility
published-at = "...",
use-environment = "mainnet_alpha" # override/specify the dep's environment
}
Move.lock contains information about pinned dependencies for each environment:
[move]
version = 4
# The `pinned` section contains a dependency graph for each environment. Each node in
# the graph contains the pinned dependency location, a digest of the manifest of the dependency (to
# detect local changes), and a set of outgoing edges. The edges are labeled by the name of the dependency.
#
# The identities of the nodes are arbitrary, but it seems nice to generate them from the package
# names that dependencies declare for themselves (adding numbers to disambiguate if there are
# collisions).
#
# There is also a node for the current package; the only thing that makes it special is that it has
# the name of the current package as its identity (it will also always have `{ root = true }` as its
# source)
[pinned.mainnet.example]
source = { root = true, use-environment = "mainnet" }
manifest_digest = "..."
deps.std = "MoveStdlib"
deps.sui = "Sui"
deps.foo = "Foo_0" # ensure rename-from is respected
deps.non = "Foo_1" # ensure rename-from is respected
deps.bar = "bar"
[pinned.mainnet.MoveStdlib]
source = { git = "...", path = "...", rev = "1234" }
manifest_digest = "..."
deps = {}
[pinned.mainnet.Sui]
source = { git = "...", path = "...", rev = "1234" }
manifest_digest = "..."
deps.std = "MoveStdlib"
[pinned.mainnet.Foo_0]
source = { git = "...", path = "...", rev = "bade", use-environment = "mainnet_alpha" }
manifest_digest = "..."
deps.std = "MoveStdlib"
deps.sui = "Sui"
[pinned.mainnet.Foo_1]
source = { git = "...", path = "...", rev = "baaa" }
manifest_digest = "..."
deps.std = "MoveStdlib"
deps.sui = "Sui"
[pinned.mainnet.bar]
source = { git = "...", path = "...", rev = "bara" }
manifest_digest = "..."
deps.baz = "baz"
deps.std = "MoveStdlib"
deps.sui = "Sui"
[pinned.mainnet.baz]
source = { git = "...", path = "...", rev = "baza", modes = ["test"] }
manifest_digest = "..."
deps.std = "MoveStdlib"
deps.sui = "Sui"
[pinned.mainnet.[...]] # other transitive dependencies from example
# the same for testnet environment as above
[pinned.testnet.MoveStdlib]
source = { git = "...", path = "...", rev = "1234" }
manifest_digest = "..."
deps = {}
[pinned.testnet.Sui]
source = { git = "...", path = "...", rev = "1234" }
manifest_digest = "..."
deps.std = "MoveStdlib"
# the same for other defined environments
[pinned.env.Sui]
source = { git = "...", path = "...", rev = "1234" }
manifest_digest = "..."
deps.std = "MoveStdlib"
deps.sui = "Sui"
Published.toml contains information about historical publications in each environment:
# This file should be checked in
[published.mainnet] # metadata from most recent publish to mainnet
# generic move fields:
chain-id = "35834a8a" # used to ensure the chain ID is consistent
published-at = "..."
original-id = "..."
version = "3"
# other useful chain-specific stuff:
upgrade-cap = "..."
build-config = { ... }
toolchain-version = "..."
[published.testnet] # metadata from most recent publish to testnet
chain-id = "4c78adac"
published-at = "..."
original-id = "..."
version = "5"
upgrade-cap = "..."
toolchain-version = "..."
build-config = "..."
See src/schema for the schemata implementations.
Move.toml
package
name : PackageName
edition : "2025"
version : Optional String
license : Optional String
authors : Optional Array of String
system_dependencies : Optional Array of String (default None)
environments : EnvironmentName → ChainID
dependencies : PackageName → DefaultDependency
dep-replacements : EnvironmentName → PackageName → ReplacementDependency
DefaultDependency: # information used to locate a dependency
# dep-type specific fields e.g. git = "...", rev = "..."
override: bool
rename-from: PackageName
modes: Optional Array of String
ReplacementDependency:
optionally any DefaultDependency fields
optionally both AddressInfo fields # see backwards compatibility
optionally
use-environment : Optional EnvironmentName
Move.lock
move
version : 4
pinned : EnvironmentName -> PackageID →
source : PinnedDependency
manifest_digest : Digest
deps : PackageName → PackageID
PinnedDependency:
ReplacementDependency with additional constraints - see Pinning
Move.published
published : EnvironmentName →
chain-id: EnvironmentID
published-at: Object ID
original-id: Object ID
version: uint
# sui specific
upgrade-cap: Optional Object ID
toolchain-verison: String
build-config: table
Published.toml
published : EnvironmentName →
chain-id: EnvironmentID
published-at: Object ID
original-id: Object ID
version: uint
# sui specific
upgrade-cap: Optional Object ID
toolchain-verison: String
build-config: table
AddressInfo:
published-at : ObjectID
original-id : ObjectID
All operations are performed in the context of a specific environment. From the package management
perspective, the environments must be declared in the manifest (although we provide default
environments for mainnet and testnet). The CLI may support other environments - for example
publishing to localnet must be supported, but in this case the user must supply --build-env to
indicate which manifest environment to use.
From here on, when we say "all dependencies", we are referring to dependencies in the current environment.
From the CLI perspective, we have the problem of selecting an environment to use if the user doesn't
provide one. By default we will use the current chain ID from sui client (which we will keep
cached to support offline builds) to detect the correct environment from the manifest to use. By
using the chain ID we decouple the client environment names (which are really RPC specific) from the
manifest environment names, but we make things work out in the common cases of mainnet and testnet.
In our current MVP we do not do any all-environment operations. However, we could consider doing some cross-environment sanity checks (such as detecting packages that conflict in one environment but not another, or running tests in all environments.
The purpose of the lockfile is to give stability to developers by pinning consistent (transitive) dependency versions. These pinned versions are then used by other developers working on the same project, by CI, by source verifiers, and so forth.
A pinned dependency is one that guarantees that future lookups of the same dependency will yield the same source package. For example, a git dependency with a branch revision is not pinned, because the branch may be moved to a different commit, which might have different source code. To pin this kind of dependency, we must replace the branch name with a commit hash - downloading the same commit should always produce the same source code.
Local dependencies of git dependencies are pinned as git dependencies with the paths fixed up, but local dependencies of the root package are pinned as they are.
Although we expect pinned dependencies to not change, there are a few situations where they could. One possiblity is local dependencies - if a single repository contains both a dependency and the depending project, we would want changes in the dependency to be reflected immediately in the depending project. Moreover, this doesn't cause inconsistencies in CI because presumably those changes would be committed together. Another example would be if a user wants to temporarily change a cached dependency during debugging to rerun a test or something. To handle these situations, we will store digests of all transitive dependency manifests and repin if any of them change.
Dependencies are always pinned as a group and are only repinned in two situations:
The user explicitly asks for it by running sui move update-deps. This command will repin all
dependencies for the current environment.
If the parts of the manifest that are relevant for the current environment have changed, then all dependencies are repinned.
Note: we had considered only repinning the dependencies that had changed and allowing the user to repin only specific deps, but this leads to a lot of confusing corner cases. This is consistent with the philosophy is that pinning is a mechanism for reliable rebuilding and not for dependency version management - if a user cares about the exact version of a dependency then they can indicate that by referring to that version in their manifest.
In addition to the main set of dependencies, the user can also override dependencies in specific
environments using a [dep-replacements] section. These have a slightly different set of
information - for example they can include the on-chain addresses for packages (although this should
only be used in the case of a legacy package whose address isn't recorded in its lockfile).
In the dependency graph, we store a dependency graph for each environment. See the Move.lock example above.
In the new system, each environment potentially represents an entirely different dependency graph, so the environment that we choose matters a great deal, and we must always be operating in the context of an environment. However, we'd like the environment selection process to be as unintrusive as possible (while still doing the "right thing").
The first principle of environment selection is that all commands (build, test, publish, etc) should select environments in the same way.
The second principle is that we should only allow publishing to a chain with a chain ID that matches the chain that the dependencies were published for and that the root package was built for (although see below for localnet publication).
Since the established publication workflow publishes publishes to the chain identified by the active CLI environment, we use the active environment to select the environment to build for. However, we need to be careful because while the manifest environment names are global and have associated chain IDs (in the sense that they are contained in manifest files and used by dependents), CLI environments are local to a machine and may encapsulate things like different RPC endpoints as well as the actual underlying chain.
Putting these constraints together leads to a somewhat complicated process for selecting the build environment. Suppose that the active CLI environment is named "e" and that the chain ID for the associated RPC is "i"1 (and the user hasn't provided a command-line argument to override):
If the exact pair ("e", "i") is contained in the [environments] table of the manifest, we use
that as the build environment
If an environment named "e" exists in the manifest but the associated chain ID "i'" doesn't match, we produce an error (perhaps this could be a warning during build/test and an error on publish). This can happen if the user messes up their manifest or local environment, or if the network has been wiped and restarted
Error: Environment
ehas chain IDiin your local environment, butMove.tomlexpectseto have chain IDi'; this may indicate thatehas been wiped or that you have a misconfigured environment.
If no environment named "e" exists in the manifest, we use the chain ID to try to select the
correct environment. This situation might happen e.g. if the user decides to call their local
environment main instead of mainnet, or if they have testnet_1, testnet_2, etc. If there
is a unique environment named e' with chain ID i, we choose that environment and emit an
info message.
Note:
Move.tomldoes not define aneenvironment; building fore'instead
If there are multiple environments e1, e2 in the manifest having chain ID i, we produce an
error, requiring the user to specify the build env. This may happen if there are testnet_alpha
and testnet_beta environments in the manifest and the user's active CLI environment is
testnet.
Error: There is no
eenvironment in the manifest, but environmentse1ande2are available. Runsui move build --build-env e1orsui move build --build-env e2
If there are no environments in the manifest with chain ID i, then we assume i is an
ephemeral network (e.g. localnet or devnet). If Pub.e.toml exists, we will use the
build-env from that file (Note that if the chain-id in Pub.e.toml doesn't match and we are
publishing, we will get warning or error from the chain-id consistency checks).
If there is no Pub.e.toml file, we produce an error, depending on the command:
For build:
Error: Your active environment
eis not present inMove.toml, so you must specify the environment to use to determine dependencies. Pass--build-env <env-name>, e.g.sui move build --build-env testnet
Note: adding local networks to
Move.tomlis discouraged; seesui client test-publish --helpfor information on managing local networks.
TODO: this message might be confusing since we have implicit environments
For publish:
Error: Your active environment
eis not present inMove.toml, so you cannot publish toe.
If you want to create a temporary publication on
eand record the addresses in a local file, use thetest-publishcommand insteadsui client test-publish --help
If you want to publish to
eand record the addresses in the sharedPublished.tomlfile, you will need to add the following toMove.toml:[environments] e = "i"
We had considered using an environments like feature to support things like test-only or spec-only
dependencies (like the [dev-dependencies] section in rust or the old system). However, this leads
to a lot of complexity around integrating environments and modes.
Instead, we add an optional modes field to each dependency which contains a list of the modes to
enable the dependency for (if missing, we include the dep for all modes).
The set of allowed modes should be determined the same way they are for the Move compiler.
The advantage of this approach is that in each environment, each dependency has a well-defined set of modes, and the dependency graph for a particular mode is always a subgraph of the dependency graph for environment ignoring modes. Therefore we don't have to worry about conflicts between environment overrides and mode overrides.
Mode filtering happens after package graph generation but before compilation. Since linkage happens after compilation, that means we won't introduce any artificial dependency conflicts by treating all modes simultaneously for the earlier stages. Keeping all modes through pinning means that we can pin once-and-for-all for each environment. It also means that we fetch test-only deps even when not building for test, which is good because it means we support offline test builds even if you've only done a non-test build before (or vice-versa).
check for repin
recursively repin everything
use-environment herecheck for conflicts
ensure the graph is not cyclic
if a node doesn't have a published id for the current environment, warn: you won't be able to publish
if two nodes have same original id, there may be a version conflict (but we can't know for sure until after compilation - see linkage below); warn
rewrite the dependency graph to the lock file
To the best of our ability, we establish the following invariants while constructing the package graph:
for each environment, there is a node in the [pinned.<env>] section having an ID that matches
the package name and a source field containing exactly { root = true }; we call this the
"root node"
the dependency graph in pinned is a DAG rooted at the root node.
every entry in pinned has a different (source
For each node p in the pinned section of Move.lock:
p.manifest_digest is the digest of Move.toml for the corresponding packagep.deps contains the same keys as the dependencies of Move.tomlp.source has been cached locallyall environments in Published.toml or Move.lock are in Move.toml
These can also be violated if a user mucks around with their lockfiles - I think we should just do
best-effort on that. We may provide an additional tool to help fix things up (e.g. sui move sync-lock or something).
We maintain the distinction between “internal” and “external” resolvers, but the job of an external resolver is much simpler than the current design - it is simply responsible for converting its own dependencies into internal dependencies (currently git, local, or on-chain).
External resolvers may want to return different information for each network (e.g if there are different versions published on mainnet or testnet). They may also want to batch their lookups. To support this and possible future extensions, we use JSON rpc (over stdio) for communication between the package management system and external resolver binaries.
To be concrete, if there are any dependencies of the form {r.<res> = <data>} we will invoke the
binary <res> from the path with the --resolve-deps command line argument. We will pass it a JSON
RPC batch request on standard in and read a batch response from its standard out. The batch request
will contain one call to the resolve method for each environment and for each dependency, passing
the environment and the <data> as arguments. The method call responses will then be decoded as
internal dependencies.
Currently the only external resolver is mvr, and it will just look up the mvr name and convert it into a git dependency (there is also a mock-resolver that echoes its input that is used for testing).
Unlike the current system, explicitly including a system dependency is an error; you disable
system dependencies by adding system_dependencies = [] to the [package] section of your
manifest. Each flavor can specify its default system dependencies (for Sui, that's sui and std).
Non-default system dependencies can be specified like that: system_dependencies = ["sui", "std", "sui_system"]
Like externally resolved dependencies, system dependencies will be pinned to different versions for each environment.
TODO: maybe this isn't necessary; we can just disable local deps in the monorepo and put them in explicitly:
The default system deps for Sui would be
suiandstd. The available system deps arestd,sui,system,deepbook-v2,bridge,monorepo-sui,monorepo-std. Themonorepodeps are converted to local dependencies are are used for our internal tests (they would expand tosui = { local = "path_to_monorepo/crates/sui-framework/packages/sui" }and would fail if they are used outside the monorepo.
Once dependencies have been pinned they should be fetched to the local cache in .move. Since
dependencies are pinned, we don’t need to keep around a git repository for the cache - the cache is
simply a snapshot of the files.
In particular, we use sparse shallow checkouts to make downloading fast. This requires care when multiple projects live in the same repo, but there is no fundamental problem.
Since the cached files will never change, it is safe to use them across projects. In particular, I think we don’t need to have a copy of dependency sources within a project’s build artifacts. However, we may want to mark the files as read-only in this case, since the LSP may end up navigating to them during debugging and accidental changes could be bad.
Of course, the users may choose to update the cache anyway (for example if they want to locally test a change). We should only allow building with a dirty cache if the user gives a special command-line argument.
Note that since we always fetch after pinning, we never need to go to the network unless a dependency needs to be repinned (because the manifest changed or the user ran update-deps).
(not part of MVP)
Bytecode dependencies are handled entirely during the fetching process. When fetching a bytecode dependency, we immediately convert it into a source dependency by generating stubs, a manifest, and lockfiles.
Care must be taken to properly generate dependencies. In particular, we should use the bytecode to determine the direct dependencies rather than relying on the on-chain linkage table. See Linkage below.
Once this process is complete, bytecode deps are identical to any other dependency.
The only other place where bytecode deps are relevant is during testing - we need to make sure that we run the stored bytecode rather than the compiled stubs. However, it is probably best to always use the cached build artifacts when testing.
Finally, we may want to do some validation and sanity checks after downloading. For example, mvr
would check the published-at fields of the dependencies' lock files match the registered versions.
Additionally, we perform other checks. We can abstract this as a "validation" step, where we run all
validators; mvr could supply an external validator.
For example, we can perform the following checks:
do the (transitive) dependencies have published versions on all of the defined environments?
Warning: your package depends on foo but foo's Move.lock does not indicate that it is published on mainnet.
If the package is published, you should ask the author to add the address to the
Published.tomlfile, but you can work around this by adding the address in yourMove.tomlin the [dep-replacements] section:[dep-replacements] mainnet.foo = { published-at = "0x....", original-id = "0x..." }
here published-at should refer to the current version of the package and original-id should refer to the first version of the package.
are the chain IDs for the environments consistent?
Warning: your package depends on foo but foo’s devnet chain ID (defined in foo/Move.toml) is different from your chain ID (in Move.toml). You may need to change the chain ID for devnet in your Move.toml or update foo using
sui move update-deps foo
If the dep-replacements do specify a published-at / original-id, does it match the one in the package?
Warning: your package specifies a published-at address for foo on mainnet, but package foo is published at a different address according to its Published.toml file. Consider removing the published-at field for foo from your Move.toml file
If two dependencies in the graph have the same original ID but different published addresses, then there is a conflict - they are referring to different versions of the same package.
Error: packages foo and bar depend on different versions of baz
You must choose a specific version of
bazby giving an override dependency:[dependencies] baz = { <info for latest of the conflicting versions>, override = "true" }
Update: we can only perform this step after compilation because whether an override is necessary or not depends on whether dependencies are direct or transitive and we can't know that (esp. for legacy packages) until after tree shaking. See Linkage
Are there local dependencies that don’t share a repository with the current package? This is almost certainly a bug!
Warning: you have declared a local dependency on “../../../other-package/” but “../../../other-package/” is not contained in your repository. This can lead to version inconsistencies during publication and is strongly discouraged.
Naming consistency: do names declared by dependencies match the names given to them by dependents? (Also check if package name is different from its dependencies’ names)
Error: the package referred to by
fooinMove.tomlis actually namedbar. If this is intentional, add arename-fromfield to the dependency line inMove.toml:foo = { …, rename-from = “bar” }
In general, any property that would cause a publish to fail should probably be reported either at update or at build time.
Although compilation itself is outside the purview of the package system, the set of packages that we pass to the compiler depends on the package graph, and there are some important changes from the previous system.
In particular, the previous system precomputed the overrides before handing packages to the compiler, but this means that dependencies may be compiled inconsistently from how they were compiled on-chain. Source validation doesn't even help here unless we are super-careful, because the source code and compiled bytecode can match completely but the bytecode we generate for our internal operations can differ (e.g. in the presence of macros).
Therefore, the new system will compile each package against the pinned dependency version that it has. This means there may be multiple version of the same package in the package graph. We only detect and disambiguate conflicts during the linkage step, after compilation.
Another important difference is that in the old system packages inherit named addresses from their dependencies. In the new system, if you refer to a package by name in your source code, you must specify a direct dependency in your manifest. This makes all dependency names local, which makes it possible to integrate multiple packages with the same name.
We also perform mode filtering before handing packages to the compiler.
For the entire process through compilation, the package system can treat the package graph as a tree: there is no problem if multiple nodes represent "the same" package. However, before we can publish or run a package, we must select a single implementation for each package original-id. This is the process of linkage.
The rules for linkage and how they interact with overrides is the same as in the previous system,
although because we allow multiple packages with the same ids to appear in the compilation graph,
there are some corner cases that the old system gets a little bit wrong. In particular, the
definition of a valid linkage requires a distinction between direct and transitive dependencies, and
because named addresses are inherited in the old system it is possible to allow some linkages that
should require override = true in the manifests. There are also corner cases if a dependency is
declared but not used, but these only require people to write an additional unnecessary dep and so
aren't a large concern.
See the test module in [src/graph/linkage.rs] for a bunch of worked examples of linkage checking
(you can also render diagrams for the tests - see the comments there for instructions).
If the user runs sui move update-deps, we rerun resolution, pinning, fetching, and validation for
all dependencies. If they run sui move update-deps d1 d2 we rerun these steps only for the
specified dependencies.
Building and testing for a given environment is easy - once we reestablish the invariants, all the source packages for the pinned dependencies are available. We always compile against the source/bytecode we have.
When running tests, we compute the linkage first and then hand those packages to the VM for execution. This models what happens on-chain as closely as possible.
When running tests, we use the "test" mode.
During normal publication, we need to recheck that the dependencies are all published on the given network and that the relevant chain IDs are consistent. We also need to ensure that the additional on-chain linkage requirements are met (for example, on-chain packages can have extra dependencies in their linkage that they don't actually use, so we need to union in the on-chain linkages).
During upgrade we can use the stored upgrade cap.
We also need to update the Published.toml file to include the updated PublishedMetadata.
There are some footguns we want to prevent. For example, if a user has two different environments for the same chain ID, we should require them to specify a specific environment (this would prevent accidentally publishing an alpha version to the beta address, for example).
Some users will want to perform the actual publication step outside of the CLI (for example if they want to go through a separate signing process for the transaction). For these transactions, I don't think we know the published address until after the transaction executes. I think we should still update the lock file and write "TODO"s for the fields that we don't know. We might also provide a way for users to replace the TODOs their lock file given the transaction ID of a publication transaction.
[published.mainnet]
...
published-at = "TODO: replace this with the published address of your package"
...
The user can still screw this up, but they at least have the chance of noticing the problem and fixing it when they review the commit. We can also output a loud warning from the command line!
A common operation that we would like to support is to make an ephemeral publication of a package and its dependencies for testing (for example on localnet). To support this, we will add an ephemeral publish command that works like a normal publish except that the publication addresses for packages are read from and written to a separate file.
The format of an ephemeral publication file is as follows:
# generated by move
# this file contains metadata from ephemeral publications
# this file should not be committed to source control
build-env = "mainnet"
chain-id = "localnet chain ID"
[[published]]
source = { root = true }
published-at = "..."
original-id = "..."
upgrade-cap = "..."
[[published]]
source = { git = "...", rev = "...", path = "..." }
published-at = "0x000000000000000000000000000000000000000000000000000000000000cccc"
original-id = "0x000000000000000000000000000000000000000000000000000000000000cc00"
upgrade-cap = "0x000000000000000000000000000000000000000000000000000000000011cc00"
[[published]]
source = { local = "../foo" }
published-at = "0x0000000000000000000000000000000000000000000000000000000000001234"
original-id = "0x0000000000000000000000000000000000000000000000000000000000005678"
upgrade-cap = "0x000000000000000000000000000000000000000000000000000000000022cc00"
We will add a new command sui client test-publish <pubfile> which will publish build the root
package for one environment and then publish it to a chain using the addresses from the ephemeral
publication file.
sui client test-publish <pubfile> --build-env <env> will work as follows:
if <pubfile> is omitted, it defaults to Pub.<env-name>.toml where <env-name> is the name of
the active environment (irrespective of the environments defined in the manifest). This file
contains the addresses to use for publication
if --build-env <env> is omitted, it defaults to the build-env name of the file at <file>;
if that is missing then we error. The build environment determines how we resolve dependencies
for the package
Error: When creating a new test publication file, you must pass
--build-env <env>, for examplesui client test-publish <pubfile> --build-env testnet
if --build-env <env> is present and different from the build-env in the file, we
fail2.
Error: the addresses in
<pubfile>are for dependencies built with--build-env <pubfile-build-env>; they should not be used for publishing with--build-env <env>
if <file> exists and chain-id doesn't match, we don't need to explicitly fail2
in this stage, but the dependency addresses will be marked with chain-id, so we will get an
error from the pre-publication checks indicating that the chain id of the dependency is different
from the current chain ID
We then load the package graph from the lockfile for build-env (repinning if
necessary). We then publish the root package to the active environment using
the addresses from <file> instead of their published-at addresses (if an
entry is missing from the ephemeral file but the real chain ID matches the
ephemeral chain ID then we can use the real address instead). Finally, we
update the entry for the root package in <file>.
As a post-MVP feature, we can provide a version that publishes or upgrades dependencies as well, but this facility at least allows users (and internal tests) to build up dependencies iteratively without modifying their source.
Source verification is the process of checking that recompiling a given source package produces a given binary output.
Our goal for this MVP is not to reimplement source verification correctly, but rather to store enough information in the lock file that we (or someone) can reliably perform source verification in the future.
We envision an architecture where each version of the compiler defines a build config format and has a code generation module that, given a build config and source code (including dependency sources), will always produce the same compiled bytecode.
For a given PublishedMetadata, we can use the toolchain-version to determine which code
generation module to use; this code generation module can then use the stored pinned dependencies
and the build-config field to reliably reproduce the bytecode (which can then be compared to the
on-chain version).
When publishing or upgrading a package, there are certain facts about the dependencies that can be
important for the user to be conscious of. For example, an upgrade can change the version of a
dependency, while a publication can produce a linkage that forces a dependency to upgrade. Although
the developer has to "sign off" on these (e.g. by adding override = true to the manifest or
running upgrade-deps), we think it makes sense to display these kinds of facts to the user as part
of the publication process.
For backwards compatibility, we need to be able to mix packages that use the old-style lock and manifest format with the formats described here.
For migration, we need to be able to assist a user in upgrading their package to the new format.
In both cases, we need to be able to map an old-style package into a new-style package, but they are different because for compatibility we need something fully automated (but that only needs to work on a single chain), while for migration we can do a better job using user assistance.
More research is needed to describe the exact mapping, but the goal is that we unpack old-style lock files into the same data structure as new-style lock files.
We currently plan to remove support for the following features, and we don't mind if we break how they work in legacy packages:
foo = "_"--with-unpublished-addresses[dev-addresses]Another field that exists today is build. It particularly refers to an architecture or language
version. I believe we’re not using it in our Move style code, but we might have to consider it in
some way.
To provide consistency between legacy and modern packages, we are attempting to extract a new-style
package name for legacy packages. This means, for example, that the MoveStdlib package should
actually be called std (so that its name in source code matches its package name, as with modern
packages).
To do this, we have a handful of heuristics for pulling the name from the named address table, using the source code to disambiguate if necessary.
Although there's no facility for writing named addresses in the modern system, we're keeping around the named addresses from legacy packages and using them. This means we need to perform a pass to collect all of them from transitive dependencies. For legacy packages only, we include these in the named addresses we hand to the compiler.
If a legacy package depends on a modern package, it will not inherit addresses from that package.
When processing the [dev-dependencies] section of a legacy manifest, we produce normal dependencies
with mode = ["test"]. We should make a best effort to do the right thing if the same dependency
shows up in both [dependencies] and [dev-dependencies] --- I'm not sure what the current system
does in this case.
Note: @Manos Liolios promised me a particularly wild example or two, and a more comprehensive list of examples post-bootcamp.
We will not make any changes to Move. A future version of Move may add restrictions that all modules
in the package's source code are defined in that package (right now you can write module p::m for
any p). The new package system will make this more sensible, but we don't plan to make any changes
now.
TODO: what about forking? Is publishing against a forked network something we even care about? If so, does a forked network share a chain ID with the original? How do we distinguish packaged that are published pre-fork from those published post-fork? There's a lot of complexity here.
We want to support offline builds, which means we need to have the CLI locally cache the chain IDs for each environment. We will update that cache whenever we actually contact an endpoint and whenever we update the local set of environments (and of course we must make sure it is up-to-date before publishing) ↩
We have a post-mvp proposal to automatically publish dependencies instead of failing if they are missing; see "Recursive test-publish; test-upgrade" ↩ ↩2