Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
dbd0cdc
refactor
CCFenner Sep 19, 2025
7550012
use defer
CCFenner Sep 20, 2025
091e4ae
fix typo in parameter
CCFenner Sep 20, 2025
99e03f7
install wheel dependency
CCFenner Sep 20, 2025
b759b6e
fix test cases
CCFenner Sep 20, 2025
ed35c2d
add sonar config
CCFenner Aug 15, 2025
416feb3
Revert "add sonar config"
CCFenner Aug 27, 2025
1baca48
add build function
CCFenner Aug 29, 2025
602b154
move install function
CCFenner Aug 29, 2025
a116c85
add feature flag pkg
CCFenner Aug 29, 2025
03f19fd
add support for toml file
CCFenner Aug 30, 2025
ebbafeb
update docs
CCFenner Aug 30, 2025
9ca556c
rename
CCFenner Sep 2, 2025
211a3a8
add toml flag
CCFenner Sep 11, 2025
d58b7be
Revert "add toml flag"
CCFenner Sep 12, 2025
43b6432
remove feature flag
CCFenner Sep 12, 2025
ac6c0e7
Revert "add feature flag pkg"
CCFenner Sep 12, 2025
d7b65dc
adjust log messages
CCFenner Sep 12, 2025
cebce21
install build module
CCFenner Sep 12, 2025
f4edbcd
add functions to install
CCFenner Sep 12, 2025
5faf70a
update
CCFenner Sep 12, 2025
4f5dbc5
use other pip
CCFenner Sep 12, 2025
bcd6d14
debug
CCFenner Sep 12, 2025
6b0a242
debug
CCFenner Sep 12, 2025
8655d57
debug
CCFenner Sep 12, 2025
07f7eaf
fix test case
CCFenner Sep 17, 2025
8fa38ce
remove commented code
CCFenner Sep 17, 2025
305cd1e
switch param order
CCFenner Sep 17, 2025
216731c
rename param
CCFenner Sep 17, 2025
6161ef9
add test cases
CCFenner Sep 17, 2025
6b0ba9a
move venv handling to pip pkg
CCFenner Sep 17, 2025
8022ad7
move venv handling to pip pkg
CCFenner Sep 17, 2025
b8b8f0f
move venv handling to build file
CCFenner Sep 17, 2025
ef75950
move publish to python pkg
CCFenner Sep 17, 2025
50727b8
fix test
CCFenner Sep 18, 2025
1561451
fix test
CCFenner Sep 18, 2025
516d92d
fix test
CCFenner Sep 18, 2025
ac5d1fe
fix tests
CCFenner Sep 18, 2025
2e78dc1
fix test cases
CCFenner Sep 22, 2025
5518724
Merge branch 'master' into python
CCFenner Sep 23, 2025
f66f97c
cleanup
CCFenner Sep 23, 2025
4372f9c
add error message
CCFenner Sep 23, 2025
a5e03bc
cleanup
CCFenner Sep 23, 2025
c06abb2
fix test case
CCFenner Sep 23, 2025
4a61ae9
handle toml file
CCFenner Sep 25, 2025
355acb9
add toml handling
CCFenner Oct 1, 2025
cd56093
cleanup
CCFenner Oct 2, 2025
16939fb
add test cases
CCFenner Oct 2, 2025
bf5bf03
update tests
CCFenner Oct 2, 2025
e0ffd5c
use toml pkg
CCFenner Oct 2, 2025
e7e9100
Added toml unit tests
petkodimitrov24 Oct 24, 2025
4ba437b
Merge branch 'master' into python-version
CCFenner Oct 29, 2025
672e8e9
Merge branch 'master' into python-version
petkodimitrov24 Nov 3, 2025
5db3ed1
Merge branch 'master' into python-version
petkodimitrov24 Nov 4, 2025
73961cd
Merge branch 'master' into python-version
petkodimitrov24 Nov 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions cmd/pythonBuild_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,99 @@ func TestRunPythonBuild(t *testing.T) {
assert.Equal(t, []string{"env", "--output-file", "bom-pip.xml", "--output-format", "XML", "--spec-version", "1.4"}, utils.ExecMockRunner.Calls[5].Params)
})
}

func TestRunPythonBuildWithToml(t *testing.T) {
cpe := pythonBuildCommonPipelineEnvironment{}
// utils := newPythonBuildTestsUtils()

SetConfigOptions(ConfigCommandOptions{
// OpenFile: utils.FilesMock.OpenFile,
OpenFile: config.OpenPiperFile,
})

t.Run("success - build", func(t *testing.T) {
config := pythonBuildOptions{
VirtualEnvironmentName: "dummy",
}
utils := newPythonBuildTestsUtils()
utils.AddFile("pyproject.toml", []byte(minimalSetupPyFileContent))
utils.AddDir("dummy")
telemetryData := telemetry.CustomData{}

err := runPythonBuild(&config, &telemetryData, utils, &cpe)
assert.NoError(t, err)
// assert.Equal(t, 3, len(utils.ExecMockRunner.Calls))
assert.Equal(t, "python3", utils.ExecMockRunner.Calls[0].Exec)
assert.Equal(t, []string{"-m", "venv", "dummy"}, utils.ExecMockRunner.Calls[0].Params)
})

t.Run("success - publishes binaries", func(t *testing.T) {
config := pythonBuildOptions{
Publish: true,
TargetRepositoryURL: "https://my.target.repository.local",
TargetRepositoryUser: "user",
TargetRepositoryPassword: "password",
VirtualEnvironmentName: "dummy",
}
utils := newPythonBuildTestsUtils()
utils.AddFile("pyproject.toml", []byte(minimalSetupPyFileContent))
utils.AddDir("dummy")
telemetryData := telemetry.CustomData{}

err := runPythonBuild(&config, &telemetryData, utils, &cpe)
assert.NoError(t, err)
assert.Equal(t, "python3", utils.ExecMockRunner.Calls[0].Exec)
assert.Equal(t, []string{"-m", "venv", config.VirtualEnvironmentName}, utils.ExecMockRunner.Calls[0].Params)
assert.Equal(t, "bash", utils.ExecMockRunner.Calls[1].Exec)
assert.Equal(t, []string{"-c", "source " + filepath.Join("dummy", "bin", "activate")}, utils.ExecMockRunner.Calls[1].Params)
assert.Equal(t, filepath.Join("dummy", "bin", "pip"), utils.ExecMockRunner.Calls[2].Exec)
assert.Equal(t, []string{"install", "--upgrade", "--root-user-action=ignore", "pip"}, utils.ExecMockRunner.Calls[2].Params)
assert.Equal(t, filepath.Join("dummy", "bin", "pip"), utils.ExecMockRunner.Calls[3].Exec)
assert.Equal(t, []string{"install", "--upgrade", "--root-user-action=ignore", "."}, utils.ExecMockRunner.Calls[3].Params)
assert.Equal(t, filepath.Join("dummy", "bin", "pip"), utils.ExecMockRunner.Calls[4].Exec)
assert.Equal(t, []string{"install", "--upgrade", "--root-user-action=ignore", "build"}, utils.ExecMockRunner.Calls[4].Params)
assert.Equal(t, filepath.Join("dummy", "bin", "pip"), utils.ExecMockRunner.Calls[5].Exec)
assert.Equal(t, []string{"install", "--upgrade", "--root-user-action=ignore", "wheel"}, utils.ExecMockRunner.Calls[5].Params)
assert.Equal(t, filepath.Join("dummy", "bin", "python"), utils.ExecMockRunner.Calls[6].Exec)
assert.Equal(t, []string{"-m", "build", "--no-isolation"}, utils.ExecMockRunner.Calls[6].Params)
assert.Equal(t, filepath.Join("dummy", "bin", "pip"), utils.ExecMockRunner.Calls[7].Exec)
assert.Equal(t, []string{"install", "--upgrade", "--root-user-action=ignore", "twine"}, utils.ExecMockRunner.Calls[7].Params)
assert.Equal(t, filepath.Join("dummy", "bin", "twine"), utils.ExecMockRunner.Calls[8].Exec)
assert.Equal(t, []string{"upload", "--username", config.TargetRepositoryUser,
"--password", config.TargetRepositoryPassword, "--repository-url", config.TargetRepositoryURL,
"--disable-progress-bar", "dist/*"}, utils.ExecMockRunner.Calls[8].Params)
})

t.Run("success - create BOM", func(t *testing.T) {
config := pythonBuildOptions{
CreateBOM: true,
Publish: false,
VirtualEnvironmentName: "dummy",
}
utils := newPythonBuildTestsUtils()
utils.AddFile("pyproject.toml", []byte(minimalSetupPyFileContent))
utils.AddDir("dummy")
telemetryData := telemetry.CustomData{}

err := runPythonBuild(&config, &telemetryData, utils, &cpe)
assert.NoError(t, err)
assert.Equal(t, "python3", utils.ExecMockRunner.Calls[0].Exec)
assert.Equal(t, []string{"-m", "venv", config.VirtualEnvironmentName}, utils.ExecMockRunner.Calls[0].Params)
assert.Equal(t, "bash", utils.ExecMockRunner.Calls[1].Exec)
assert.Equal(t, []string{"-c", "source " + filepath.Join("dummy", "bin", "activate")}, utils.ExecMockRunner.Calls[1].Params)
assert.Equal(t, []string{"install", "--upgrade", "--root-user-action=ignore", "pip"}, utils.ExecMockRunner.Calls[2].Params)
assert.Equal(t, filepath.Join("dummy", "bin", "pip"), utils.ExecMockRunner.Calls[2].Exec)
assert.Equal(t, []string{"install", "--upgrade", "--root-user-action=ignore", "."}, utils.ExecMockRunner.Calls[3].Params)
assert.Equal(t, filepath.Join("dummy", "bin", "pip"), utils.ExecMockRunner.Calls[3].Exec)
assert.Equal(t, []string{"install", "--upgrade", "--root-user-action=ignore", "build"}, utils.ExecMockRunner.Calls[4].Params)
assert.Equal(t, filepath.Join("dummy", "bin", "pip"), utils.ExecMockRunner.Calls[4].Exec)
assert.Equal(t, []string{"install", "--upgrade", "--root-user-action=ignore", "wheel"}, utils.ExecMockRunner.Calls[5].Params)
assert.Equal(t, filepath.Join("dummy", "bin", "pip"), utils.ExecMockRunner.Calls[5].Exec)
assert.Equal(t, []string{"-m", "build", "--no-isolation"}, utils.ExecMockRunner.Calls[6].Params)
assert.Equal(t, filepath.Join("dummy", "bin", "python"), utils.ExecMockRunner.Calls[6].Exec)
assert.Equal(t, []string{"install", "--upgrade", "--root-user-action=ignore", "cyclonedx-bom==6.1.1"}, utils.ExecMockRunner.Calls[7].Params)
assert.Equal(t, filepath.Join("dummy", "bin", "pip"), utils.ExecMockRunner.Calls[7].Exec)
assert.Equal(t, []string{"env", "--output-file", "bom-pip.xml", "--output-format", "XML", "--spec-version", "1.4"}, utils.ExecMockRunner.Calls[8].Params)
assert.Equal(t, filepath.Join("dummy", "bin", "cyclonedx-py"), utils.ExecMockRunner.Calls[8].Exec)
})
}
107 changes: 107 additions & 0 deletions pkg/versioning/toml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package versioning

import (
"fmt"
"strings"

"github.com/BurntSushi/toml"
)

const (
TomlBuildDescriptor = "pyproject.toml"
)

// Pip utility to interact with Python specific versioning
type Toml struct {
Pip
coordinates tomlCoordinates
}

type tomlCoordinates struct {
Project struct {
Name string `toml:"name"`
Version string `toml:"version"`
} `toml:"project"`
}

func (p *Toml) init() error {
var coordinates tomlCoordinates

if !strings.Contains(p.Pip.path, TomlBuildDescriptor) {
return fmt.Errorf("file '%v' is not a %s", p.Pip.path, TomlBuildDescriptor)
}

if err := p.Pip.init(); err != nil {
return err
}

if _, err := toml.Decode(p.Pip.buildDescriptorContent, &coordinates); err != nil {
return err
}
p.coordinates = coordinates
return nil
}

// GetName returns the name from the build descriptor
func (p *Toml) GetName() (string, error) {
if err := p.init(); err != nil {
return "", fmt.Errorf("failed to read file '%v': %w", p.Pip.path, err)
}
if len(p.coordinates.Project.Name) == 0 {
return "", fmt.Errorf("no name information found in file '%v'", p.Pip.path)
}
return p.coordinates.Project.Name, nil
}

// // GetVersion returns the current version from the build descriptor
func (p *Toml) GetVersion() (string, error) {
if err := p.init(); err != nil {
return "", fmt.Errorf("failed to read file '%v': %w", p.Pip.path, err)
}
if len(p.coordinates.Project.Version) == 0 {
return "", fmt.Errorf("no version information found in file '%v'", p.Pip.path)
}
return p.coordinates.Project.Version, nil
}
Copy link
Member

@maxcask maxcask Oct 1, 2025

Choose a reason for hiding this comment

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

To parse TOML it's better to use BurntSushi/toml, less fragile and provides robust parsing:

import (
    "github.com/BurntSushi/toml"
)

type pyproject struct {
    Project struct {
        Name    string `toml:"name"`
        Version string `toml:"version"`
    } `toml:"project"`
}

func (p *Toml) parsePyproject() (*pyproject, error) {
    var py pyproject
    if _, err := toml.Decode(p.Pip.buildDescriptorContent, &py); err != nil {
        return nil, err
    }
    return &py, nil
}

func (p *Toml) GetName() (string, error) {
    py, err := p.parsePyproject()
    if err != nil {
        return "", err
    }
    if py.Project.Name == "" {
        return "", fmt.Errorf("no name information found in file '%v'", p.Pip.path)
    }
    return py.Project.Name, nil
}

func (p *Toml) GetVersion() (string, error) {
    py, err := p.parsePyproject()
    if err != nil {
        return "", err
    }
    if py.Project.Version == "" {
        return "", fmt.Errorf("no version information found in file '%v'", p.Pip.path)
    }
    return py.Project.Version, nil
}


// SetVersion updates the version in the build descriptor
func (p *Toml) SetVersion(new string) error {
if current, err := p.GetVersion(); err != nil {
return err
} else {
// replace with single quotes
p.Pip.buildDescriptorContent = strings.ReplaceAll(
p.Pip.buildDescriptorContent,
fmt.Sprintf("version = '%v'", current),
fmt.Sprintf("version = '%v'", new))
// replace with double quotes as well
p.Pip.buildDescriptorContent = strings.ReplaceAll(
p.Pip.buildDescriptorContent,
fmt.Sprintf("version = \"%v\"", current),
fmt.Sprintf("version = \"%v\"", new))
err = p.Pip.writeFile(p.Pip.path, []byte(p.Pip.buildDescriptorContent), 0600)
if err != nil {
return fmt.Errorf("failed to write file '%v': %w", p.Pip.path, err)
}
return nil
}
}

// GetCoordinates returns the build descriptor coordinates
func (p *Toml) GetCoordinates() (Coordinates, error) {
result := Coordinates{}
// get name
if name, err := p.GetName(); err != nil {
return result, fmt.Errorf("failed to retrieve coordinates: %w", err)
} else {
result.ArtifactID = name
}
// get version
if version, err := p.GetVersion(); err != nil {
return result, fmt.Errorf("failed to retrieve coordinates: %w", err)
} else {
result.Version = version
}

return result, nil
}
Loading
Loading