diff --git a/.github/workflows/ci-toolchain.yml b/.github/workflows/ci-toolchain.yml index 6cbc9302c..7db742c4e 100644 --- a/.github/workflows/ci-toolchain.yml +++ b/.github/workflows/ci-toolchain.yml @@ -60,7 +60,7 @@ jobs: run: ./bin/alr -d -n printenv || ./bin/alr -n -v -d printenv - shell: bash - run: mv ./bin ./bin-old || { sleep 5s && sudo mv ./bin ./bin-old; } + run: mv ./bin ./bin-old || { sleep 5s && mv ./bin ./bin-old; } # Windows doesn't allow to replace a running exe so the next command # fails otherwise. Also, this mv fails sometimes so we try twice JIC. diff --git a/alire.toml b/alire.toml index d62d577d8..dda65c9a5 100644 --- a/alire.toml +++ b/alire.toml @@ -49,7 +49,7 @@ windows = { ALIRE_OS = "windows" } [[pins]] aaa = { url = "https://github.com/mosteo/aaa", commit = "dff61d2615cc6332fa6205267bae19b4d044b9da" } ada_toml = { url = "https://github.com/mosteo/ada-toml", commit = "da4e59c382ceb0de6733d571ecbab7ea4919b33d" } -clic = { url = "https://github.com/alire-project/clic", commit = "de0330053584bad4dbb3dbd5e1ba939c4e8c6b55" } +clic = { url = "https://github.com/alire-project/clic", commit = "56bbdc008e16996b6f76e443fd0165a240de1b13" } dirty_booleans = { url = "https://github.com/mosteo/dirty_booleans", branch = "alire" } diskflags = { url = "https://github.com/mosteo/diskflags", branch = "alire" } gnatcoll = { url = "https://github.com/alire-project/gnatcoll-core.git", commit = "4e663b87a028252e7e074f054f8f453661397166" } diff --git a/deps/clic b/deps/clic index de0330053..56bbdc008 160000 --- a/deps/clic +++ b/deps/clic @@ -1 +1 @@ -Subproject commit de0330053584bad4dbb3dbd5e1ba939c4e8c6b55 +Subproject commit 56bbdc008e16996b6f76e443fd0165a240de1b13 diff --git a/doc/user-changes.md b/doc/user-changes.md index 24341e0b1..3ddfb7925 100644 --- a/doc/user-changes.md +++ b/doc/user-changes.md @@ -6,6 +6,23 @@ stay on top of `alr` new features. ## Release `2.0-dev` +### New switch `alr build --stop-after=` + +PR [#1573](https://github.com/alire-project/alire/pull/1573) + +From `alr help build`: + +**Build stages** + + Instead of always doing a full build, the process can be stopped early using `--stop-after=`, where `` is one of: + + * sync: sync pristine sources to build location + * generation: generate configuration-dependent files + * post-fetch: running of post-fetch actions + * pre-build: running of pre-build actions + * build: actual building of sources + * post-build: running of post-build actions + ### Enable shared dependencies by default PR [#1449](https://github.com/alire-project/alire/pull/1449) diff --git a/src/alire/alire-builds.ads b/src/alire/alire-builds.ads index c84316812..9fad780fd 100644 --- a/src/alire/alire-builds.ads +++ b/src/alire/alire-builds.ads @@ -27,6 +27,32 @@ package Alire.Builds is -- many more shared releases in the vault, finding toolchains could take -- much more time, hence the separate storage. + -- The following are moments during the build process after which we can + -- interrupt the process "safely", that is, some consistency is to be + -- expected: actions run as a whole, config files all generated, etc. + type Stop_Points is + (Sync, + -- Synchronization of pristine sources from the vault to the build dir. + -- This stage does not exist when using sandboxed dependencies. + + Generation, + -- Generation of files based on profiles/configuration variables + + Post_Fetch, + -- Running of the post-fetch actions, which happens only on the first + -- build after syncing to a new build location. + + Pre_Build, + -- Running of the pre-build actions, which happens on every build + + Build, + -- The actual building of sources + + Post_Build + -- Running of the post-build actions + + ); + function Sandboxed_Dependencies return Boolean; -- Queries config to see if dependencies should be sandboxed in workspace diff --git a/src/alire/alire-roots.adb b/src/alire/alire-roots.adb index 3269d6fe7..a987479fa 100644 --- a/src/alire/alire-roots.adb +++ b/src/alire/alire-roots.adb @@ -1,7 +1,6 @@ with Ada.Directories; with Ada.Unchecked_Deallocation; -with Alire.Builds; with Alire.Conditional; with Alire.Dependencies.Containers; with Alire.Environment.Loading; @@ -33,13 +32,33 @@ package body Alire.Roots is use type UString; + ---------------- + -- Stop_Build -- + ---------------- + + function Stop_Build (Wanted, Actual : Builds.Stop_Points) return Boolean + is + use type Builds.Stop_Points; + begin + if Wanted <= Actual then + Trace.Debug ("Stopping build as requested at stage: " & Wanted'Image); + return True; + else + return False; + end if; + end Stop_Build; + ------------------- -- Build_Prepare -- ------------------- procedure Build_Prepare (This : in out Root; Saved_Profiles : Boolean; - Force_Regen : Boolean) is + Force_Regen : Boolean; + Stop_After : Builds.Stop_Points := + Builds.Stop_Points'Last) + is + use all type Builds.Stop_Points; begin -- Check whether we should override configuration with the last one used -- and stored on disk. Since the first time the one from disk will be be @@ -68,6 +87,10 @@ package body Alire.Roots is -- Changes in configuration may require new build dirs. end if; + if Stop_Build (Stop_After, Actual => Sync) then + return; + end if; + -- Ensure configurations are in place and up-to-date This.Generate_Configuration (Full => Force or else Force_Regen); @@ -83,11 +106,15 @@ package body Alire.Roots is function Build (This : in out Root; Cmd_Args : AAA.Strings.Vector; Build_All_Deps : Boolean := False; - Saved_Profiles : Boolean := True) + Saved_Profiles : Boolean := True; + Stop_After : Builds.Stop_Points := + Builds.Stop_Points'Last) return Boolean is Build_Failed : exception; + use all type Builds.Stop_Points; + -------------------------- -- Build_Single_Release -- -------------------------- @@ -193,18 +220,29 @@ package body Alire.Roots is end if; -- Run post-fetch, it will be skipped if already ran + Properties.Actions.Executor.Execute_Actions (This, State, Properties.Actions.Post_Fetch); + if Stop_Build (Stop_After, Actual => Post_Fetch) then + return; + end if; + -- Pre-build must run always + Properties.Actions.Executor.Execute_Actions (This, State, Properties.Actions.Pre_Build); + if Stop_Build (Stop_After, Actual => Pre_Build) then + return; + end if; + -- Actual build + if Release.Origin.Requires_Build then Call_Gprbuild (Release); else @@ -213,7 +251,12 @@ package body Alire.Roots is & ": release has no sources.", Detail); end if; + if Stop_Build (Stop_After, Actual => Build) then + return; + end if; + -- Post-build must run always + Properties.Actions.Executor.Execute_Actions (This, State, @@ -224,9 +267,13 @@ package body Alire.Roots is end Build_Single_Release; begin - This.Build_Prepare (Saved_Profiles => Saved_Profiles, - Force_Regen => False); + Force_Regen => False, + Stop_After => stop_after); + + if Stop_Build (Stop_After, Actual => Generation) then + return True; + end if; This.Export_Build_Environment; diff --git a/src/alire/alire-roots.ads b/src/alire/alire-roots.ads index ca9eab8f0..47d79308d 100644 --- a/src/alire/alire-roots.ads +++ b/src/alire/alire-roots.ads @@ -3,6 +3,7 @@ private with Ada.Finalization; with AAA.Strings; +with Alire.Builds; private with Alire.Builds.Hashes; with Alire.Containers; with Alire.Crate_Configuration; @@ -264,7 +265,9 @@ package Alire.Roots is function Build (This : in out Root; Cmd_Args : AAA.Strings.Vector; Build_All_Deps : Boolean := False; - Saved_Profiles : Boolean := True) + Saved_Profiles : Boolean := True; + Stop_After : Builds.Stop_Points := + Builds.Stop_Points'Last) return Boolean; -- Recursively build all dependencies that declare executables, and finally -- the root release. Also executes all pre-build/post-build actions for @@ -286,7 +289,9 @@ package Alire.Roots is procedure Build_Prepare (This : in out Root; Saved_Profiles : Boolean; - Force_Regen : Boolean); + Force_Regen : Boolean; + Stop_After : Builds.Stop_Points := + Builds.Stop_Points'Last); -- Perform all preparations but the building step itself. This will require -- complete configuration, and will leave all files on disk as if an actual -- build were attempted. May optionally use saved profiles from the command diff --git a/src/alr/alr-commands-build.adb b/src/alr/alr-commands-build.adb index e4327b58d..92c8afe28 100644 --- a/src/alr/alr-commands-build.adb +++ b/src/alr/alr-commands-build.adb @@ -1,5 +1,7 @@ -with Alire.Builds; +with AAA.Enum_Tools; + with Alire.Crate_Configuration; +with Alire.TOML_Adapters; with Alire.Utils.Switches; with Stopwatch; @@ -7,6 +9,7 @@ with Stopwatch; package body Alr.Commands.Build is Switch_Profiles : constant String := "--profiles"; + Switch_Stop : constant String := "--stop-after"; -------------------- -- Apply_Profiles -- @@ -51,17 +54,36 @@ package body Alr.Commands.Build is procedure Execute (Cmd : in out Command; Args : AAA.Strings.Vector) is + function Is_Valid_Stage is + new AAA.Enum_Tools.Is_Valid (Alire.Builds.Stop_Points); + use Alire.Utils.Switches; Profiles_Selected : constant Natural := Alire.Utils.Count_True ((Cmd.Release_Mode, Cmd.Validation_Mode, Cmd.Dev_Mode)); Profile : Profile_Kind; + Stop_After : Alire.Builds.Stop_Points := Alire.Builds.Stop_Points'Last; begin + -- Validation + if Profiles_Selected > 1 then Reportaise_Wrong_Arguments ("Only one build profile can be selected"); end if; + if Cmd.Stop_After.all /= "" then + if Is_Valid_Stage (Alire.TOML_Adapters.Adafy (Cmd.Stop_After.all)) + then + Stop_After := Alire.Builds.Stop_Points'Value + (Alire.TOML_Adapters.Adafy (Cmd.Stop_After.all)); + else + Reportaise_Wrong_Arguments + ("Stopping stage is invalid: " & TTY.Error (Cmd.Stop_After.all) + & "; see " & TTY.Terminal ("alr help build") + & " for valid values"); + end if; + end if; + Cmd.Requires_Workspace; -- Build profile in the command line takes precedence. The configuration @@ -88,7 +110,7 @@ package body Alr.Commands.Build is -- And redirect to actual execution procedure - if not Execute (Cmd, Args) then + if not Execute (Cmd, Args, Stop_After) then Reportaise_Command_Failed ("Compilation failed."); end if; end Execute; @@ -98,9 +120,12 @@ package body Alr.Commands.Build is ------------- function Execute (Cmd : in out Commands.Command'Class; - Args : AAA.Strings.Vector) + Args : AAA.Strings.Vector; + Stop : Alire.Builds.Stop_Points := + Alire.Builds.Stop_Points'Last) return Boolean is + use type Alire.Builds.Stop_Points; begin -- Prevent premature update of dependencies, as the exact folders -- will depend on the build hashes, which are yet unknown until @@ -110,16 +135,30 @@ package body Alr.Commands.Build is declare Timer : Stopwatch.Instance; + Build_Kind : constant String := + (if Stop < Alire.Builds.Stop_Points'Last + then TTY.Warn ("Partial") & " build" + else "Build"); begin if Cmd.Root.Build (Args, - Saved_Profiles => Cmd not in Build.Command'Class) + Saved_Profiles => Cmd not in Build.Command'Class, + Stop_After => Stop) -- That is, we apply the saved profiles unless the user is -- explicitly invoking `alr build`. then - Trace.Info ("Build finished successfully in " - & TTY.Bold (Timer.Image) & " seconds."); - Trace.Detail ("Use alr run --list to check available executables"); + Alire.Put_Success (Build_Kind & " finished successfully in " + & TTY.Bold (Timer.Image) & " seconds."); + + if Stop < Alire.Builds.Stop_Points'Last then + Alire.Put_Info + ("Build was requested to stop after stage: " + & TTY.Emph (AAA.Strings.To_Lower_Case (Stop'Image)) + & "; build artifacts may be missing."); + else + Trace.Detail + ("Use alr run --list to check available executables"); + end if; return True; @@ -137,10 +176,30 @@ package body Alr.Commands.Build is overriding function Long_Description (Cmd : Command) return AAA.Strings.Vector - is (AAA.Strings.Empty_Vector + is + use all type Alire.Builds.Stop_Points; + + ----------- + -- Stage -- + ----------- + + function Stage (Name : Alire.Builds.Stop_Points; + Description : String) + return String + is ("* " + & TTY.Emph (Alire.TOML_Adapters.Tomify (Name'Image)) + & ": " & Description); + + function Building return Alire.Builds.Stop_Points + is (Alire.Builds.Build); + + begin + return AAA.Strings.Empty_Vector .Append ("Invokes gprbuild to compile all targets in the current" & " crate.") .New_Line + .Append (TTY.Bold ("Build profiles")) + .New_Line .Append ("A build profile can be selected with the appropriate switch." & " The profile is applied to the root release only, " & "whereas dependencies are built in release mode. Use " @@ -158,7 +217,33 @@ package body Alr.Commands.Build is & " (dependencies). Indirect builds through, e.g., '" & TTY.Terminal ("alr run") & "' will use the last '" & TTY.Terminal ("alr build") & "' configuration.") - ); + .New_Line + .Append (TTY.Bold ("Build stages")) + .New_Line + .Append ("Instead of a full build, the process can be stopped early " + & "using " & TTY.Terminal (Switch_Stop) & "=, where " + & "is one of:") + .New_Line + .Append (Stage (Sync, " sync pristine sources to build location")) + .Append (Stage (Generation, "generate configuration-dependent files")) + .Append (Stage (Post_Fetch, "running of post-fetch actions")) + .Append (Stage (Pre_Build, " running of pre-build actions")) + .Append (Stage (Building, " actual building of sources")) + .Append (Stage (Post_Build, "running of post-build actions")) + .New_Line + .Append ("These stages are always run in the given order. A premature" + & " stop will likely not produce the complete build " + & "artifacts, so it is intended for advanced usage when " + & "debugging or testing specific build stages, or to ensure " + & "generated files are up-to-date without launching a " + & "costly build, for example.") + .New_Line + .Append ("After a partial build, to ensure a proper full build is" + & " performed, just run a regular " + & TTY.Terminal ("alr build") & " without " + & Switch_Stop & ".") + ; + end Long_Description; -------------------- -- Setup_Switches -- @@ -190,6 +275,12 @@ package body Alr.Commands.Build is "", Switch_Profiles & "=", "Comma-separated list of = values (see description)"); + Define_Switch + (Config, + Cmd.Stop_After'Access, + "", Switch_Stop & "=", + "Build stage after which to stop (see description)"); + end Setup_Switches; end Alr.Commands.Build; diff --git a/src/alr/alr-commands-build.ads b/src/alr/alr-commands-build.ads index 74d6d636b..5c7591bfc 100644 --- a/src/alr/alr-commands-build.ads +++ b/src/alr/alr-commands-build.ads @@ -1,5 +1,7 @@ with AAA.Strings; +with Alire.Builds; + private with GNAT.OS_Lib; package Alr.Commands.Build is @@ -21,7 +23,9 @@ package Alr.Commands.Build is Args : AAA.Strings.Vector); function Execute (Cmd : in out Commands.Command'Class; - Args : AAA.Strings.Vector) + Args : AAA.Strings.Vector; + Stop : Alire.Builds.Stop_Points := + Alire.Builds.Stop_Points'Last) return Boolean; -- Returns True if compilation succeeded. For invocations after some other -- command that already has set up the build environment we need to avoid @@ -53,5 +57,6 @@ private Profiles : aliased GNAT.OS_Lib.String_Access; -- A string of "crate:profile" values, with "*" meaning all crates and -- "%" meaning all crates without a previous setting in a manifest. + Stop_After : aliased GNAT.OS_Lib.String_Access; end record; end Alr.Commands.Build; diff --git a/testsuite/tests/build/stop-after/test.py b/testsuite/tests/build/stop-after/test.py new file mode 100644 index 000000000..bd8448469 --- /dev/null +++ b/testsuite/tests/build/stop-after/test.py @@ -0,0 +1,69 @@ +""" +Check the several stop points in the build process +""" + +from enum import IntEnum +import os +import shutil +from drivers.alr import run_alr, init_local_crate, add_action, alr_with +from drivers import builds +# from drivers.asserts import assert_eq, assert_match + + +class StopStage(IntEnum): + sync = 0 + generation = 1 + post_fetch = 2 + pre_build = 3 + build = 4 + post_build = 5 + + +# A function that checks things are as they should be +def check_stop(stop: StopStage): + # Run alr + out = run_alr("build", f"--stop-after={stop.name.replace('_', '-')}", + quiet=False).out + + # Sync should have happened + if builds.are_shared(): + assert builds.find_dir("libhello"), "libhello build dir should exist" + + # Check generation of config files + assert (stop >= StopStage.generation) == os.path.isfile("config/xxx_config.gpr"), \ + f"config dir existence [un]expected: {idx}, {os.path.isfile('config/xxx_config.gpr')}" + + # Check post-fetch, which should happen only once + assert (stop == StopStage.post_fetch) == ("post-fetch-triggered" in out), \ + f"post-fetch-triggered should be in output: {out}" + + # Check pre-build, which should happen in every build that goes beyond it + assert (stop >= StopStage.pre_build) == ("pre-build-triggered" in out), \ + f"pre-build-triggered should be in output: {out}" + + # Check build artifacts exist only after build + assert (stop >= StopStage.build) == os.path.isdir("obj"), \ + f"obj dir existence [un]expected" + + # Check post-build + assert (stop >= StopStage.post_build) == ("post-build-triggered" in out), \ + f"post-build-triggered should be in output: {out}" + + +# TEST BEGIN + +# Prepare the crate with actions and a dependency whose syncing we can test +init_local_crate() +add_action("post-fetch", ["echo", "post-fetch-triggered"]) +add_action("pre-build", ["echo", "pre-build-triggered"]) +add_action("post-build", ["echo", "post-build-triggered"]) +alr_with("libhello") + +# Remove config dir which may have been generated during initialization +shutil.rmtree("config") + +# Check that the stop points are honored +for stop in StopStage: + check_stop(stop) + +print("SUCCESS") diff --git a/testsuite/tests/build/stop-after/test.yaml b/testsuite/tests/build/stop-after/test.yaml new file mode 100644 index 000000000..fc358a9cc --- /dev/null +++ b/testsuite/tests/build/stop-after/test.yaml @@ -0,0 +1,4 @@ +driver: python-script +build_mode: both # one of shared, sandboxed, both (default) +indexes: + basic_index: {}