@@ -16,7 +16,7 @@ use petgraph::{
1616use serde:: { Deserialize , Serialize } ;
1717use std:: {
1818 collections:: { hash_map, BTreeSet , HashMap , HashSet } ,
19- fmt,
19+ fmt, fs ,
2020 hash:: { Hash , Hasher } ,
2121 path:: { Path , PathBuf } ,
2222 str:: FromStr ,
@@ -235,6 +235,68 @@ impl BuildPlan {
235235 } )
236236 }
237237
238+ /// Create a new build plan taking into account the state of both the Manifest and the existing lock file if there is one.
239+ ///
240+ /// This will first attempt to load a build plan from the lock file and validate the resulting graph using the current state of the Manifest.
241+ ///
242+ /// This includes checking if the [dependencies] or [patch] tables have changed and checking the validity of the local path dependencies.
243+ /// If any changes are detected, the graph is updated and any new packages that require fetching are fetched.
244+ ///
245+ /// The resulting build plan should always be in a valid state that is ready for building or checking.
246+ pub fn load_from_manifest (
247+ manifest : & ManifestFile ,
248+ locked : bool ,
249+ offline : bool ,
250+ sway_git_tag : & str ,
251+ ) -> Result < Self > {
252+ let lock_path = forc_util:: lock_path ( manifest. dir ( ) ) ;
253+ let plan_result = BuildPlan :: from_lock_file ( & lock_path, sway_git_tag) ;
254+
255+ // Retrieve the old lock file state so we can produce a diff.
256+ let old_lock = plan_result
257+ . as_ref ( )
258+ . ok ( )
259+ . map ( |plan| Lock :: from_graph ( plan. graph ( ) ) )
260+ . unwrap_or_default ( ) ;
261+
262+ // Check if there are any errors coming from the BuildPlan generation from the lock file
263+ // If there are errors we will need to create the BuildPlan from scratch, i.e fetch & pin everything
264+ let mut new_lock_cause = None ;
265+ let mut plan = plan_result. or_else ( |e| -> Result < BuildPlan > {
266+ if locked {
267+ bail ! (
268+ "The lock file {} needs to be updated but --locked was passed to prevent this." ,
269+ lock_path. to_string_lossy( )
270+ ) ;
271+ }
272+ new_lock_cause = if e. to_string ( ) . contains ( "No such file or directory" ) {
273+ Some ( anyhow ! ( "lock file did not exist" ) )
274+ } else {
275+ Some ( e)
276+ } ;
277+ let plan = BuildPlan :: new ( manifest, sway_git_tag, offline) ?;
278+ Ok ( plan)
279+ } ) ?;
280+
281+ // If there are no issues with the BuildPlan generated from the lock file
282+ // Check and apply the diff.
283+ if new_lock_cause. is_none ( ) {
284+ let diff = plan. validate ( manifest, sway_git_tag) ?;
285+ if !diff. added . is_empty ( ) || !diff. removed . is_empty ( ) {
286+ new_lock_cause = Some ( anyhow ! ( "lock file did not match manifest `diff`" ) ) ;
287+ plan = plan. apply_pkg_diff ( diff, sway_git_tag, offline) ?;
288+ }
289+ }
290+
291+ if let Some ( cause) = new_lock_cause {
292+ info ! ( " Creating a new `Forc.lock` file. (Cause: {})" , cause) ;
293+ create_new_lock ( & plan, & old_lock, manifest, & lock_path) ?;
294+ info ! ( " Created new lock file at {}" , lock_path. display( ) ) ;
295+ }
296+
297+ Ok ( plan)
298+ }
299+
238300 /// Create a new build plan from an existing one. Needs the difference with the existing plan with the lock.
239301 pub fn apply_pkg_diff (
240302 & self ,
@@ -1316,6 +1378,19 @@ pub fn dependency_namespace(
13161378 namespace
13171379}
13181380
1381+ /// Compiles the package to an AST.
1382+ pub fn compile_ast (
1383+ manifest : & ManifestFile ,
1384+ build_config : & BuildConfig ,
1385+ namespace : namespace:: Module ,
1386+ ) -> Result < CompileAstResult > {
1387+ let source = manifest. entry_string ( ) ?;
1388+ let sway_build_config =
1389+ sway_build_config ( manifest. dir ( ) , & manifest. entry_path ( ) , build_config) ?;
1390+ let ast_res = sway_core:: compile_to_ast ( source, namespace, Some ( & sway_build_config) ) ;
1391+ Ok ( ast_res)
1392+ }
1393+
13191394/// Compiles the given package.
13201395///
13211396/// ## Program Types
@@ -1342,12 +1417,11 @@ pub fn compile(
13421417 source_map : & mut SourceMap ,
13431418) -> Result < ( Compiled , Option < namespace:: Root > ) > {
13441419 let entry_path = manifest. entry_path ( ) ;
1345- let source = manifest. entry_string ( ) ?;
13461420 let sway_build_config = sway_build_config ( manifest. dir ( ) , & entry_path, build_config) ?;
13471421 let silent_mode = build_config. silent ;
13481422
13491423 // First, compile to an AST. We'll update the namespace and check for JSON ABI output.
1350- let ast_res = sway_core :: compile_to_ast ( source , namespace , Some ( & sway_build_config ) ) ;
1424+ let ast_res = compile_ast ( manifest , build_config , namespace ) ? ;
13511425 match & ast_res {
13521426 CompileAstResult :: Failure { warnings, errors } => {
13531427 print_on_failure ( silent_mode, warnings, errors) ;
@@ -1444,6 +1518,43 @@ pub fn build(
14441518 Ok ( ( compiled, source_map) )
14451519}
14461520
1521+ /// Compile the entire forc package and return a CompileAstResult.
1522+ pub fn check (
1523+ plan : & BuildPlan ,
1524+ silent_mode : bool ,
1525+ sway_git_tag : & str ,
1526+ ) -> anyhow:: Result < CompileAstResult > {
1527+ let conf = & BuildConfig {
1528+ print_ir : false ,
1529+ print_finalized_asm : false ,
1530+ print_intermediate_asm : false ,
1531+ silent : silent_mode,
1532+ } ;
1533+
1534+ let mut namespace_map = Default :: default ( ) ;
1535+ let mut source_map = SourceMap :: new ( ) ;
1536+ for ( i, & node) in plan. compilation_order . iter ( ) . enumerate ( ) {
1537+ let dep_namespace =
1538+ dependency_namespace ( & namespace_map, & plan. graph , & plan. compilation_order , node) ;
1539+ let pkg = & plan. graph [ node] ;
1540+ let path = & plan. path_map [ & pkg. id ( ) ] ;
1541+ let manifest = ManifestFile :: from_dir ( path, sway_git_tag) ?;
1542+ let ast_res = compile_ast ( & manifest, conf, dep_namespace) ?;
1543+ if let CompileAstResult :: Success { typed_program, .. } = & ast_res {
1544+ if let TreeType :: Library { .. } = typed_program. kind . tree_type ( ) {
1545+ namespace_map. insert ( node, typed_program. root . namespace . clone ( ) ) ;
1546+ }
1547+ }
1548+ source_map. insert_dependency ( path. clone ( ) ) ;
1549+
1550+ // We only need to return the final CompileAstResult
1551+ if i == plan. compilation_order . len ( ) - 1 {
1552+ return Ok ( ast_res) ;
1553+ }
1554+ }
1555+ bail ! ( "unable to check sway program: build plan contains no packages" )
1556+ }
1557+
14471558/// Attempt to find a `Forc.toml` with the given project name within the given directory.
14481559///
14491560/// Returns the path to the package on success, or `None` in the case it could not be found.
@@ -1565,3 +1676,18 @@ pub fn fuel_core_not_running(node_url: &str) -> anyhow::Error {
15651676 let message = format ! ( "could not get a response from node at the URL {}. Start a node with `fuel-core`. See https://github.com/FuelLabs/fuel-core#running for more information" , node_url) ;
15661677 Error :: msg ( message)
15671678}
1679+
1680+ fn create_new_lock (
1681+ plan : & BuildPlan ,
1682+ old_lock : & Lock ,
1683+ manifest : & ManifestFile ,
1684+ lock_path : & Path ,
1685+ ) -> Result < ( ) > {
1686+ let lock = Lock :: from_graph ( plan. graph ( ) ) ;
1687+ let diff = lock. diff ( old_lock) ;
1688+ super :: lock:: print_diff ( & manifest. project . name , & diff) ;
1689+ let string = toml:: ser:: to_string_pretty ( & lock)
1690+ . map_err ( |e| anyhow ! ( "failed to serialize lock file: {}" , e) ) ?;
1691+ fs:: write ( & lock_path, & string) . map_err ( |e| anyhow ! ( "failed to write lock file: {}" , e) ) ?;
1692+ Ok ( ( ) )
1693+ }
0 commit comments