From 87cae7f2277f340ae973c7faf27efb0c1a4d86ff Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 4 Jun 2025 10:52:34 +0200 Subject: [PATCH 01/18] when collecting libraries, convert them to absolute path --- bundle/libraries/upload.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bundle/libraries/upload.go b/bundle/libraries/upload.go index d38c7a5a043..a52c366c72a 100644 --- a/bundle/libraries/upload.go +++ b/bundle/libraries/upload.go @@ -118,6 +118,10 @@ func collectLocalLibraries(b *bundle.Bundle) (map[string][]configLocation, error } } + // Need to convert relative path to absolute so that paths collected above match paths collected here + // artifacts.*.files[*].source are relative to bundle root, not sync root AFAIK + source = filepath.Join(b.BundleRootPath, source) + libs[source] = append(libs[source], configLocation{ configPath: p.Append(dyn.Key("remote_path")), location: v.Location(), From d6b012accb61148dbc7b4007676602f416b015cb Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 4 Jun 2025 11:11:08 +0200 Subject: [PATCH 02/18] only join is not abs already --- bundle/libraries/upload.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bundle/libraries/upload.go b/bundle/libraries/upload.go index a52c366c72a..65fa1d2e71d 100644 --- a/bundle/libraries/upload.go +++ b/bundle/libraries/upload.go @@ -73,7 +73,10 @@ func collectLocalLibraries(b *bundle.Bundle) (map[string][]configLocation, error return v, nil } - source = filepath.Join(b.SyncRootPath, source) + if !filepath.IsAbs(source) { + source = filepath.Join(b.SyncRootPath, source) + } + libs[source] = append(libs[source], configLocation{ configPath: p, location: v.Location(), @@ -120,7 +123,9 @@ func collectLocalLibraries(b *bundle.Bundle) (map[string][]configLocation, error // Need to convert relative path to absolute so that paths collected above match paths collected here // artifacts.*.files[*].source are relative to bundle root, not sync root AFAIK - source = filepath.Join(b.BundleRootPath, source) + if !filepath.IsAbs(source) { + source = filepath.Join(b.BundleRootPath, source) + } libs[source] = append(libs[source], configLocation{ configPath: p.Append(dyn.Key("remote_path")), From 138190ab7153217581560427511a6f714c944f05 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 3 Jun 2025 15:52:11 +0200 Subject: [PATCH 03/18] add filter + version --- libs/patchwheel/filter.go | 57 ++++++++++++++++++++++++ libs/patchwheel/filter_test.go | 47 ++++++++++++++++++++ libs/patchwheel/version.go | 79 +++++++++++++++++++++++++++++++++ libs/patchwheel/version_test.go | 37 +++++++++++++++ 4 files changed, 220 insertions(+) create mode 100644 libs/patchwheel/filter.go create mode 100644 libs/patchwheel/filter_test.go create mode 100644 libs/patchwheel/version.go create mode 100644 libs/patchwheel/version_test.go diff --git a/libs/patchwheel/filter.go b/libs/patchwheel/filter.go new file mode 100644 index 00000000000..f792b14ef33 --- /dev/null +++ b/libs/patchwheel/filter.go @@ -0,0 +1,57 @@ +package patchwheel + +import ( + "context" + + "github.com/databricks/cli/libs/log" +) + +// FilterLatestWheels iterates over provided wheel file paths, groups them by distribution name +// and, for every group, keeps only the wheel that has the latest version according to a best-effort +// comparison of the version strings. Returned slice preserves the order of the input slice – the +// first occurrence of the chosen wheel for every distribution is retained. +// +// The comparison is *heuristic*: the algorithm tokenises version strings into alternating numeric +// and non-numeric chunks. Numeric chunks are compared as integers, while non-numeric chunks are +// compared lexicographically. This covers common cases such as "1.2.10" > "1.2.3" and timestamps +// added via calculateNewVersion (e.g. "1.2.3+1741091696…" > "1.2.3"). It does not attempt to +// implement the full PEP 440 specification, which is unnecessary for the dynamic versions +// produced by this package. +func FilterLatestWheels(ctx context.Context, paths []string) []string { + // Build output incrementally, preserving the order of the *chosen* wheels. + out := make([]string, 0, len(paths)) + + // distribution -> index in out slice + bestIdx := make(map[string]int) + + for _, p := range paths { + info, err := ParseWheelFilename(p) + if err != nil { + // Unparsable: always keep. + out = append(out, p) + continue + } + + if idx, seen := bestIdx[info.Distribution]; !seen { + // First wheel for this distribution. + bestIdx[info.Distribution] = len(out) + out = append(out, p) + continue + } else { + // Compare against the current winner. + winnerPath := out[idx] + winnerInfo, _ := ParseWheelFilename(winnerPath) // guaranteed parseable + + if compareVersion(info.Version, winnerInfo.Version) > 0 { + // Current wheel wins: replace earlier entry in-place. + log.Debugf(ctx, "Skipping wheel %s (older than %s)", winnerPath, p) + out[idx] = p + } else { + // Current wheel loses. + log.Debugf(ctx, "Skipping wheel %s (older than %s)", p, winnerPath) + } + } + } + + return out +} diff --git a/libs/patchwheel/filter_test.go b/libs/patchwheel/filter_test.go new file mode 100644 index 00000000000..0f88b3ecf6a --- /dev/null +++ b/libs/patchwheel/filter_test.go @@ -0,0 +1,47 @@ +package patchwheel + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFilterLatestWheels(t *testing.T) { + paths := []string{ + "mypkg-0.1.0-py3-none-any.whl", + "mypkg-0.2.0-py3-none-any.whl", + "other-1.0.0-py3-none-any.whl", + "other-0.9.0-py3-none-any.whl", + } + + filtered := FilterLatestWheels(context.Background(), paths) + require.ElementsMatch(t, []string{ + "mypkg-0.2.0-py3-none-any.whl", + "other-1.0.0-py3-none-any.whl", + }, filtered) +} + +func TestFilterLatestWheelsWithTimestamp(t *testing.T) { + // Second package has timestamp suffix which should win over plain version. + paths := []string{ + "mypkg-1.2.3-py3-none-any.whl", + "mypkg-1.2.3+1741091696780123321-py3-none-any.whl", + } + filtered := FilterLatestWheels(context.Background(), paths) + require.Equal(t, []string{"mypkg-1.2.3+1741091696780123321-py3-none-any.whl"}, filtered) +} + +func TestFilterLatestWheelsKeepsUnparsable(t *testing.T) { + paths := []string{ + "not-a-wheel.txt", + "mypkg-0.1.0-py3-none-any.whl", + "mypkg-0.2.0-py3-none-any.whl", + } + + filtered := FilterLatestWheels(context.Background(), paths) + require.Equal(t, []string{ + "not-a-wheel.txt", + "mypkg-0.2.0-py3-none-any.whl", + }, filtered) +} diff --git a/libs/patchwheel/version.go b/libs/patchwheel/version.go new file mode 100644 index 00000000000..af5b49bf58d --- /dev/null +++ b/libs/patchwheel/version.go @@ -0,0 +1,79 @@ +package patchwheel + +import ( + "strconv" + "unicode" +) + +// compareVersion compares version strings a and b. +// Returns: +// +// 1 if a > b +// -1 if a < b +// 0 if equal. +// +// The algorithm splits each string into consecutive numeric and non-numeric tokens and compares them +// pair-wise: +// - Numeric tokens are compared as integers. +// - Non-numeric tokens are compared lexicographically. +// - Numeric tokens are considered greater than non-numeric tokens when types differ. +// +// Missing tokens are treated as zero-length strings / 0. +func compareVersion(a, b string) int { + ta := tokenizeVersion(a) + tb := tokenizeVersion(b) + + minLen := min(len(ta), len(tb)) + + for i, tokA := range ta[:minLen] { + tokB := tb[i] + + if tokA.numeric && tokB.numeric { + intA, _ := strconv.Atoi(tokA.value) + intB, _ := strconv.Atoi(tokB.value) + if intA != intB { + if intA > intB { + return 1 + } + return -1 + } + continue + } + + if tokA.value != tokB.value { + if tokA.value > tokB.value { + return 1 + } + return -1 + } + } + + // All shared tokens are equal; the longer version wins if it has extra tokens. + if len(ta) > len(tb) { + return 1 + } + if len(tb) > len(ta) { + return -1 + } + return 0 +} + +type versionToken struct { + numeric bool + value string +} + +func tokenizeVersion(v string) []versionToken { + var tokens []versionToken + start := 0 + for start < len(v) { + isDigit := unicode.IsDigit(rune(v[start])) + end := start + for end < len(v) && unicode.IsDigit(rune(v[end])) == isDigit { + end++ + } + tokens = append(tokens, versionToken{numeric: isDigit, value: v[start:end]}) + start = end + } + return tokens +} diff --git a/libs/patchwheel/version_test.go b/libs/patchwheel/version_test.go new file mode 100644 index 00000000000..f3abc02c1f4 --- /dev/null +++ b/libs/patchwheel/version_test.go @@ -0,0 +1,37 @@ +package patchwheel + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCompareVersion(t *testing.T) { + cases := []struct { + a, b string + expect int + }{ + {"1.2.10", "1.2.3", 1}, + {"1.2.3", "1.2.3", 0}, + {"1.2.3+2", "1.2.3", 1}, + {"10.0.0", "2.0.0", 1}, + {"1.2.3a", "1.2.3", 1}, // non-numeric suffix greater lexicographically + {"1.2.3.1", "1.2.3", 1}, // leftover tokens make version greater + {"1.2.3", "1.2.3.0", -1}, + } + + for _, c := range cases { + tc := c // capture + t.Run(fmt.Sprintf("%s_vs_%s", tc.a, tc.b), func(t *testing.T) { + got := compareVersion(tc.a, tc.b) + require.Equal(t, tc.expect, got) + }) + + // Mirror case + t.Run(fmt.Sprintf("%s_vs_%s_mirror", tc.b, tc.a), func(t *testing.T) { + got := compareVersion(tc.b, tc.a) + require.Equal(t, -tc.expect, got) + }) + } +} From 6f48b6b12a365d18a03aec79f9ce902d4b0ba741 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 3 Jun 2025 17:55:45 +0200 Subject: [PATCH 04/18] enable filtering; disable pythin clean ups add acc test; ignore previously deleted stuff when syncing --- .../artifacts/whl_change_version/.gitignore | 3 + .../whl_change_version/databricks.yml | 12 +++ .../my_test_code/__init__.py | 2 + .../my_test_code/__main__.py | 16 ++++ .../artifacts/whl_change_version/output.txt | 75 +++++++++++++++++++ .../artifacts/whl_change_version/script | 30 ++++++++ .../artifacts/whl_change_version/setup.py | 15 ++++ .../bundle/artifacts/whl_dynamic/output.txt | 2 - .../bundle/artifacts/whl_explicit/output.txt | 1 - .../bundle/artifacts/whl_implicit/output.txt | 1 - .../whl_implicit_notebook/output.txt | 1 - .../bundle/artifacts/whl_multiple/output.txt | 2 - .../whl_prebuilt_multiple/output.txt | 2 - .../whl_via_environment_key/output.txt | 1 - bundle/artifacts/build.go | 2 - bundle/artifacts/cleanup.go | 38 ---------- bundle/artifacts/expand_globs.go | 7 ++ .../expand_pipeline_glob_paths.go | 3 + bundle/deploy/files/sync.go | 6 +- bundle/libraries/expand_glob_references.go | 17 +++-- libs/python/utils.go | 13 ---- 21 files changed, 178 insertions(+), 71 deletions(-) create mode 100644 acceptance/bundle/artifacts/whl_change_version/.gitignore create mode 100644 acceptance/bundle/artifacts/whl_change_version/databricks.yml create mode 100644 acceptance/bundle/artifacts/whl_change_version/my_test_code/__init__.py create mode 100644 acceptance/bundle/artifacts/whl_change_version/my_test_code/__main__.py create mode 100644 acceptance/bundle/artifacts/whl_change_version/output.txt create mode 100644 acceptance/bundle/artifacts/whl_change_version/script create mode 100644 acceptance/bundle/artifacts/whl_change_version/setup.py delete mode 100644 bundle/artifacts/cleanup.go diff --git a/acceptance/bundle/artifacts/whl_change_version/.gitignore b/acceptance/bundle/artifacts/whl_change_version/.gitignore new file mode 100644 index 00000000000..f03e23bc260 --- /dev/null +++ b/acceptance/bundle/artifacts/whl_change_version/.gitignore @@ -0,0 +1,3 @@ +build/ +*.egg-info +.databricks diff --git a/acceptance/bundle/artifacts/whl_change_version/databricks.yml b/acceptance/bundle/artifacts/whl_change_version/databricks.yml new file mode 100644 index 00000000000..61019c57bbb --- /dev/null +++ b/acceptance/bundle/artifacts/whl_change_version/databricks.yml @@ -0,0 +1,12 @@ +resources: + jobs: + test_job: + name: "[${bundle.target}] My Wheel Job" + tasks: + - task_key: TestTask + existing_cluster_id: "0717-aaaaa-bbbbbb" + python_wheel_task: + package_name: "my_test_code" + entry_point: "run" + libraries: + - whl: ./dist/*.whl diff --git a/acceptance/bundle/artifacts/whl_change_version/my_test_code/__init__.py b/acceptance/bundle/artifacts/whl_change_version/my_test_code/__init__.py new file mode 100644 index 00000000000..e96953330ef --- /dev/null +++ b/acceptance/bundle/artifacts/whl_change_version/my_test_code/__init__.py @@ -0,0 +1,2 @@ +__version__ = "0.1.0" +__author__ = "Databricks" diff --git a/acceptance/bundle/artifacts/whl_change_version/my_test_code/__main__.py b/acceptance/bundle/artifacts/whl_change_version/my_test_code/__main__.py new file mode 100644 index 00000000000..ea918ce2d53 --- /dev/null +++ b/acceptance/bundle/artifacts/whl_change_version/my_test_code/__main__.py @@ -0,0 +1,16 @@ +""" +The entry point of the Python Wheel +""" + +import sys + + +def main(): + # This method will print the provided arguments + print("Hello from my func") + print("Got arguments:") + print(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/acceptance/bundle/artifacts/whl_change_version/output.txt b/acceptance/bundle/artifacts/whl_change_version/output.txt new file mode 100644 index 00000000000..4d43b493ca0 --- /dev/null +++ b/acceptance/bundle/artifacts/whl_change_version/output.txt @@ -0,0 +1,75 @@ + +>>> [CLI] bundle deploy +Building python_artifact... +Uploading dist/my_test_code-0.1.0-py3-none-any.whl... +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> find.py --expect 1 whl +dist/my_test_code-0.1.0-py3-none-any.whl + +=== Expecting 1 wheel in libraries section in /jobs/create +>>> jq -s .[] | select(.path=="/api/2.2/jobs/create") | .body.tasks out.requests.txt +[ + { + "existing_cluster_id": "0717-aaaaa-bbbbbb", + "libraries": [ + { + "whl": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.1.0-py3-none-any.whl" + } + ], + "python_wheel_task": { + "entry_point": "run", + "package_name": "my_test_code" + }, + "task_key": "TestTask" + } +] + +=== Expecting 1 wheel to be uploaded +>>> jq .path +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.1.0-py3-none-any.whl" + +=== Update glob to target 0.1.0 version + +>>> [CLI] bundle deploy +Building python_artifact... +Uploading dist/my_test_code-0.1.0-py3-none-any.whl... +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> find.py --expect 1 whl +dist/my_test_code-0.1.0-py3-none-any.whl + +=== Expecting 1 wheel in libraries section in /jobs/create +>>> jq -s .[] | select(.path=="/api/2.2/jobs/create") | .body.tasks out.requests.txt + +=== Expecting 1 wheel to be uploaded +>>> jq .path +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.1.0-py3-none-any.whl" + +=== Update wheel version +>>> [CLI] bundle deploy +Building python_artifact... +Uploading dist/my_test_code-0.1.0-py3-none-any.whl... +Uploading dist/my_test_code-0.2.0-py3-none-any.whl... +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> find.py --expect 2 whl +dist/my_test_code-0.1.0-py3-none-any.whl +dist/my_test_code-0.2.0-py3-none-any.whl + +=== Expecting 1 wheel in libraries section in /jobs/create +>>> jq -s .[] | select(.path=="/api/2.2/jobs/create") | .body.tasks out.requests.txt + +=== Expecting 1 wheel to be uploaded +>>> jq .path +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.1.0-py3-none-any.whl" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.2.0-py3-none-any.whl" diff --git a/acceptance/bundle/artifacts/whl_change_version/script b/acceptance/bundle/artifacts/whl_change_version/script new file mode 100644 index 00000000000..47a2435c2f4 --- /dev/null +++ b/acceptance/bundle/artifacts/whl_change_version/script @@ -0,0 +1,30 @@ +trace $CLI bundle deploy + +print_status() { + title "Expecting 1 wheel in libraries section in /jobs/create" + trace jq -s '.[] | select(.path=="/api/2.2/jobs/create") | .body.tasks' out.requests.txt + + title "Expecting 1 wheel to be uploaded" + trace jq .path < out.requests.txt | grep import | grep whl | sort + + rm out.requests.txt +} + +trace find.py --expect 1 whl +print_status + +title "Update glob to target 0.1.0 version\n" +update_file.py databricks.yml './dist/*.whl' './dist/my*0.1.0*.whl' + +trace $CLI bundle deploy + +trace find.py --expect 1 whl +print_status + +title "Update wheel version" +update_file.py my_test_code/__init__.py 0.1.0 0.2.0 + +trace $CLI bundle deploy + +trace find.py --expect 2 whl +print_status diff --git a/acceptance/bundle/artifacts/whl_change_version/setup.py b/acceptance/bundle/artifacts/whl_change_version/setup.py new file mode 100644 index 00000000000..7a1317b2f58 --- /dev/null +++ b/acceptance/bundle/artifacts/whl_change_version/setup.py @@ -0,0 +1,15 @@ +from setuptools import setup, find_packages + +import my_test_code + +setup( + name="my_test_code", + version=my_test_code.__version__, + author=my_test_code.__author__, + url="https://databricks.com", + author_email="john.doe@databricks.com", + description="my test wheel", + packages=find_packages(include=["my_test_code"]), + entry_points={"group_1": "run=my_test_code.__main__:main"}, + install_requires=["setuptools"], +) diff --git a/acceptance/bundle/artifacts/whl_dynamic/output.txt b/acceptance/bundle/artifacts/whl_dynamic/output.txt index bad3f6f7441..8dbe89a3426 100644 --- a/acceptance/bundle/artifacts/whl_dynamic/output.txt +++ b/acceptance/bundle/artifacts/whl_dynamic/output.txt @@ -99,7 +99,6 @@ my_test_code-0.0.1+[UNIX_TIME_NANOS].dist-info/RECORD >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1+[UNIX_TIME_NANOS]-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/other_test_code-0.0.1+[UNIX_TIME_NANOS]-py3-none-any.whl" -"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/dist/my_test_code-0.0.1-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/prebuilt/other_test_code-0.0.1-py3-none-any.whl" === Updating the local wheel and deploying again @@ -172,4 +171,3 @@ my_test_code-0.0.1+[UNIX_TIME_NANOS].dist-info/RECORD >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1+[UNIX_TIME_NANOS]-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/other_test_code-0.0.1+[UNIX_TIME_NANOS]-py3-none-any.whl" -"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/dist/my_test_code-0.0.1-py3-none-any.whl" diff --git a/acceptance/bundle/artifacts/whl_explicit/output.txt b/acceptance/bundle/artifacts/whl_explicit/output.txt index 6c1c06c46e0..c970d3fa52b 100644 --- a/acceptance/bundle/artifacts/whl_explicit/output.txt +++ b/acceptance/bundle/artifacts/whl_explicit/output.txt @@ -31,7 +31,6 @@ my_test_code/dist/my_test_code-0.0.1-py3-none-any.whl === Expecting 1 wheel to be uploaded >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1-py3-none-any.whl" -"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/dist/my_test_code-0.0.1-py3-none-any.whl" === Expecting delete request to artifact_path/.internal folder >>> jq -s .[] | select(.path=="/api/2.0/workspace/delete") | select(.body.path | test(".*/artifacts/.internal")) out.requests.txt diff --git a/acceptance/bundle/artifacts/whl_implicit/output.txt b/acceptance/bundle/artifacts/whl_implicit/output.txt index 698cc20c514..ed70f1c0b17 100644 --- a/acceptance/bundle/artifacts/whl_implicit/output.txt +++ b/acceptance/bundle/artifacts/whl_implicit/output.txt @@ -31,4 +31,3 @@ dist/my_test_code-0.0.1-py3-none-any.whl === Expecting 1 wheels to be uploaded >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1-py3-none-any.whl" -"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/dist/my_test_code-0.0.1-py3-none-any.whl" diff --git a/acceptance/bundle/artifacts/whl_implicit_notebook/output.txt b/acceptance/bundle/artifacts/whl_implicit_notebook/output.txt index c60adc515eb..b2f6c9a562a 100644 --- a/acceptance/bundle/artifacts/whl_implicit_notebook/output.txt +++ b/acceptance/bundle/artifacts/whl_implicit_notebook/output.txt @@ -30,4 +30,3 @@ dist/my_test_code-0.0.1-py3-none-any.whl === Expecting 1 wheel to be uploaded >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1-py3-none-any.whl" -"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/dist/my_test_code-0.0.1-py3-none-any.whl" diff --git a/acceptance/bundle/artifacts/whl_multiple/output.txt b/acceptance/bundle/artifacts/whl_multiple/output.txt index a03025d9103..4928f425ff8 100644 --- a/acceptance/bundle/artifacts/whl_multiple/output.txt +++ b/acceptance/bundle/artifacts/whl_multiple/output.txt @@ -38,5 +38,3 @@ my_test_code/dist/my_test_code_2-0.0.1-py3-none-any.whl >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code_2-0.0.1-py3-none-any.whl" -"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/dist/my_test_code-0.0.1-py3-none-any.whl" -"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/dist/my_test_code_2-0.0.1-py3-none-any.whl" diff --git a/acceptance/bundle/artifacts/whl_prebuilt_multiple/output.txt b/acceptance/bundle/artifacts/whl_prebuilt_multiple/output.txt index 4fe390e6350..0d9899b24c3 100644 --- a/acceptance/bundle/artifacts/whl_prebuilt_multiple/output.txt +++ b/acceptance/bundle/artifacts/whl_prebuilt_multiple/output.txt @@ -41,5 +41,3 @@ dist/my_test_code-0.0.1-py3-none-any.whl >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/other_test_code-0.0.1-py3-none-any.whl" -"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/dist/lib/other_test_code-0.0.1-py3-none-any.whl" -"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/dist/my_test_code-0.0.1-py3-none-any.whl" diff --git a/acceptance/bundle/artifacts/whl_via_environment_key/output.txt b/acceptance/bundle/artifacts/whl_via_environment_key/output.txt index 8afa59e7d8b..6b9734c4c58 100644 --- a/acceptance/bundle/artifacts/whl_via_environment_key/output.txt +++ b/acceptance/bundle/artifacts/whl_via_environment_key/output.txt @@ -51,4 +51,3 @@ my_test_code/dist/my_test_code-0.0.1-py3-none-any.whl === Expecting 1 wheel to be uploaded >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1-py3-none-any.whl" -"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/dist/my_test_code-0.0.1-py3-none-any.whl" diff --git a/bundle/artifacts/build.go b/bundle/artifacts/build.go index ff30e10fbd6..e062ee0b172 100644 --- a/bundle/artifacts/build.go +++ b/bundle/artifacts/build.go @@ -38,8 +38,6 @@ func (m *build) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { }) } - cleanupPythonDistBuild(ctx, b) - for _, artifactName := range utils.SortedKeys(b.Config.Artifacts) { a := b.Config.Artifacts[artifactName] diff --git a/bundle/artifacts/cleanup.go b/bundle/artifacts/cleanup.go deleted file mode 100644 index 9937178206c..00000000000 --- a/bundle/artifacts/cleanup.go +++ /dev/null @@ -1,38 +0,0 @@ -package artifacts - -import ( - "context" - "os" - "path/filepath" - - "github.com/databricks/cli/bundle" - "github.com/databricks/cli/libs/log" - "github.com/databricks/cli/libs/python" - "github.com/databricks/cli/libs/utils" -) - -func cleanupPythonDistBuild(ctx context.Context, b *bundle.Bundle) { - removeFolders := make(map[string]bool, len(b.Config.Artifacts)) - cleanupWheelFolders := make(map[string]bool, len(b.Config.Artifacts)) - - for _, artifactName := range utils.SortedKeys(b.Config.Artifacts) { - artifact := b.Config.Artifacts[artifactName] - if artifact.Type == "whl" && artifact.BuildCommand != "" { - dir := artifact.Path - removeFolders[filepath.Join(dir, "dist")] = true - cleanupWheelFolders[dir] = true - } - } - - for _, dir := range utils.SortedKeys(removeFolders) { - err := os.RemoveAll(dir) - if err != nil { - log.Infof(ctx, "Failed to remove %s: %s", dir, err) - } - } - - for _, dir := range utils.SortedKeys(cleanupWheelFolders) { - log.Infof(ctx, "Cleaning up Python build artifacts in %s", dir) - python.CleanupWheelFolder(dir) - } -} diff --git a/bundle/artifacts/expand_globs.go b/bundle/artifacts/expand_globs.go index 1d9e188a0cc..af358d3dda9 100644 --- a/bundle/artifacts/expand_globs.go +++ b/bundle/artifacts/expand_globs.go @@ -9,6 +9,7 @@ import ( "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/patchwheel" ) func createGlobError(v dyn.Value, p dyn.Path, message string) diag.Diagnostic { @@ -72,6 +73,12 @@ func (e expandGlobs) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnosti return v, nil } + // Note, we're applying this for all artifact types, not just "whl". + // Rationale: + // 1. type is optional + // 2. if you have wheels in other artifact type, maybe you still want the filter logic? impossible to say. + matches = patchwheel.FilterLatestWheels(ctx, matches) + if len(matches) == 1 && matches[0] == source { // No glob expansion was performed. // Keep node unchanged. We need to ensure that "patched" field remains and not wiped out by code below. diff --git a/bundle/config/mutator/resourcemutator/expand_pipeline_glob_paths.go b/bundle/config/mutator/resourcemutator/expand_pipeline_glob_paths.go index a89de3c0cc4..9716bea4138 100644 --- a/bundle/config/mutator/resourcemutator/expand_pipeline_glob_paths.go +++ b/bundle/config/mutator/resourcemutator/expand_pipeline_glob_paths.go @@ -9,6 +9,7 @@ import ( "github.com/databricks/cli/bundle/libraries" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/patchwheel" ) type expandPipelineGlobPaths struct{} @@ -47,6 +48,8 @@ func (m *expandPipelineGlobPaths) expandLibrary(dir string, v dyn.Value) ([]dyn. return []dyn.Value{v}, nil } + matches = patchwheel.FilterLatestWheels(context.Background(), matches) + // Emit a new value for each match. var ev []dyn.Value for _, match := range matches { diff --git a/bundle/deploy/files/sync.go b/bundle/deploy/files/sync.go index a3ead13a4e3..fe2b5599ab1 100644 --- a/bundle/deploy/files/sync.go +++ b/bundle/deploy/files/sync.go @@ -27,12 +27,16 @@ func GetSyncOptions(ctx context.Context, b *bundle.Bundle) (*sync.SyncOptions, e return nil, fmt.Errorf("cannot get list of sync includes: %w", err) } + // We used to delete __pycache__ and build and most of the dist, so now we're excluding it manually + // TODO: if users include those manually, then we should not exclude it? + excludes := append(b.Config.Sync.Exclude, "__pycache__", "build", "dist") + opts := &sync.SyncOptions{ WorktreeRoot: b.WorktreeRoot, LocalRoot: b.SyncRoot, Paths: b.Config.Sync.Paths, Include: includes, - Exclude: b.Config.Sync.Exclude, + Exclude: excludes, RemotePath: b.Config.Workspace.FilePath, Host: b.WorkspaceClient().Config.Host, diff --git a/bundle/libraries/expand_glob_references.go b/bundle/libraries/expand_glob_references.go index 7a808f62705..039157b086c 100644 --- a/bundle/libraries/expand_glob_references.go +++ b/bundle/libraries/expand_glob_references.go @@ -9,6 +9,7 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/patchwheel" ) type expand struct{} @@ -37,7 +38,7 @@ func getLibDetails(v dyn.Value) (string, string, bool) { return "", "", false } -func findMatches(b *bundle.Bundle, path string) ([]string, error) { +func findMatches(ctx context.Context, b *bundle.Bundle, path string) ([]string, error) { matches, err := filepath.Glob(filepath.Join(b.SyncRootPath, path)) if err != nil { return nil, err @@ -51,6 +52,8 @@ func findMatches(b *bundle.Bundle, path string) ([]string, error) { } } + matches = patchwheel.FilterLatestWheels(ctx, matches) + // We make the matched path relative to the sync root path before storing it // to allow upload mutator to distinguish between local and remote paths for i, match := range matches { @@ -69,7 +72,7 @@ func isGlobPattern(path string) bool { return strings.ContainsAny(path, "*?[") } -func expandLibraries(b *bundle.Bundle, p dyn.Path, v dyn.Value) (diag.Diagnostics, []dyn.Value) { +func expandLibraries(ctx context.Context, b *bundle.Bundle, p dyn.Path, v dyn.Value) (diag.Diagnostics, []dyn.Value) { var output []dyn.Value var diags diag.Diagnostics @@ -84,7 +87,7 @@ func expandLibraries(b *bundle.Bundle, p dyn.Path, v dyn.Value) (diag.Diagnostic lp = lp.Append(dyn.Key(libType)) - matches, err := findMatches(b, path) + matches, err := findMatches(ctx, b, path) if err != nil { diags = diags.Append(matchError(lp, lib.Locations(), err.Error())) continue @@ -100,7 +103,7 @@ func expandLibraries(b *bundle.Bundle, p dyn.Path, v dyn.Value) (diag.Diagnostic return diags, output } -func expandEnvironmentDeps(b *bundle.Bundle, p dyn.Path, v dyn.Value) (diag.Diagnostics, []dyn.Value) { +func expandEnvironmentDeps(ctx context.Context, b *bundle.Bundle, p dyn.Path, v dyn.Value) (diag.Diagnostics, []dyn.Value) { var output []dyn.Value var diags diag.Diagnostics @@ -113,7 +116,7 @@ func expandEnvironmentDeps(b *bundle.Bundle, p dyn.Path, v dyn.Value) (diag.Diag continue } - matches, err := findMatches(b, path) + matches, err := findMatches(ctx, b, path) if err != nil { diags = diags.Append(matchError(lp, dep.Locations(), err.Error())) continue @@ -129,7 +132,7 @@ func expandEnvironmentDeps(b *bundle.Bundle, p dyn.Path, v dyn.Value) (diag.Diag type expandPattern struct { pattern dyn.Pattern - fn func(b *bundle.Bundle, p dyn.Path, v dyn.Value) (diag.Diagnostics, []dyn.Value) + fn func(ctx context.Context, b *bundle.Bundle, p dyn.Path, v dyn.Value) (diag.Diagnostics, []dyn.Value) } var taskLibrariesPattern = dyn.NewPattern( @@ -184,7 +187,7 @@ func (e *expand) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { var err error for _, expander := range expanders { v, err = dyn.MapByPattern(v, expander.pattern, func(p dyn.Path, lv dyn.Value) (dyn.Value, error) { - d, output := expander.fn(b, p, lv) + d, output := expander.fn(ctx, b, p, lv) diags = diags.Extend(d) return dyn.V(output), nil }) diff --git a/libs/python/utils.go b/libs/python/utils.go index b94f5d4f65c..fb95db8bdcf 100644 --- a/libs/python/utils.go +++ b/libs/python/utils.go @@ -9,19 +9,6 @@ import ( "github.com/databricks/cli/libs/log" ) -func CleanupWheelFolder(dir string) { - // there or not there - we don't care - os.RemoveAll(filepath.Join(dir, "__pycache__")) - os.RemoveAll(filepath.Join(dir, "build")) - eggInfo := FindFilesWithSuffixInPath(dir, ".egg-info") - if len(eggInfo) == 0 { - return - } - for _, f := range eggInfo { - os.RemoveAll(f) - } -} - func FindFilesWithSuffixInPath(dir, suffix string) []string { f, err := os.Open(dir) if err != nil { From e35e71391002bd3b57d83ee094c3fffcd13474d0 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 3 Jun 2025 18:15:32 +0200 Subject: [PATCH 05/18] no Background --- .../resourcemutator/expand_pipeline_glob_paths.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bundle/config/mutator/resourcemutator/expand_pipeline_glob_paths.go b/bundle/config/mutator/resourcemutator/expand_pipeline_glob_paths.go index 9716bea4138..38f835793ea 100644 --- a/bundle/config/mutator/resourcemutator/expand_pipeline_glob_paths.go +++ b/bundle/config/mutator/resourcemutator/expand_pipeline_glob_paths.go @@ -18,7 +18,7 @@ func ExpandPipelineGlobPaths() bundle.Mutator { return &expandPipelineGlobPaths{} } -func (m *expandPipelineGlobPaths) expandLibrary(dir string, v dyn.Value) ([]dyn.Value, error) { +func (m *expandPipelineGlobPaths) expandLibrary(ctx context.Context, dir string, v dyn.Value) ([]dyn.Value, error) { // Probe for the path field in the library. for _, p := range []dyn.Path{ dyn.NewPath(dyn.Key("notebook"), dyn.Key("path")), @@ -48,7 +48,7 @@ func (m *expandPipelineGlobPaths) expandLibrary(dir string, v dyn.Value) ([]dyn. return []dyn.Value{v}, nil } - matches = patchwheel.FilterLatestWheels(context.Background(), matches) + matches = patchwheel.FilterLatestWheels(ctx, matches) // Emit a new value for each match. var ev []dyn.Value @@ -72,7 +72,7 @@ func (m *expandPipelineGlobPaths) expandLibrary(dir string, v dyn.Value) ([]dyn. return []dyn.Value{v}, nil } -func (m *expandPipelineGlobPaths) expandSequence(dir string, p dyn.Path, v dyn.Value) (dyn.Value, error) { +func (m *expandPipelineGlobPaths) expandSequence(ctx context.Context, dir string, p dyn.Path, v dyn.Value) (dyn.Value, error) { s, ok := v.AsSequence() if !ok { return dyn.InvalidValue, fmt.Errorf("expected sequence, got %s", v.Kind()) @@ -80,7 +80,7 @@ func (m *expandPipelineGlobPaths) expandSequence(dir string, p dyn.Path, v dyn.V var vs []dyn.Value for _, sv := range s { - v, err := m.expandLibrary(dir, sv) + v, err := m.expandLibrary(ctx, dir, sv) if err != nil { return dyn.InvalidValue, err } @@ -91,7 +91,7 @@ func (m *expandPipelineGlobPaths) expandSequence(dir string, p dyn.Path, v dyn.V return dyn.NewValue(vs, v.Locations()), nil } -func (m *expandPipelineGlobPaths) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics { +func (m *expandPipelineGlobPaths) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { p := dyn.NewPattern( dyn.Key("resources"), @@ -102,7 +102,7 @@ func (m *expandPipelineGlobPaths) Apply(_ context.Context, b *bundle.Bundle) dia // Visit each pipeline's "libraries" field and expand any glob patterns. return dyn.MapByPattern(v, p, func(path dyn.Path, value dyn.Value) (dyn.Value, error) { - return m.expandSequence(b.BundleRootPath, path, value) + return m.expandSequence(ctx, b.BundleRootPath, path, value) }) }) From e7f1f41152644c3f4084f392a5164be0f4c51d95 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 3 Jun 2025 18:19:37 +0200 Subject: [PATCH 06/18] clean up --- libs/patchwheel/version_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/patchwheel/version_test.go b/libs/patchwheel/version_test.go index f3abc02c1f4..11e73e0a3b4 100644 --- a/libs/patchwheel/version_test.go +++ b/libs/patchwheel/version_test.go @@ -21,8 +21,7 @@ func TestCompareVersion(t *testing.T) { {"1.2.3", "1.2.3.0", -1}, } - for _, c := range cases { - tc := c // capture + for _, tc := range cases { t.Run(fmt.Sprintf("%s_vs_%s", tc.a, tc.b), func(t *testing.T) { got := compareVersion(tc.a, tc.b) require.Equal(t, tc.expect, got) From 21370e491b37b9ac37623f7ba6242d290abe1473 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 3 Jun 2025 18:24:40 +0200 Subject: [PATCH 07/18] update test --- acceptance/bundle/artifacts/whl_no_cleanup/output.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/acceptance/bundle/artifacts/whl_no_cleanup/output.txt b/acceptance/bundle/artifacts/whl_no_cleanup/output.txt index a78810dbca9..4f2ee6f3efc 100644 --- a/acceptance/bundle/artifacts/whl_no_cleanup/output.txt +++ b/acceptance/bundle/artifacts/whl_no_cleanup/output.txt @@ -13,7 +13,6 @@ dist/my_test_code-0.0.1-py3-none-any.whl === Expecting 1 wheels to be uploaded >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1-py3-none-any.whl" -"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/dist/my_test_code-0.0.1-py3-none-any.whl" >>> [CLI] bundle deploy Building python_artifact... From 980a066ba2b3833503d9f4ed8776efa415748710 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 4 Jun 2025 09:59:46 +0200 Subject: [PATCH 08/18] clean up and extend tests --- libs/patchwheel/filter_test.go | 35 +++++++++++---------------------- libs/patchwheel/version_test.go | 1 + 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/libs/patchwheel/filter_test.go b/libs/patchwheel/filter_test.go index 0f88b3ecf6a..a3faa97969f 100644 --- a/libs/patchwheel/filter_test.go +++ b/libs/patchwheel/filter_test.go @@ -9,39 +9,26 @@ import ( func TestFilterLatestWheels(t *testing.T) { paths := []string{ + "project_name_bvs7tide6bhhpjy4dmcsb2qg44-0.0.1+20250604.74809-py3-none-any.whl", + "not-a-wheel.txt", "mypkg-0.1.0-py3-none-any.whl", "mypkg-0.2.0-py3-none-any.whl", "other-1.0.0-py3-none-any.whl", "other-0.9.0-py3-none-any.whl", - } - - filtered := FilterLatestWheels(context.Background(), paths) - require.ElementsMatch(t, []string{ - "mypkg-0.2.0-py3-none-any.whl", - "other-1.0.0-py3-none-any.whl", - }, filtered) -} - -func TestFilterLatestWheelsWithTimestamp(t *testing.T) { - // Second package has timestamp suffix which should win over plain version. - paths := []string{ - "mypkg-1.2.3-py3-none-any.whl", - "mypkg-1.2.3+1741091696780123321-py3-none-any.whl", - } - filtered := FilterLatestWheels(context.Background(), paths) - require.Equal(t, []string{"mypkg-1.2.3+1741091696780123321-py3-none-any.whl"}, filtered) -} + "project_name_bvs7tide6bhhpjy4dmcsb2qg44-0.0.1+20250604.74804-py3-none-any.whl", + "not-a-wheel.whl", -func TestFilterLatestWheelsKeepsUnparsable(t *testing.T) { - paths := []string{ - "not-a-wheel.txt", - "mypkg-0.1.0-py3-none-any.whl", - "mypkg-0.2.0-py3-none-any.whl", + "hello-1.2.3-py3-none-any.whl", + "hello-1.2.3+1741091696780123321-py3-none-any.whl", } filtered := FilterLatestWheels(context.Background(), paths) - require.Equal(t, []string{ + require.ElementsMatch(t, []string{ + "project_name_bvs7tide6bhhpjy4dmcsb2qg44-0.0.1+20250604.74809-py3-none-any.whl", "not-a-wheel.txt", "mypkg-0.2.0-py3-none-any.whl", + "other-1.0.0-py3-none-any.whl", + "not-a-wheel.whl", + "hello-1.2.3+1741091696780123321-py3-none-any.whl", }, filtered) } diff --git a/libs/patchwheel/version_test.go b/libs/patchwheel/version_test.go index 11e73e0a3b4..29300045555 100644 --- a/libs/patchwheel/version_test.go +++ b/libs/patchwheel/version_test.go @@ -19,6 +19,7 @@ func TestCompareVersion(t *testing.T) { {"1.2.3a", "1.2.3", 1}, // non-numeric suffix greater lexicographically {"1.2.3.1", "1.2.3", 1}, // leftover tokens make version greater {"1.2.3", "1.2.3.0", -1}, + {"0.0.1+20250604.74804", "0.0.1+20250604.74809", -1}, } for _, tc := range cases { From 2e4445efc8384ab2e5afc2458767499f34ee172e Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 4 Jun 2025 11:15:17 +0200 Subject: [PATCH 09/18] fix --- bundle/artifacts/prepare.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bundle/artifacts/prepare.go b/bundle/artifacts/prepare.go index 3bca30a8053..be0165e352d 100644 --- a/bundle/artifacts/prepare.go +++ b/bundle/artifacts/prepare.go @@ -52,6 +52,11 @@ func (m *prepare) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics if artifact.Type == "whl" { if artifact.BuildCommand == "" && len(artifact.Files) == 0 { artifact.BuildCommand = python.GetExecutable() + " setup.py bdist_wheel" + artifact.Files = []config.ArtifactFile{ + { + Source: "dist/*.whl", + }, + } } // Wheel builds write to `./dist`. Pick up all wheel files by default if nothing is specified. From 8b24696eaed48a5e4b311cb8689cbc1485ce01b7 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 5 Jun 2025 10:43:02 +0200 Subject: [PATCH 10/18] clean up & rebase --- bundle/artifacts/prepare.go | 5 ----- libs/python/utils.go | 35 ----------------------------------- libs/python/utils_test.go | 21 --------------------- 3 files changed, 61 deletions(-) delete mode 100644 libs/python/utils.go delete mode 100644 libs/python/utils_test.go diff --git a/bundle/artifacts/prepare.go b/bundle/artifacts/prepare.go index be0165e352d..3bca30a8053 100644 --- a/bundle/artifacts/prepare.go +++ b/bundle/artifacts/prepare.go @@ -52,11 +52,6 @@ func (m *prepare) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics if artifact.Type == "whl" { if artifact.BuildCommand == "" && len(artifact.Files) == 0 { artifact.BuildCommand = python.GetExecutable() + " setup.py bdist_wheel" - artifact.Files = []config.ArtifactFile{ - { - Source: "dist/*.whl", - }, - } } // Wheel builds write to `./dist`. Pick up all wheel files by default if nothing is specified. diff --git a/libs/python/utils.go b/libs/python/utils.go deleted file mode 100644 index fb95db8bdcf..00000000000 --- a/libs/python/utils.go +++ /dev/null @@ -1,35 +0,0 @@ -package python - -import ( - "context" - "os" - "path/filepath" - "strings" - - "github.com/databricks/cli/libs/log" -) - -func FindFilesWithSuffixInPath(dir, suffix string) []string { - f, err := os.Open(dir) - if err != nil { - log.Debugf(context.Background(), "open dir %s: %s", dir, err) - return nil - } - defer f.Close() - - entries, err := f.ReadDir(0) - if err != nil { - log.Debugf(context.Background(), "read dir %s: %s", dir, err) - // todo: log - return nil - } - - var files []string - for _, child := range entries { - if !strings.HasSuffix(child.Name(), suffix) { - continue - } - files = append(files, filepath.Join(dir, child.Name())) - } - return files -} diff --git a/libs/python/utils_test.go b/libs/python/utils_test.go deleted file mode 100644 index 1656d1ecb4d..00000000000 --- a/libs/python/utils_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package python - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestFindFilesWithSuffixInPath(t *testing.T) { - dir, err := os.Getwd() - require.NoError(t, err) - - files := FindFilesWithSuffixInPath(dir, "test.go") - - matches, err := filepath.Glob(filepath.Join(dir, "*test.go")) - require.NoError(t, err) - - require.ElementsMatch(t, files, matches) -} From a7f36bf63d7a31bf85bfadae1c0c2900a6e2929b Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 5 Jun 2025 11:24:20 +0200 Subject: [PATCH 11/18] rewrite test --- .../artifacts/whl_change_version/output.txt | 84 ++++++++++++++++--- .../artifacts/whl_change_version/script | 43 ++++++---- 2 files changed, 98 insertions(+), 29 deletions(-) diff --git a/acceptance/bundle/artifacts/whl_change_version/output.txt b/acceptance/bundle/artifacts/whl_change_version/output.txt index 4d43b493ca0..e57555f6538 100644 --- a/acceptance/bundle/artifacts/whl_change_version/output.txt +++ b/acceptance/bundle/artifacts/whl_change_version/output.txt @@ -29,30 +29,68 @@ dist/my_test_code-0.1.0-py3-none-any.whl ] === Expecting 1 wheel to be uploaded ->>> jq .path +>>> jq .path out.requests.txt "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.1.0-py3-none-any.whl" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/.gitignore" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/databricks.yml" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/__init__.py" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/__main__.py" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/out.requests.txt" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/output.txt" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/repls.json" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/script" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/setup.py" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deployment.json" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/metadata.json" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/terraform.tfstate" -=== Update glob to target 0.1.0 version +>>> update_file.py my_test_code/__init__.py 0.1.0 0.2.0 >>> [CLI] bundle deploy Building python_artifact... -Uploading dist/my_test_code-0.1.0-py3-none-any.whl... +Uploading dist/my_test_code-0.2.0-py3-none-any.whl... Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Deploying resources... Updating deployment state... Deployment complete! ->>> find.py --expect 1 whl +>>> find.py --expect 2 whl dist/my_test_code-0.1.0-py3-none-any.whl +dist/my_test_code-0.2.0-py3-none-any.whl -=== Expecting 1 wheel in libraries section in /jobs/create ->>> jq -s .[] | select(.path=="/api/2.2/jobs/create") | .body.tasks out.requests.txt +=== Expecting 1 wheel in libraries section in /jobs/reset +>>> jq -s .[] | select(.path=="/api/2.2/jobs/reset") | .body.new_settings.tasks out.requests.txt +[ + { + "existing_cluster_id": "0717-aaaaa-bbbbbb", + "libraries": [ + { + "whl": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.2.0-py3-none-any.whl" + } + ], + "python_wheel_task": { + "entry_point": "run", + "package_name": "my_test_code" + }, + "task_key": "TestTask" + } +] === Expecting 1 wheel to be uploaded ->>> jq .path -"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.1.0-py3-none-any.whl" +>>> jq .path out.requests.txt +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.2.0-py3-none-any.whl" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/__init__.py" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/out.requests.txt" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/output.txt" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deployment.json" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/metadata.json" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/terraform.tfstate" + +=== Restore config to target old wheel +>>> update_file.py databricks.yml ./dist/*.whl ./dist/my*0.1.0*.whl -=== Update wheel version >>> [CLI] bundle deploy Building python_artifact... Uploading dist/my_test_code-0.1.0-py3-none-any.whl... @@ -66,10 +104,32 @@ Deployment complete! dist/my_test_code-0.1.0-py3-none-any.whl dist/my_test_code-0.2.0-py3-none-any.whl -=== Expecting 1 wheel in libraries section in /jobs/create ->>> jq -s .[] | select(.path=="/api/2.2/jobs/create") | .body.tasks out.requests.txt +=== Expecting 1 wheel in libraries section in /jobs/reset +>>> jq -s .[] | select(.path=="/api/2.2/jobs/reset") | .body.new_settings.tasks out.requests.txt +[ + { + "existing_cluster_id": "0717-aaaaa-bbbbbb", + "libraries": [ + { + "whl": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.1.0-py3-none-any.whl" + } + ], + "python_wheel_task": { + "entry_point": "run", + "package_name": "my_test_code" + }, + "task_key": "TestTask" + } +] === Expecting 1 wheel to be uploaded ->>> jq .path +>>> jq .path out.requests.txt "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.1.0-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.2.0-py3-none-any.whl" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/databricks.yml" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/out.requests.txt" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/output.txt" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deployment.json" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/metadata.json" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/terraform.tfstate" diff --git a/acceptance/bundle/artifacts/whl_change_version/script b/acceptance/bundle/artifacts/whl_change_version/script index 47a2435c2f4..2ccc1043078 100644 --- a/acceptance/bundle/artifacts/whl_change_version/script +++ b/acceptance/bundle/artifacts/whl_change_version/script @@ -1,30 +1,39 @@ trace $CLI bundle deploy -print_status() { - title "Expecting 1 wheel in libraries section in /jobs/create" - trace jq -s '.[] | select(.path=="/api/2.2/jobs/create") | .body.tasks' out.requests.txt +trace find.py --expect 1 whl - title "Expecting 1 wheel to be uploaded" - trace jq .path < out.requests.txt | grep import | grep whl | sort +title "Expecting 1 wheel in libraries section in /jobs/create" +trace jq -s '.[] | select(.path=="/api/2.2/jobs/create") | .body.tasks' out.requests.txt - rm out.requests.txt -} +title "Expecting 1 wheel to be uploaded" +trace jq .path out.requests.txt | grep import | sort -trace find.py --expect 1 whl -print_status +rm out.requests.txt -title "Update glob to target 0.1.0 version\n" -update_file.py databricks.yml './dist/*.whl' './dist/my*0.1.0*.whl' +trace update_file.py my_test_code/__init__.py 0.1.0 0.2.0 trace $CLI bundle deploy -trace find.py --expect 1 whl -print_status +trace find.py --expect 2 whl # there are now 2 wheels on disk -title "Update wheel version" -update_file.py my_test_code/__init__.py 0.1.0 0.2.0 +title "Expecting 1 wheel in libraries section in /jobs/reset" +trace jq -s '.[] | select(.path=="/api/2.2/jobs/reset") | .body.new_settings.tasks' out.requests.txt -trace $CLI bundle deploy +title "Expecting 1 wheel to be uploaded" +trace jq .path out.requests.txt | grep import | sort + +rm out.requests.txt + +title 'Restore config to target old wheel' +trace update_file.py databricks.yml './dist/*.whl' './dist/my*0.1.0*.whl' +trace $CLI bundle deploy trace find.py --expect 2 whl -print_status + +title "Expecting 1 wheel in libraries section in /jobs/reset" +trace jq -s '.[] | select(.path=="/api/2.2/jobs/reset") | .body.new_settings.tasks' out.requests.txt + +title "Expecting 1 wheel to be uploaded" +trace jq .path out.requests.txt | grep import | sort + +rm out.requests.txt From 2cc0910b231f7b51cbfd3aafa954cf46f4c02bcd Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 5 Jun 2025 11:39:52 +0200 Subject: [PATCH 12/18] stricter defaultExcludes --- .../bundle/artifacts/whl_dynamic/output.txt | 2 ++ .../bundle/artifacts/whl_explicit/output.txt | 1 + .../bundle/artifacts/whl_multiple/output.txt | 2 ++ .../whl_via_environment_key/output.txt | 1 + bundle/deploy/files/sync.go | 23 ++++++++++++++++++- 5 files changed, 28 insertions(+), 1 deletion(-) diff --git a/acceptance/bundle/artifacts/whl_dynamic/output.txt b/acceptance/bundle/artifacts/whl_dynamic/output.txt index 8dbe89a3426..bad3f6f7441 100644 --- a/acceptance/bundle/artifacts/whl_dynamic/output.txt +++ b/acceptance/bundle/artifacts/whl_dynamic/output.txt @@ -99,6 +99,7 @@ my_test_code-0.0.1+[UNIX_TIME_NANOS].dist-info/RECORD >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1+[UNIX_TIME_NANOS]-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/other_test_code-0.0.1+[UNIX_TIME_NANOS]-py3-none-any.whl" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/dist/my_test_code-0.0.1-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/prebuilt/other_test_code-0.0.1-py3-none-any.whl" === Updating the local wheel and deploying again @@ -171,3 +172,4 @@ my_test_code-0.0.1+[UNIX_TIME_NANOS].dist-info/RECORD >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1+[UNIX_TIME_NANOS]-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/other_test_code-0.0.1+[UNIX_TIME_NANOS]-py3-none-any.whl" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/dist/my_test_code-0.0.1-py3-none-any.whl" diff --git a/acceptance/bundle/artifacts/whl_explicit/output.txt b/acceptance/bundle/artifacts/whl_explicit/output.txt index c970d3fa52b..6c1c06c46e0 100644 --- a/acceptance/bundle/artifacts/whl_explicit/output.txt +++ b/acceptance/bundle/artifacts/whl_explicit/output.txt @@ -31,6 +31,7 @@ my_test_code/dist/my_test_code-0.0.1-py3-none-any.whl === Expecting 1 wheel to be uploaded >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1-py3-none-any.whl" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/dist/my_test_code-0.0.1-py3-none-any.whl" === Expecting delete request to artifact_path/.internal folder >>> jq -s .[] | select(.path=="/api/2.0/workspace/delete") | select(.body.path | test(".*/artifacts/.internal")) out.requests.txt diff --git a/acceptance/bundle/artifacts/whl_multiple/output.txt b/acceptance/bundle/artifacts/whl_multiple/output.txt index 4928f425ff8..a03025d9103 100644 --- a/acceptance/bundle/artifacts/whl_multiple/output.txt +++ b/acceptance/bundle/artifacts/whl_multiple/output.txt @@ -38,3 +38,5 @@ my_test_code/dist/my_test_code_2-0.0.1-py3-none-any.whl >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code_2-0.0.1-py3-none-any.whl" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/dist/my_test_code-0.0.1-py3-none-any.whl" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/dist/my_test_code_2-0.0.1-py3-none-any.whl" diff --git a/acceptance/bundle/artifacts/whl_via_environment_key/output.txt b/acceptance/bundle/artifacts/whl_via_environment_key/output.txt index 6b9734c4c58..8afa59e7d8b 100644 --- a/acceptance/bundle/artifacts/whl_via_environment_key/output.txt +++ b/acceptance/bundle/artifacts/whl_via_environment_key/output.txt @@ -51,3 +51,4 @@ my_test_code/dist/my_test_code-0.0.1-py3-none-any.whl === Expecting 1 wheel to be uploaded >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1-py3-none-any.whl" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/dist/my_test_code-0.0.1-py3-none-any.whl" diff --git a/bundle/deploy/files/sync.go b/bundle/deploy/files/sync.go index fe2b5599ab1..876039cb037 100644 --- a/bundle/deploy/files/sync.go +++ b/bundle/deploy/files/sync.go @@ -3,6 +3,7 @@ package files import ( "context" "fmt" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/sync" @@ -16,6 +17,12 @@ func GetSync(ctx context.Context, b *bundle.Bundle) (*sync.Sync, error) { return sync.New(ctx, *opts) } +var defaultExcludes = []string{ + "__pycache__/", + "^build/", + "^dist/", +} + func GetSyncOptions(ctx context.Context, b *bundle.Bundle) (*sync.SyncOptions, error) { cacheDir, err := b.CacheDir(ctx) if err != nil { @@ -28,8 +35,22 @@ func GetSyncOptions(ctx context.Context, b *bundle.Bundle) (*sync.SyncOptions, e } // We used to delete __pycache__ and build and most of the dist, so now we're excluding it manually + + var excludes []string + + for _, defaultExclude := range defaultExcludes { + if slices.Contains(includes, defaultExclude) { + continue + } + if slices.Contains(b.Config.Sync.Exclude, defaultExclude) { + continue + } + excludes = append(excludes, defaultExclude) + } + + excludes = append(b.Config.Sync.Exclude, excludes...) + // TODO: if users include those manually, then we should not exclude it? - excludes := append(b.Config.Sync.Exclude, "__pycache__", "build", "dist") opts := &sync.SyncOptions{ WorktreeRoot: b.WorktreeRoot, From fd214ffa22d680d9fa3df546bf126b695ab725c3 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 5 Jun 2025 11:43:44 +0200 Subject: [PATCH 13/18] remove default sync excludes --- .../artifacts/whl_change_version/output.txt | 3 +++ .../bundle/artifacts/whl_implicit/output.txt | 1 + .../whl_implicit_notebook/output.txt | 1 + .../artifacts/whl_no_cleanup/output.txt | 1 + .../whl_prebuilt_multiple/output.txt | 2 ++ bundle/deploy/files/sync.go | 27 +------------------ 6 files changed, 9 insertions(+), 26 deletions(-) diff --git a/acceptance/bundle/artifacts/whl_change_version/output.txt b/acceptance/bundle/artifacts/whl_change_version/output.txt index e57555f6538..a2bfb797586 100644 --- a/acceptance/bundle/artifacts/whl_change_version/output.txt +++ b/acceptance/bundle/artifacts/whl_change_version/output.txt @@ -33,6 +33,7 @@ dist/my_test_code-0.1.0-py3-none-any.whl "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.1.0-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/.gitignore" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/databricks.yml" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/dist/my_test_code-0.1.0-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/__init__.py" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/__main__.py" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/out.requests.txt" @@ -80,6 +81,7 @@ dist/my_test_code-0.2.0-py3-none-any.whl === Expecting 1 wheel to be uploaded >>> jq .path out.requests.txt "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.2.0-py3-none-any.whl" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/dist/my_test_code-0.2.0-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/my_test_code/__init__.py" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/out.requests.txt" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/output.txt" @@ -127,6 +129,7 @@ dist/my_test_code-0.2.0-py3-none-any.whl "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.1.0-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.2.0-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/databricks.yml" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/dist/my_test_code-0.2.0-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/out.requests.txt" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/output.txt" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock" diff --git a/acceptance/bundle/artifacts/whl_implicit/output.txt b/acceptance/bundle/artifacts/whl_implicit/output.txt index ed70f1c0b17..698cc20c514 100644 --- a/acceptance/bundle/artifacts/whl_implicit/output.txt +++ b/acceptance/bundle/artifacts/whl_implicit/output.txt @@ -31,3 +31,4 @@ dist/my_test_code-0.0.1-py3-none-any.whl === Expecting 1 wheels to be uploaded >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1-py3-none-any.whl" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/dist/my_test_code-0.0.1-py3-none-any.whl" diff --git a/acceptance/bundle/artifacts/whl_implicit_notebook/output.txt b/acceptance/bundle/artifacts/whl_implicit_notebook/output.txt index b2f6c9a562a..c60adc515eb 100644 --- a/acceptance/bundle/artifacts/whl_implicit_notebook/output.txt +++ b/acceptance/bundle/artifacts/whl_implicit_notebook/output.txt @@ -30,3 +30,4 @@ dist/my_test_code-0.0.1-py3-none-any.whl === Expecting 1 wheel to be uploaded >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1-py3-none-any.whl" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/dist/my_test_code-0.0.1-py3-none-any.whl" diff --git a/acceptance/bundle/artifacts/whl_no_cleanup/output.txt b/acceptance/bundle/artifacts/whl_no_cleanup/output.txt index 4f2ee6f3efc..a78810dbca9 100644 --- a/acceptance/bundle/artifacts/whl_no_cleanup/output.txt +++ b/acceptance/bundle/artifacts/whl_no_cleanup/output.txt @@ -13,6 +13,7 @@ dist/my_test_code-0.0.1-py3-none-any.whl === Expecting 1 wheels to be uploaded >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1-py3-none-any.whl" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/dist/my_test_code-0.0.1-py3-none-any.whl" >>> [CLI] bundle deploy Building python_artifact... diff --git a/acceptance/bundle/artifacts/whl_prebuilt_multiple/output.txt b/acceptance/bundle/artifacts/whl_prebuilt_multiple/output.txt index 0d9899b24c3..4fe390e6350 100644 --- a/acceptance/bundle/artifacts/whl_prebuilt_multiple/output.txt +++ b/acceptance/bundle/artifacts/whl_prebuilt_multiple/output.txt @@ -41,3 +41,5 @@ dist/my_test_code-0.0.1-py3-none-any.whl >>> jq .path "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/my_test_code-0.0.1-py3-none-any.whl" "/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts/.internal/other_test_code-0.0.1-py3-none-any.whl" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/dist/lib/other_test_code-0.0.1-py3-none-any.whl" +"/api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/dist/my_test_code-0.0.1-py3-none-any.whl" diff --git a/bundle/deploy/files/sync.go b/bundle/deploy/files/sync.go index 876039cb037..a3ead13a4e3 100644 --- a/bundle/deploy/files/sync.go +++ b/bundle/deploy/files/sync.go @@ -3,7 +3,6 @@ package files import ( "context" "fmt" - "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/sync" @@ -17,12 +16,6 @@ func GetSync(ctx context.Context, b *bundle.Bundle) (*sync.Sync, error) { return sync.New(ctx, *opts) } -var defaultExcludes = []string{ - "__pycache__/", - "^build/", - "^dist/", -} - func GetSyncOptions(ctx context.Context, b *bundle.Bundle) (*sync.SyncOptions, error) { cacheDir, err := b.CacheDir(ctx) if err != nil { @@ -34,30 +27,12 @@ func GetSyncOptions(ctx context.Context, b *bundle.Bundle) (*sync.SyncOptions, e return nil, fmt.Errorf("cannot get list of sync includes: %w", err) } - // We used to delete __pycache__ and build and most of the dist, so now we're excluding it manually - - var excludes []string - - for _, defaultExclude := range defaultExcludes { - if slices.Contains(includes, defaultExclude) { - continue - } - if slices.Contains(b.Config.Sync.Exclude, defaultExclude) { - continue - } - excludes = append(excludes, defaultExclude) - } - - excludes = append(b.Config.Sync.Exclude, excludes...) - - // TODO: if users include those manually, then we should not exclude it? - opts := &sync.SyncOptions{ WorktreeRoot: b.WorktreeRoot, LocalRoot: b.SyncRoot, Paths: b.Config.Sync.Paths, Include: includes, - Exclude: excludes, + Exclude: b.Config.Sync.Exclude, RemotePath: b.Config.Workspace.FilePath, Host: b.WorkspaceClient().Config.Host, From 670bd5d8c9a91106931d3ee3e11c1e5b2472fdf2 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 5 Jun 2025 11:50:27 +0200 Subject: [PATCH 14/18] revert convertion to abs in upload.go --- bundle/libraries/upload.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/bundle/libraries/upload.go b/bundle/libraries/upload.go index 65fa1d2e71d..d38c7a5a043 100644 --- a/bundle/libraries/upload.go +++ b/bundle/libraries/upload.go @@ -73,10 +73,7 @@ func collectLocalLibraries(b *bundle.Bundle) (map[string][]configLocation, error return v, nil } - if !filepath.IsAbs(source) { - source = filepath.Join(b.SyncRootPath, source) - } - + source = filepath.Join(b.SyncRootPath, source) libs[source] = append(libs[source], configLocation{ configPath: p, location: v.Location(), @@ -121,12 +118,6 @@ func collectLocalLibraries(b *bundle.Bundle) (map[string][]configLocation, error } } - // Need to convert relative path to absolute so that paths collected above match paths collected here - // artifacts.*.files[*].source are relative to bundle root, not sync root AFAIK - if !filepath.IsAbs(source) { - source = filepath.Join(b.BundleRootPath, source) - } - libs[source] = append(libs[source], configLocation{ configPath: p.Append(dyn.Key("remote_path")), location: v.Location(), From b7518f63568f1faae582368d56c262f5361b1dff Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 5 Jun 2025 12:02:12 +0200 Subject: [PATCH 15/18] update NEXT_CHANGELOG --- NEXT_CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 49340c6c822..aa95242abe5 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -10,5 +10,7 @@ ### Bundles * Fix reading dashboard contents when the sync root is different than the bundle root ([#3006](https://github.com/databricks/cli/pull/3006)) +* When glob for wheels is used, like "\*.whl", it will filter out different version of the same package and will only take the most recent version. ([#2982](https://github.com/databricks/cli/pull/2982)) +* When building Python artifacts as part of "bundle deploy" we no longer delete "dist", "build", "\*egg-info" and __pycache__ directories. ([#2982](https://github.com/databricks/cli/pull/2982)) ### API Changes From c0d9c66948e64b7d21a635c26a93449191e98258 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 5 Jun 2025 14:55:49 +0200 Subject: [PATCH 16/18] formatting --- NEXT_CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index aa95242abe5..186a1872681 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -11,6 +11,6 @@ ### Bundles * Fix reading dashboard contents when the sync root is different than the bundle root ([#3006](https://github.com/databricks/cli/pull/3006)) * When glob for wheels is used, like "\*.whl", it will filter out different version of the same package and will only take the most recent version. ([#2982](https://github.com/databricks/cli/pull/2982)) -* When building Python artifacts as part of "bundle deploy" we no longer delete "dist", "build", "\*egg-info" and __pycache__ directories. ([#2982](https://github.com/databricks/cli/pull/2982)) +* When building Python artifacts as part of "bundle deploy" we no longer delete `dist`, `build`, `*egg-info` and `__pycache__` directories. ([#2982](https://github.com/databricks/cli/pull/2982)) ### API Changes From 4f46907dbd0928cbfc27c893e8ac41286622647e Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 12 Jun 2025 11:30:29 +0200 Subject: [PATCH 17/18] fix formatting --- acceptance/bundle/artifacts/whl_change_version/databricks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/bundle/artifacts/whl_change_version/databricks.yml b/acceptance/bundle/artifacts/whl_change_version/databricks.yml index 61019c57bbb..5a8d55ded01 100644 --- a/acceptance/bundle/artifacts/whl_change_version/databricks.yml +++ b/acceptance/bundle/artifacts/whl_change_version/databricks.yml @@ -9,4 +9,4 @@ resources: package_name: "my_test_code" entry_point: "run" libraries: - - whl: ./dist/*.whl + - whl: ./dist/*.whl From 00b560a4ae9cee0d85c12c517d3ac3b0d56f7f4d Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 12 Jun 2025 12:04:16 +0200 Subject: [PATCH 18/18] clean up newline --- libs/patchwheel/filter_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/patchwheel/filter_test.go b/libs/patchwheel/filter_test.go index a3faa97969f..2ed2af831b6 100644 --- a/libs/patchwheel/filter_test.go +++ b/libs/patchwheel/filter_test.go @@ -17,7 +17,6 @@ func TestFilterLatestWheels(t *testing.T) { "other-0.9.0-py3-none-any.whl", "project_name_bvs7tide6bhhpjy4dmcsb2qg44-0.0.1+20250604.74804-py3-none-any.whl", "not-a-wheel.whl", - "hello-1.2.3-py3-none-any.whl", "hello-1.2.3+1741091696780123321-py3-none-any.whl", }