Skip to content

Conversation

@MeteorsLiu
Copy link
Collaborator

  • Implement Builder struct with Init and Build methods for formula builds
  • Add lockedfile package for safe concurrent file operations
  • Support platform-specific file locking (Unix, Windows, Plan9)
  • Include build caching to avoid redundant builds
  • Add Mutex type for cross-process synchronization
  • Implement comprehensive tests for file locking behavior

@gemini-code-assist
Copy link

Summary of Changes

Hello @MeteorsLiu, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a foundational build system designed for managing and executing formula builds. It prioritizes concurrency safety and efficiency by incorporating cross-process file locking and a caching mechanism. The system is built to be robust across different operating systems and provides controlled execution environments for builds, ensuring reliable and repeatable results.

Highlights

  • New Build System Implementation: Introduced a Builder struct with Init and Build methods to manage formula builds, including dependency resolution and execution of build steps.
  • Cross-Process File Locking: Integrated a lockedfile package to provide robust cross-process file locking, ensuring safe concurrent operations during builds and repository synchronization across different processes.
  • Platform-Specific Locking Support: The file locking mechanism now supports various operating systems, including Unix, Windows, and Plan9, with specific implementations tailored for each platform.
  • Build Caching: Implemented a build caching mechanism to store and retrieve results of previous builds, significantly reducing redundant build times for unchanged formulas.
  • Environment Management during Build: The build process now saves and restores the environment variables and redirects standard output/error, allowing for cleaner and more controlled build execution, especially for non-verbose builds.
  • Comprehensive Testing: Added extensive unit tests for both the core build logic and the file locking mechanisms, covering various scenarios including mutual exclusion and cache behavior.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new build system with cross-process file locking to ensure safe concurrent builds. The lockedfile package, which provides platform-specific file locking, is well-implemented and tested, seemingly adapted from Go's internal tooling. The overall build logic in the Builder struct correctly uses these locks to manage build caches and outputs.

However, there are several critical issues in internal/build/build.go related to the modification of process-wide state. The use of os.Chdir, modification of process environment variables (os.Setenv), and redirection of global os.Stdout/os.Stderr make the build process unsafe for concurrent execution within the same process (e.g., via goroutines). These operations can lead to race conditions and unpredictable behavior. I've provided detailed comments and suggestions to address these concurrency-related problems.

Additionally, I've found a compilation error in the test setup and some minor code style issues in the tests.

return nil
}

if err := os.Chdir(f.Proj.BuildDir); err != nil {

Choose a reason for hiding this comment

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

critical

The use of os.Chdir changes the current working directory for the entire process. This is not safe for concurrency. If multiple builds are run in parallel within the same process (e.g., in different goroutines), they will interfere with each other, leading to race conditions and incorrect build behavior. The build script execution should be configured to run in a specific directory without changing the process's global state. For example, if using os/exec, the Cmd.Dir field should be set. To make this safer even in sequential execution, you should also defer a change back to the original directory to handle errors and panics correctly.

Comment on lines 90 to 119
savedEnv := os.Environ()

// Redirect stdout/stderr if not verbose
var savedStdout, savedStderr *os.File
if !opts.Verbose {
savedStdout = os.Stdout
savedStderr = os.Stderr
devNull, err := os.Open(os.DevNull)
if err != nil {
return err
}
os.Stdout = devNull
os.Stderr = devNull
defer func() {
devNull.Close()
os.Stdout = savedStdout
os.Stderr = savedStderr
}()
}

if err := f.OnBuild(f.Proj, results); err != nil {
return err
}

os.Clearenv()
for _, e := range savedEnv {
if k, v, ok := strings.Cut(e, "="); ok {
os.Setenv(k, v)
}
}

Choose a reason for hiding this comment

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

critical

The build logic modifies the process-wide environment variables using os.Environ, os.Clearenv, and os.Setenv. This is not safe for concurrent execution within the same process, as parallel builds would overwrite each other's environment. Environment variables should be passed directly to the child process being executed (e.g., via exec.Cmd.Env).

Furthermore, the restoration logic (lines 114-119) is not in a defer block. If f.OnBuild panics, the environment will not be restored, leaving the process in a corrupted state. This logic should be moved into a defer function to ensure it always executes.

Comment on lines 92 to 108
// Redirect stdout/stderr if not verbose
var savedStdout, savedStderr *os.File
if !opts.Verbose {
savedStdout = os.Stdout
savedStderr = os.Stderr
devNull, err := os.Open(os.DevNull)
if err != nil {
return err
}
os.Stdout = devNull
os.Stderr = devNull
defer func() {
devNull.Close()
os.Stdout = savedStdout
os.Stderr = savedStderr
}()
}

Choose a reason for hiding this comment

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

critical

Redirecting os.Stdout and os.Stderr affects the entire process. If multiple builds run concurrently in goroutines, their output will be unpredictably silenced or redirected, and they may interfere with each other's stream restoration. Instead of redirecting the global os.Stdout/os.Stderr, the output streams of the executed command should be controlled directly (e.g., by setting cmd.Stdout and cmd.Stderr on an exec.Cmd struct to io.Discard or a file).

}
os.RemoveAll(formulaDir)

if err = os.CopyFS(formulaDir, os.DirFS("testdata")); err != nil {

Choose a reason for hiding this comment

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

critical

The function os.CopyFS does not exist in the standard os package. This will cause a compilation error. You might be looking for a function to copy a directory from an fs.FS, which would typically require walking the fs.FS (e.g., with fs.WalkDir) and copying files individually, or using a helper from a third-party library.

)

type Builder struct {
initOnce sync.Once

Choose a reason for hiding this comment

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

medium

The initOnce field in the Builder struct is declared but never used. If it's intended for future use, a comment explaining its purpose would be helpful. Otherwise, it should be removed to avoid dead code.

}
}

func TestBuildZlib(t *testing.T) {

Choose a reason for hiding this comment

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

medium

The test function TestBuildZlib also includes a sub-test for "libpng". It would be more descriptive to rename this test function to something more general, like TestBuild, to accurately reflect that it tests the build process for multiple libraries.

Comment on lines +36 to +57
t.Fatal(err)
return

Choose a reason for hiding this comment

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

medium

The return statement after t.Fatal(err) is redundant because t.Fatal stops the execution of the current test goroutine. This can be removed to make the code more concise. This applies to other similar error handling blocks in this file (e.g., lines 41-42, 53-54, etc.).

Suggested change
t.Fatal(err)
return
t.Fatal(err)

Add Matrix struct with Require, Options, and DefaultOptions fields to
support multi-dimensional build configurations. Implement methods for
string representation, cartesian product combinations, and combination
counting. Also update OnBuild callback to return error for better
error handling.
- Add app() method to ModuleF to expose gsh.App instance
- Initialize gsh.App in Gopt_ModuleF_Main entry point
- Add OutputDir field to BuildResult struct
- Bump github.com/qiniu/x dependency from v1.15.1 to v1.15.3
Remove unused OutputDir field from the BuildResult struct to simplify
the build result representation.
- Implement Builder struct with Init and Build methods for formula builds
- Add lockedfile package for safe concurrent file operations
- Support platform-specific file locking (Unix, Windows, Plan9)
- Include build caching to avoid redundant builds
- Add Mutex type for cross-process synchronization
- Implement comprehensive tests for file locking behavior
- Rename formula package import to classfile for clarity
- Relocate lockedfile to internal/lockedfile, modload to internal/modules
- Remove Init method and integrate VCS sync into Build method
- Simplify Build signature to accept pre-loaded module targets
- Remove BuildOptions and verbose output handling
- Change directory structure to use .source/ and .build/{version}/{matrix}
- Replace panic with error return for missing OnBuild handler
- Remove environment save/restore logic during build execution
- Add Result struct with OutputDir field to return build outputs
- Move matrix parameter from Build() to NewBuilder() constructor
- Change Build() signature to return []Result with main target at index 0
- Rename BuildDir to OutputDir in Project struct for consistency
- Update tests to use new Builder API and verify returned results
- Add OutputDir field to BuildResult struct
- Extend Project struct with Deps, BuildResults, and Matrix fields
- Change DirFS to FileFS (ReadFileFS) for simpler file reading
- Add Use() method to configure build environment for dependencies
- Set PKG_CONFIG_PATH, CMAKE paths, and platform-specific compiler flags
- Support both Windows (MSVC) and Unix (GCC/Autotools) environments
- Add Context struct to encapsulate build environment (matrix, source dir, build results)
- Change OnBuild signature to accept Context, Project, and BuildResult parameters
- Update error handling to check results.Err instead of OnBuild return value
- Rename module.Version.ID to module.Version.Path for clarity
- Change BuildResults from pointer to value type in maps
- Rename BuildResult.OutputDir to BuildResult.Dir
- Add FileFS field to Project using fs.ReadFileFS interface
- Update all tests to use Path instead of ID

This refactoring improves separation of concerns by introducing a Context
object that carries build-related state, making the build process more
flexible and testable.
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.

1 participant