Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft of cli based build system #1967

Closed
wants to merge 24 commits into from

Conversation

DmitryAstafyev
Copy link
Collaborator

No description provided.

@DmitryAstafyev DmitryAstafyev marked this pull request as draft November 24, 2023 12:24
Copy link
Member

@AmmarAbouZor AmmarAbouZor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a look on the great draft and allowed myself to write some comments on some of the files. I will have a deeper look on the other files and may write other comments if desired

cli/src/main.rs Outdated
Comment on lines 21 to 25
lazy_static! {
static ref LOCATION: Location = Location::new().expect("Fail to setup location of ICSMW");
static ref TRACKER: Tracker = Tracker::new();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can have a look at once_cell which advertised as the new replacement for lazy_static where the standard library is supported.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a look on the current progress in rust standard library of OnceLock and LazyLock.
Since LazyLock is in nightly only, I don't think that we need to change the current implementation

Comment on lines 16 to 17
src.to_string_lossy(),
dest.to_string_lossy()
Copy link
Member

@AmmarAbouZor AmmarAbouZor Nov 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PathBuf implements Display trait, so this could be written as

  format!("copied: {} to {}", src.display(), dest.display()),

I think though, that display calls to_string_lossy() under the hood on most operation systems

Comment on lines 16 to 28
let mut root = current_dir()?;
let mut len = root.iter().collect::<Vec<&OsStr>>().len();
loop {
if len == 0 {
return Err(Error::new(
ErrorKind::NotFound,
"Fail to find ICSMW location",
));
}
// TODO: better compare folders stucts or some file, like some git config file
if root.ends_with("chipmunk") || root.ends_with("logviewer") {
break;
}
if len > 0 {
len = len.saturating_sub(1);
}
root.pop();
}
Ok(Self { root })
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is rewrite to the same function if you want to drop the extra allocation and keeping track of the len variable

        let mut root = current_dir()?;
        // TODO: better compare folders stucts or some file, like some git config file
        while !root.ends_with("chipmunk") && !root.ends_with("logviewer") {
            if !root.pop() {
                return Err(Error::new(
                    ErrorKind::NotFound,
                    "Fail to find ICSMW location",
                ));
            }
        }

        Ok(Self { root })

Comment on lines 39 to 40
let path_str = path.to_string_lossy().to_string();
path_str.replace(&LOCATION.root.to_string_lossy().to_string(), "")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use the built in methods from PathBuf

    if let Ok(relative_path) = path.strip_prefix(&LOCATION.root) {
        relative_path.to_string_lossy().to_string()
    } else {
        // TODO: Should we panic here or return an error?
        path.to_string_lossy().to_string()
    }

@DmitryAstafyev
Copy link
Collaborator Author

@AmmarAbouZor you are welcome to create a PR to my fork with your suggestions. Please, let me know if there are some kind of permissions issues.

@AmmarAbouZor
Copy link
Member

@DmitryAstafyev I created a PR to your fork with the small refactoring mentioned here. I'll have a deeper look on maybe create then other PRs with farther optimizations

DmitryAstafyev and others added 16 commits January 22, 2024 10:29
- Display is more suitable when printing infos for the users
- Use rust built-in path methods
- Change method signature to return Path instead of String
- Function is rewritten in a more idiomatic way without having to
  allocate memory and keeping track of the length manually
- Use Path built-in methods to set the path of the build command
- Module for wasm management have been created with the needed
  implementation to integrate in the system.
- Some Todos are left to be clarified later.
- Method to run the tests has been implemented to use when running the
  tests is built in the cli app
- TODOs has been removed after clarification
- Set Color flag to always in build command
- Test Cli Command defined and implemented
- Methods for tests in Manager trait has been defined and implemented
- Test commands in wasm module added
- Spawn Options has been added to provide more control over how spawn call
  should behave.
- It's used to suppress sending messages in wasm npm test command
  because it caused weird behavior in the progress bars because when too
  many lines are sent at the same time
- Rewrite jobs alignment to keep the braces and the symbols in the same
  horizontal alignment among all lines
- Small refactoring to get the length or max
- Change job numbers placeholder from zero to spaces in tracker
@DmitryAstafyev
Copy link
Collaborator Author

DmitryAstafyev commented Jan 22, 2024

@AmmarAbouZor thanks for your changes, all were merged. I've also added a couple of small changes to make possible to build a solution. and cli --release build --target app (cli build --target app (dev mode)) already works. Next steps could be:

Commands and features:

  • check release command. Actually, the release command includes two steps (after build): build the electron application; and compress artifacts.
  • check tests commands
  • check build commands for each application part (client, bindings, etc)

Logs and output:

  • on errors we should drop logs from a command, that failed. It looks like it already works like that...
  • we should have some env var like CHIPMUNK_LOGS to make CLI drop any stdout/stderr into the console... in this case, progress bars can be disabled. We need this feature for github actions (progress bars there are useless)
  • makes sense to save all stdout/stderr into some log file in a root... like build.log or something like it. And we should always drop this file with each new build... I mean file includes logs of one build always, no needs to append it.

Bugs (probably) / features:

  • during building, I've noticed - the progress bar is freezing sometimes. For example during client building. Weird.
  • maybe makes sense to move time of task into the beginning
  [14/16][done][application/platform: TS compilation] Done in 2s.
  [15/16][done][application/holder: TS compilation] Done in 3s.
  [16/16][done][application/client: TS compilation] Done in 12s.

change to

  [14/16][done][2s ][application/platform: TS compilation]
  [15/16][done][3s ][application/holder: TS compilation]
  [16/16][done][12s][application/client: TS compilation]
  • also would be nice to have common time at the end
  [14/16][done][2s ][application/platform: TS compilation]
  [15/16][done][3s ][application/holder: TS compilation]
  [16/16][done][12s][application/client: TS compilation]
  [total] done all in 17s.
  • just as a bonus we can have a measurement of durations of build times (really not important, but nice to have at some day). That's just to give better understanding most expensive task:
  [14/16][done][█░░░░ 2s  ][application/platform: TS compilation]
  [15/16][done][██░░░ 3s  ][application/holder: TS compilation]
  [16/16][done][████░ 12s ][application/client: TS compilation]

@AmmarAbouZor
Copy link
Member

Bugs (probably) / features:

  • during building, I've noticed - the progress bar is freezing sometimes. For example during client building. Weird.

Currently the progress bar will be update on each tasks each time the running command outputs a line to stdout, which is inconsistent and could lead to the feeling that something is forzen when a command doesn't output anything to stdout for a while. We can change the behavior entirely be binding the progress bar to a timer which emits events with fixed interval. However, this could lead to false positive when a command is really frozen but the progress bar is keep moving. I think it's a trade-off here but I lean to the timers since the command doesn't hang that often.

I'll start with the native async-trait and the other UI stuff, then I can build the timer if the final decision is in this direction or then we can see how to implement the logs in the best way possible.

@AmmarAbouZor
Copy link
Member

@DmitryAstafyev I've provided the first PR for the UI improvements. When it's merged I can implement making the progress bar to the left moving always using a timer as mentioned above if we decide to go with that suggestion

- Job duration is moved to the prefix part of the bars.
- Total time is appended to the results.
The states of jobs has been growing and getting too complex for a tuple
The padding of finishing time can change on job ends which led to
miss-alignment in the bars in some cases
@DmitryAstafyev
Copy link
Collaborator Author

@AmmarAbouZor looks really good.
Regarding timers... let's refuse from timers usage. Timers always come with some extra headache. We have here a bug in spawner (cli/src/spawner.rs)... it doesn't work like expected.

Take a look at this piece of code. We are "catching" stdout and actually updating the message in a progress bar and bar as itself too.

let drain_stdout = {
        let storage = &mut stdout_lines;
        let stdout = child.stdout.take().unwrap();
        async move {
            let mut buf = BufReader::new(stdout);
            loop {
                let mut line = String::new();
                let read_lines = buf.read_line(&mut line).await?;
                if read_lines == 0 {
                    break;
                } else {
                    if !opts.suppress_msg {
                        TRACKER.msg(sequence, &line).await;      // <-- that should update message and progress bar
                    }
                    TRACKER.progress(sequence, None).await;
                    storage.push(line);
                }
            }
            future::pending::<()>().await;
            Ok::<Option<ExitStatus>, io::Error>(None)
        }
    };

The problem is - that we actually don't get data from stdout... for example ng build gives just 3 lines of stdout... but if you would run it in a terminal, It will be hundreds of lines of data.

Well as soon as this issue is resolved, we will not have an issue with updating of progress bar anymore.

Could you try to resolve it?

@AmmarAbouZor
Copy link
Member

@DmitryAstafyev I've found the problem. It's actually in the command yarn run build itself because it sends the most of the lines to the stderr instead of stdout.
You can check that by running the following commands from the directory application/client

# This will output about 5 lines
yarn run build > output.txt

# This will output all the lines because we are piping both stdout and stderr
yarn run build &> output_and_err.txt

We can also add the flag --verbose to the build command like yarn run build --verbose which will give as more lines too.

To solve this problem we can listen to both stdout and stderr inside a select macro. Should I implement this solution in a new PR or do we need more organized way to save the data (like saving stdout and stderr separately) ?

@AmmarAbouZor
Copy link
Member

@DmitryAstafyev I was playing to test the possibilities for that and I made two prototypes one updates the bars on both stdout and stderrand the other uses a timer to update the bars regularly with a constant interval. I've created two branches on my fork for each prototype and here are links so you can get a feeling on each solution:

I don't think updating on both stdout and stderr made a big difference because the commands seem to send their updates on big chunks which didn't gave us big impact on the feeling for the progress bar. You can check them and pick one then I refine it and create a PR on the wanted branch

@DmitryAstafyev
Copy link
Collaborator Author

DmitryAstafyev commented Feb 5, 2024

Hello @AmmarAbouZor
Thanks for updates. Let's just push each new message from stderr (like with stdout) considering options

                    if !opts.suppress_msg {
                        TRACKER.msg(sequence, &line).await;
                    }

Indeed you are welcome to make some refactoring handling stdout / stderr and handle it with select or in another way, but in general I would still keep the possibility to store it separately.

As for timers/intervals - no need to add it.

One more thing, which would be nice to change/add - migrate from async_* to tokio. Could you take care of it?

@DmitryAstafyev
Copy link
Collaborator Author

@AmmarAbouZor okay let's make a plan of an integration:

Stage 1

  • finish refactoring of spawner (storing stdout/stderr)
  • handle error case. In case of error we should drop into console full trace (all collected logs)
  • testing across platforms (windows, linux, mac)
  • merge into master

Stage 2

  • add hash function. The idea is: builder should "know" which part of solution should be rebuild and which can stay. That means if I run cli build --target app it should build solution; if I run it one more time (without any changes in solution's code) it actually should skip all steps because app with actual code already has been built before. Only if something in xxx/src has been changed - it should be rebuilt. Probably best way would be store somewhere (in some file) timestamp of last build. As soon as we have it, we can compare last-build-timestamp with time of modification/creation of each file in ./src, ./assets, etc and make the conclusion - skip or rebuild.

@AmmarAbouZor
Copy link
Member

@DmitryAstafyev I've provided a PR for updating the progress bars on both stdout and stderr, and will take care to migrating form async-std to Tokio before we start with the next steps.

We still have the following steps from the previous comment:

Commands and features:

  • check release command. Actually, the release command includes two steps (after build): build the electron application; and compress artifacts.
  • check tests commands
  • check build commands for each application part (client, bindings, etc)

Logs and output:

  • on errors we should drop logs from a command, that failed. It looks like it already works like that...
  • we should have some env var like CHIPMUNK_LOGS to make CLI drop any stdout/stderr into the console... in this case, progress bars can be disabled. We need this feature for github actions (progress bars there are useless)
  • makes sense to save all stdout/stderr into some log file in a root... like build.log or something like it. And we should always drop this file with each new build... I mean file includes logs of one build always, no needs to append it.

Should we add/merge them to the current plan, or they aren't relevant anymore?

Some build commands send big part of their infos to stderr than stdout,
which gives the app a little more responsive looking if we update the
bars on stderr too
- Bounded channels with 1 capacity is replaced with oneshot channels.
- Unused Clone is removed from Tick enum because the oneshot channels
  aren't clone
@AmmarAbouZor AmmarAbouZor mentioned this pull request Feb 17, 2024
17 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants