Module ethers_solc::project

source ·
Expand description

Manages compiling of a Project

The compilation of a project is performed in several steps.

First the project’s dependency graph crate::Graph is constructed and all imported dependencies are resolved. The graph holds all the relationships between the files and their versions. From there the appropriate version set is derived crate::Graph which need to be compiled with different crate::Solc versions.

At this point we check if we need to compile a source file or whether we can reuse an existing Artifact. We don’t to compile if: - caching is enabled - the file is not dirty - the artifact for that file exists

This concludes the preprocessing, and we now have either

  • only Source files that need to be compiled
  • only cached Artifacts, compilation can be skipped. This is considered an unchanged, cached project
  • Mix of both Source and Artifacts, only the Source files need to be compiled, the Artifacts can be reused.

The final step is invoking Solc via the standard JSON format.

Notes on Import Path Resolution

In order to be able to support reproducible builds on all platforms, the Solidity compiler has to abstract away the details of the filesystem where source files are stored. Paths used in imports must work the same way everywhere while the command-line interface must be able to work with platform-specific paths to provide good user experience. This section aims to explain in detail how Solidity reconciles these requirements.

The compiler maintains an internal database (virtual filesystem or VFS for short) where each source unit is assigned a unique source unit name which is an opaque and unstructured identifier. When you use the import statement, you specify an import path that references a source unit name. If the compiler does not find any source unit name matching the import path in the VFS, it invokes the callback, which is responsible for obtaining the source code to be placed under that name.

This becomes relevant when dealing with resolved imports

Relative Imports
import "./math/math.sol";
import "contracts/tokens/token.sol";

In the above ./math/math.sol and contracts/tokens/token.sol are import paths while the source unit names they translate to are contracts/math/math.sol and contracts/tokens/token.sol respectively.

Direct Imports

An import that does not start with ./ or ../ is a direct import.

import "/project/lib/util.sol";         // source unit name: /project/lib/util.sol
import "lib/util.sol";                  // source unit name: lib/util.sol
import "@openzeppelin/address.sol";     // source unit name: @openzeppelin/address.sol
import "https://example.com/token.sol"; // source unit name: <https://example.com/token.sol>

After applying any import remappings the import path simply becomes the source unit name.

Import Remapping
import "github.com/ethereum/dapp-bin/library/math.sol"; // source unit name: dapp-bin/library/math.sol

If compiled with solc github.com/ethereum/dapp-bin/=dapp-bin/ the compiler will look for the file in the VFS under dapp-bin/library/math.sol. If the file is not available there, the source unit name will be passed to the Host Filesystem Loader, which will then look in /project/dapp-bin/library/iterable_mapping.sol

Caching and Change detection

If caching is enabled in the Project a cache file will be created upon a successful solc build. The cache file stores metadata for all the files that were provided to solc. For every file the cache file contains a dedicated cache entry, which represents the state of the file. A solidity file can contain several contracts, for every contract a separate artifact is emitted. Therefor the entry also tracks all artifacts emitted by a file. A solidity file can also be compiled with several solc versions.

For example in A(<=0.8.10) imports C(>0.4.0) and B(0.8.11) imports C(>0.4.0), both A and B import C but there’s no solc version that’s compatible with A and B, in which case two sets are compiled: [A, C] and [B, C]. This is reflected in the cache entry which tracks the file’s artifacts by version.

The cache makes it possible to detect changes during recompilation, so that only the changed, dirty, files need to be passed to solc. A file will be considered as dirty if:

  • the file is new, not included in the existing cache
  • the file was modified since the last compiler run, detected by comparing content hashes
  • any of the imported files is dirty
  • the file’s artifacts don’t exist, were deleted.

Recompiling a project with cache enabled detects all files that meet these criteria and provides solc with only these dirty files instead of the entire source set.

Structs