usearch 0.22.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/.github/workflows/prerelease.yml +3 -3
  2. package/.releaserc +9 -0
  3. package/.vscode/launch.json +15 -11
  4. package/.vscode/settings.json +12 -2
  5. package/.vscode/tasks.json +4 -4
  6. package/CMakeLists.txt +6 -1
  7. package/Cargo.toml +2 -2
  8. package/README.md +39 -46
  9. package/build.rs +2 -2
  10. package/c/CMakeLists.txt +48 -0
  11. package/c/README.md +34 -0
  12. package/c/lib.cpp +83 -72
  13. package/c/test.c +20 -20
  14. package/c/usearch.h +132 -32
  15. package/conanfile.py +1 -1
  16. package/cpp/CMakeLists.txt +3 -1
  17. package/cpp/README.md +16 -16
  18. package/cpp/bench.cpp +50 -126
  19. package/cpp/test.cpp +164 -77
  20. package/docs/benchmarks.md +26 -5
  21. package/docs/c/reference.rst +3 -3
  22. package/docs/compilation.md +49 -45
  23. package/docs/cpp/index.rst +2 -2
  24. package/docs/cpp/reference.rst +13 -3
  25. package/docs/index.rst +8 -8
  26. package/docs/java/reference.rst +1 -1
  27. package/golang/lib.go +275 -141
  28. package/golang/lib_test.go +105 -0
  29. package/include/usearch/index.hpp +1733 -1659
  30. package/include/usearch/index_dense.hpp +1400 -0
  31. package/include/usearch/index_plugins.hpp +1263 -0
  32. package/java/README.md +1 -1
  33. package/java/cloud/unum/usearch/Index.java +10 -10
  34. package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +34 -37
  35. package/java/test/IndexTest.java +1 -1
  36. package/javascript/README.md +7 -7
  37. package/javascript/lib.cpp +94 -55
  38. package/javascript/test.js +25 -11
  39. package/javascript/usearch.d.ts +87 -0
  40. package/javascript/usearch.js +2 -118
  41. package/objc/USearchObjective.mm +57 -31
  42. package/objc/include/USearchObjective.h +32 -22
  43. package/package.json +5 -3
  44. package/pyproject.toml +3 -3
  45. package/python/README.md +23 -14
  46. package/python/lib.cpp +472 -444
  47. package/python/scripts/bench.ipynb +3 -2
  48. package/python/scripts/bench.py +4 -4
  49. package/python/scripts/index_faiss.py +5 -5
  50. package/python/scripts/join.py +4 -4
  51. package/python/scripts/test.py +152 -61
  52. package/python/scripts/test_jit.py +10 -12
  53. package/python/scripts/test_sparse.py +32 -0
  54. package/python/usearch/client.py +26 -28
  55. package/python/usearch/eval.py +73 -23
  56. package/python/usearch/index.py +520 -228
  57. package/python/usearch/io.py +57 -25
  58. package/python/usearch/numba.py +4 -4
  59. package/python/usearch/server.py +10 -10
  60. package/rust/lib.cpp +22 -27
  61. package/rust/lib.hpp +11 -12
  62. package/rust/lib.rs +3 -3
  63. package/setup.py +5 -2
  64. package/simsimd/.github/workflows/prerelease.yml +73 -0
  65. package/simsimd/.github/workflows/release.yml +110 -0
  66. package/simsimd/.github/workflows/update_version.sh +3 -0
  67. package/simsimd/.releaserc +77 -0
  68. package/simsimd/.vscode/launch.json +27 -1
  69. package/simsimd/.vscode/settings.json +22 -1
  70. package/simsimd/.vscode/tasks.json +6 -0
  71. package/simsimd/CMakeLists.txt +3 -2
  72. package/simsimd/README.md +82 -46
  73. package/simsimd/bench.cxx +63 -24
  74. package/simsimd/include/simsimd/simsimd.h +88 -66
  75. package/simsimd/include/simsimd/simsimd_chem.h +126 -0
  76. package/simsimd/package.json +8 -0
  77. package/simsimd/pyproject.toml +42 -0
  78. package/simsimd/python/python.c +81 -0
  79. package/simsimd/python/run.sh +1 -0
  80. package/simsimd/python/test.c +109 -0
  81. package/simsimd/python/test.py +23 -0
  82. package/simsimd/setup.py +44 -0
  83. package/swift/Index+Sugar.swift +75 -23
  84. package/swift/README.md +26 -2
  85. package/swift/Test.swift +19 -7
  86. package/swift/USearch.swift +2 -2
  87. package/tsconfig.json +8 -0
  88. package/wasm/CMakeLists.txt +1 -0
  89. package/wasmer.toml +5 -1
  90. package/wolfram/CMakeLists.txt +1 -0
  91. package/wolfram/lib.cpp +11 -13
  92. package/golang/usearch.h +0 -9
  93. package/golang/usearch_par_test.go +0 -40
  94. package/golang/usearch_test.go +0 -82
  95. package/include/usearch/index_punned_dense.hpp +0 -871
  96. package/include/usearch/index_punned_helpers.hpp +0 -566
@@ -1,11 +1,48 @@
1
+ import os
1
2
  import struct
3
+ import typing
2
4
 
3
5
  import numpy as np
4
6
 
5
7
 
8
+ def numpy_scalar_size(dtype) -> int:
9
+ return {
10
+ np.float64: 8,
11
+ np.int64: 8,
12
+ np.uint64: 8,
13
+ np.float32: 4,
14
+ np.int32: 4,
15
+ np.uint32: 4,
16
+ np.float16: 2,
17
+ np.int16: 2,
18
+ np.uint16: 2,
19
+ np.int8: 1,
20
+ np.uint8: 1,
21
+ }[dtype]
22
+
23
+
24
+ def guess_numpy_dtype_from_filename(filename) -> typing.Optional[type]:
25
+ if filename.endswith(".fbin"):
26
+ return np.float32
27
+ elif filename.endswith(".dbin"):
28
+ return np.float64
29
+ elif filename.endswith(".hbin"):
30
+ return np.float16
31
+ elif filename.endswith(".ibin"):
32
+ return np.int32
33
+ elif filename.endswith(".bbin"):
34
+ return np.uint8
35
+ else:
36
+ return None
37
+
38
+
6
39
  def load_matrix(
7
- filename: str, start_row: int = 0, count_rows: int = None, view: bool = False
8
- ) -> np.ndarray:
40
+ filename: str,
41
+ start_row: int = 0,
42
+ count_rows: int = None,
43
+ view: bool = False,
44
+ dtype: typing.Optional[type] = None,
45
+ ) -> typing.Optional[np.ndarray]:
9
46
  """Read *.ibin, *.bbib, *.hbin, *.fbin, *.dbin files with matrices.
10
47
 
11
48
  :param filename: path to the matrix file
@@ -16,25 +53,14 @@ def load_matrix(
16
53
  :return: parsed matrix
17
54
  :rtype: numpy.ndarray
18
55
  """
19
- dtype = np.float32
20
- scalar_size = 4
21
- if filename.endswith(".fbin"):
22
- dtype = np.float32
23
- scalar_size = 4
24
- elif filename.endswith(".dbin"):
25
- dtype = np.float64
26
- scalar_size = 8
27
- elif filename.endswith(".hbin"):
28
- dtype = np.float16
29
- scalar_size = 2
30
- elif filename.endswith(".ibin"):
31
- dtype = np.int32
32
- scalar_size = 4
33
- elif filename.endswith(".bbin"):
34
- dtype = np.uint8
35
- scalar_size = 1
36
- else:
37
- raise Exception("Unknown file type")
56
+ if dtype is None:
57
+ dtype = guess_numpy_dtype_from_filename(filename)
58
+ if dtype is None:
59
+ raise Exception("Unknown file type")
60
+ scalar_size = numpy_scalar_size(dtype)
61
+
62
+ if not os.path.exists(filename):
63
+ return None
38
64
 
39
65
  with open(filename, "rb") as f:
40
66
  rows, cols = np.fromfile(f, count=2, dtype=np.int32)
@@ -43,11 +69,18 @@ def load_matrix(
43
69
 
44
70
  if view:
45
71
  return np.memmap(
46
- f, dtype=dtype, mode="r", offset=8 + row_offset, shape=(rows, cols)
72
+ f,
73
+ dtype=dtype,
74
+ mode="r",
75
+ offset=8 + row_offset,
76
+ shape=(rows, cols),
47
77
  )
48
78
  else:
49
79
  return np.fromfile(
50
- f, count=rows * cols, dtype=dtype, offset=row_offset
80
+ f,
81
+ count=rows * cols,
82
+ dtype=dtype,
83
+ offset=row_offset,
51
84
  ).reshape(rows, cols)
52
85
 
53
86
 
@@ -59,7 +92,6 @@ def save_matrix(vectors: np.ndarray, filename: str):
59
92
  :param filename: path to the matrix file
60
93
  :type filename: str
61
94
  """
62
- dtype = np.float32
63
95
  if filename.endswith(".fbin"):
64
96
  dtype = np.float32
65
97
  elif filename.endswith(".dbin"):
@@ -71,7 +103,7 @@ def save_matrix(vectors: np.ndarray, filename: str):
71
103
  elif filename.endswith(".bbin"):
72
104
  dtype = np.uint8
73
105
  else:
74
- raise Exception("Unknown file type")
106
+ dtype = vectors.dtype
75
107
 
76
108
  assert len(vectors.shape) == 2, "Input array must have 2 dimensions"
77
109
  with open(filename, "wb") as f:
@@ -44,7 +44,7 @@ def jit(
44
44
  )
45
45
 
46
46
  numba_supported_types = (
47
- ScalarKind.F8,
47
+ ScalarKind.I8,
48
48
  # Half-precision is still unsupported
49
49
  # https://github.com/numba/numba/issues/4402
50
50
  # ScalarKind.F16: np.float16,
@@ -55,7 +55,7 @@ def jit(
55
55
  return metric
56
56
 
57
57
  scalar_kind_to_accumulator_type = {
58
- ScalarKind.F8: types.int32,
58
+ ScalarKind.I8: types.int32,
59
59
  ScalarKind.F16: types.float16,
60
60
  ScalarKind.F32: types.float32,
61
61
  ScalarKind.F64: types.float64,
@@ -98,7 +98,7 @@ def jit(
98
98
  return types.float32(ab_delta_sq)
99
99
 
100
100
  scalar_kind_to_signature = {
101
- ScalarKind.F8: signature_i8args,
101
+ ScalarKind.I8: signature_i8args,
102
102
  ScalarKind.F16: signature_f16args,
103
103
  ScalarKind.F32: signature_f32args,
104
104
  ScalarKind.F64: signature_f64args,
@@ -110,7 +110,7 @@ def jit(
110
110
  MetricKind.L2sq: numba_l2sq,
111
111
  }
112
112
 
113
- if dtype == ScalarKind.F8 and metric == MetricKind.IP:
113
+ if dtype == ScalarKind.I8 and metric == MetricKind.IP:
114
114
  metric = MetricKind.Cos
115
115
 
116
116
  pointer = cfunc(scalar_kind_to_signature[dtype])(metric_kind_to_function[metric])
@@ -7,13 +7,13 @@ import numpy as np
7
7
  from typing import List
8
8
 
9
9
  from ucall.rich_posix import Server
10
- from usearch.index import Index, Matches, Label
10
+ from usearch.index import Index, Matches, Key
11
11
 
12
12
 
13
13
  def _ascii_to_vector(string: str) -> np.ndarray:
14
14
  """
15
15
  WARNING: A dirty performance hack!
16
- Assuming the `f8` vectors in our implementations are just integers,
16
+ Assuming the `i8` vectors in our implementations are just integers,
17
17
  and generally contain scalars in the [0, 100] range, we can transmit
18
18
  them as JSON-embedded strings. The only symbols we must avoid are
19
19
  the double-quote '"' (code 22) and backslash '\' (code 60).
@@ -59,15 +59,15 @@ def serve(
59
59
  return index.connectivity()
60
60
 
61
61
  @server
62
- def add_one(label: int, vector: np.ndarray):
63
- print("adding", label, vector)
64
- labels = np.array([label], dtype=Label)
62
+ def add_one(key: int, vector: np.ndarray):
63
+ print("adding", key, vector)
64
+ keys = np.array([key], dtype=Key)
65
65
  vectors = vector.flatten().reshape(vector.shape[0], 1)
66
- index.add(labels, vectors)
66
+ index.add(keys, vectors)
67
67
 
68
68
  @server
69
- def add_many(labels: np.ndarray, vectors: np.ndarray):
70
- index.add(labels, vectors, threads=threads)
69
+ def add_many(keys: np.ndarray, vectors: np.ndarray):
70
+ index.add(keys, vectors, threads=threads)
71
71
 
72
72
  @server
73
73
  def search_one(vector: np.ndarray, count: int) -> List[dict]:
@@ -82,8 +82,8 @@ def serve(
82
82
  return results.to_list()
83
83
 
84
84
  @server
85
- def add_ascii(label: int, string: str):
86
- return add_one(label, _ascii_to_vector(string))
85
+ def add_ascii(key: int, string: str):
86
+ return add_one(key, _ascii_to_vector(string))
87
87
 
88
88
  @server
89
89
  def search_ascii(string: str, count: int):
package/rust/lib.cpp CHANGED
@@ -7,46 +7,45 @@ using namespace unum;
7
7
  using index_t = typename Index::index_t;
8
8
  using add_result_t = typename index_t::add_result_t;
9
9
  using search_result_t = typename index_t::search_result_t;
10
- using serialization_result_t = typename index_t::serialization_result_t;
11
10
 
12
11
  Index::Index(std::unique_ptr<index_t> index) : index_(std::move(index)) {}
13
12
 
14
- void Index::add_in_thread(label_t label, rust::Slice<float const> vector, size_t thread) const {
15
- add_config_t config;
13
+ void Index::add_in_thread(key_t key, rust::Slice<float const> vector, size_t thread) const {
14
+ index_update_config_t config;
16
15
  config.thread = thread;
17
- index_->add(label, vector.data(), config).error.raise();
16
+ config.expansion = index_->expansion_add();
17
+ index_->add(key, vector.data(), config).error.raise();
18
18
  }
19
19
 
20
20
  Matches Index::search_in_thread(rust::Slice<float const> vector, size_t count, size_t thread) const {
21
21
  Matches matches;
22
- matches.labels.reserve(count);
22
+ matches.keys.reserve(count);
23
23
  matches.distances.reserve(count);
24
24
  for (size_t i = 0; i != count; ++i)
25
- matches.labels.push_back(0), matches.distances.push_back(0);
26
- search_config_t config;
25
+ matches.keys.push_back(0), matches.distances.push_back(0);
26
+ index_search_config_t config;
27
27
  config.thread = thread;
28
+ config.expansion = index_->expansion_search();
28
29
  search_result_t result = index_->search(vector.data(), count, config);
29
30
  result.error.raise();
30
- matches.count = result.dump_to(matches.labels.data(), matches.distances.data());
31
- matches.labels.truncate(matches.count);
31
+ matches.count = result.dump_to(matches.keys.data(), matches.distances.data());
32
+ matches.keys.truncate(matches.count);
32
33
  matches.distances.truncate(matches.count);
33
34
  return matches;
34
35
  }
35
36
 
36
- void Index::add(label_t label, rust::Slice<float const> vector) const {
37
- index_->add(label, vector.data()).error.raise();
38
- }
37
+ void Index::add(key_t key, rust::Slice<float const> vector) const { index_->add(key, vector.data()).error.raise(); }
39
38
 
40
39
  Matches Index::search(rust::Slice<float const> vector, size_t count) const {
41
40
  Matches matches;
42
- matches.labels.reserve(count);
41
+ matches.keys.reserve(count);
43
42
  matches.distances.reserve(count);
44
43
  for (size_t i = 0; i != count; ++i)
45
- matches.labels.push_back(0), matches.distances.push_back(0);
44
+ matches.keys.push_back(0), matches.distances.push_back(0);
46
45
  search_result_t result = index_->search(vector.data(), count);
47
46
  result.error.raise();
48
- matches.count = result.dump_to(matches.labels.data(), matches.distances.data());
49
- matches.labels.truncate(matches.count);
47
+ matches.count = result.dump_to(matches.keys.data(), matches.distances.data());
48
+ matches.keys.truncate(matches.count);
50
49
  matches.distances.truncate(matches.count);
51
50
  return matches;
52
51
  }
@@ -62,7 +61,7 @@ void Index::save(rust::Str path) const { index_->save(std::string(path).c_str())
62
61
  void Index::load(rust::Str path) const { index_->load(std::string(path).c_str()); }
63
62
  void Index::view(rust::Str path) const { index_->view(std::string(path).c_str()); }
64
63
 
65
- scalar_kind_t accuracy(rust::Str quant) { return scalar_kind_from_name(quant.data(), quant.size()); }
64
+ scalar_kind_t quantization(rust::Str quant) { return scalar_kind_from_name(quant.data(), quant.size()); }
66
65
 
67
66
  std::unique_ptr<Index> wrap(index_t&& index) {
68
67
  std::unique_ptr<index_t> punned_ptr;
@@ -72,12 +71,6 @@ std::unique_ptr<Index> wrap(index_t&& index) {
72
71
  return result;
73
72
  }
74
73
 
75
- index_config_t config(size_t connectivity) {
76
- index_config_t result;
77
- result.connectivity = connectivity ? connectivity : default_connectivity();
78
- return result;
79
- }
80
-
81
74
  metric_kind_t rust_to_cpp_metric(MetricKind value) {
82
75
  switch (value) {
83
76
  case MetricKind::IP: return metric_kind_t::ip_k;
@@ -94,7 +87,7 @@ metric_kind_t rust_to_cpp_metric(MetricKind value) {
94
87
 
95
88
  scalar_kind_t rust_to_cpp_scalar(ScalarKind value) {
96
89
  switch (value) {
97
- case ScalarKind::F8: return scalar_kind_t::f8_k;
90
+ case ScalarKind::I8: return scalar_kind_t::i8_k;
98
91
  case ScalarKind::F16: return scalar_kind_t::f16_k;
99
92
  case ScalarKind::F32: return scalar_kind_t::f32_k;
100
93
  case ScalarKind::F64: return scalar_kind_t::f64_k;
@@ -104,7 +97,9 @@ scalar_kind_t rust_to_cpp_scalar(ScalarKind value) {
104
97
  }
105
98
 
106
99
  std::unique_ptr<Index> new_index(IndexOptions const& options) {
107
- return wrap(index_t::make(options.dimensions, rust_to_cpp_metric(options.metric), config(options.connectivity),
108
- rust_to_cpp_scalar(options.quantization), options.expansion_add,
109
- options.expansion_search));
100
+ metric_kind_t metric_kind = rust_to_cpp_metric(options.metric);
101
+ scalar_kind_t scalar_kind = rust_to_cpp_scalar(options.quantization);
102
+ metric_punned_t metric(options.dimensions, metric_kind, scalar_kind);
103
+ index_dense_config_t config(options.connectivity, options.expansion_add, options.expansion_search);
104
+ return wrap(index_t::make(metric, config));
110
105
  }
package/rust/lib.hpp CHANGED
@@ -3,27 +3,26 @@
3
3
 
4
4
  #include <memory> // `std::shared_ptr`
5
5
 
6
- #include <usearch/index_punned_dense.hpp>
6
+ #include <usearch/index_dense.hpp>
7
7
 
8
8
  struct Matches;
9
9
  struct IndexOptions;
10
10
 
11
11
  class Index {
12
12
  public:
13
- using metric_t = unum::usearch::index_punned_dense_metric_t;
14
- using distance_t = unum::usearch::punned_distance_t;
15
- using index_t = unum::usearch::punned_small_t;
16
- using add_result_t = typename index_t::add_result_t;
17
- using search_result_t = typename index_t::search_result_t;
18
- using label_t = typename index_t::label_t;
19
- using id_t = typename index_t::id_t;
13
+ using metric_t = unum::usearch::metric_punned_t;
14
+ using distance_t = unum::usearch::distance_punned_t;
15
+ using index_dense_t = unum::usearch::index_dense_t;
16
+ using add_result_t = typename index_dense_t::add_result_t;
17
+ using search_result_t = typename index_dense_t::search_result_t;
18
+ using key_t = typename index_dense_t::key_t;
20
19
 
21
- Index(std::unique_ptr<index_t> index);
20
+ Index(std::unique_ptr<index_dense_t> index);
22
21
 
23
22
  void reserve(size_t) const;
24
23
 
25
- void add(label_t label, rust::Slice<float const> vector) const;
26
- void add_in_thread(label_t label, rust::Slice<float const> vector, size_t thread) const;
24
+ void add(key_t key, rust::Slice<float const> vector) const;
25
+ void add_in_thread(key_t key, rust::Slice<float const> vector, size_t thread) const;
27
26
 
28
27
  Matches search(rust::Slice<float const> vector, size_t count) const;
29
28
  Matches search_in_thread(rust::Slice<float const> vector, size_t count, size_t thread) const;
@@ -38,7 +37,7 @@ class Index {
38
37
  void view(rust::Str path) const;
39
38
 
40
39
  private:
41
- std::unique_ptr<index_t> index_;
40
+ std::unique_ptr<index_dense_t> index_;
42
41
  };
43
42
 
44
43
  std::unique_ptr<Index> new_index(IndexOptions const& options);
package/rust/lib.rs CHANGED
@@ -4,7 +4,7 @@ pub mod ffi {
4
4
  // Shared structs with fields visible to both languages.
5
5
  struct Matches {
6
6
  count: usize,
7
- labels: Vec<u64>,
7
+ keys: Vec<u64>,
8
8
  distances: Vec<f32>,
9
9
  }
10
10
 
@@ -23,7 +23,7 @@ pub mod ffi {
23
23
  F64,
24
24
  F32,
25
25
  F16,
26
- F8,
26
+ I8,
27
27
  B1,
28
28
  }
29
29
 
@@ -51,7 +51,7 @@ pub mod ffi {
51
51
  pub fn size(self: &Index) -> usize;
52
52
  pub fn capacity(self: &Index) -> usize;
53
53
 
54
- pub fn add(self: &Index, label: u64, vector: &[f32]) -> Result<()>;
54
+ pub fn add(self: &Index, key: u64, vector: &[f32]) -> Result<()>;
55
55
  pub fn search(self: &Index, query: &[f32], count: usize) -> Result<Matches>;
56
56
 
57
57
  pub fn save(self: &Index, path: &str) -> Result<()>;
package/setup.py CHANGED
@@ -14,7 +14,8 @@ macros_args = [
14
14
 
15
15
  if sys.platform == "linux":
16
16
  compile_args.append("-std=c++11")
17
- compile_args.append("-O3")
17
+ compile_args.append("-O3") # Maximize performance
18
+ compile_args.append("-g") # Simplify debugging
18
19
  compile_args.append("-Wno-unknown-pragmas")
19
20
 
20
21
  macros_args.append(("USEARCH_USE_OPENMP", "1"))
@@ -26,7 +27,8 @@ if sys.platform == "darwin":
26
27
  # https://github.com/unum-cloud/usearch/actions/runs/4975434891/jobs/8902603392
27
28
  compile_args.append("-mmacosx-version-min=10.15")
28
29
  compile_args.append("-std=c++11")
29
- compile_args.append("-O3")
30
+ compile_args.append("-O3") # Maximize performance
31
+ compile_args.append("-g") # Simplify debugging
30
32
  compile_args.append("-Wno-unknown-pragmas")
31
33
 
32
34
  # Linking OpenMP requires additional preparion in CIBuildWheel
@@ -63,6 +65,7 @@ setup(
63
65
  packages=["usearch"],
64
66
  package_dir={"usearch": "python/usearch"},
65
67
  description="Smaller & Faster Single-File Vector Search Engine from Unum",
68
+ author="Ash Vardanian",
66
69
  long_description=long_description,
67
70
  long_description_content_type="text/markdown",
68
71
  license="Apache-2.0",
@@ -0,0 +1,73 @@
1
+ name: Pre-Release
2
+
3
+ on:
4
+ push:
5
+ branches: ["main-dev"]
6
+ pull_request:
7
+ branches: ["main-dev"]
8
+
9
+ env:
10
+ BUILD_TYPE: Release
11
+ GH_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }}
12
+ PYTHONUTF8: 1
13
+
14
+ # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+
20
+ test_python_311:
21
+ name: Test Python
22
+ runs-on: ${{ matrix.os }}
23
+ strategy:
24
+ matrix:
25
+ os: [ubuntu-20.04, macOS-11, windows-2022]
26
+ python-version: ["3.11"]
27
+
28
+ steps:
29
+ - uses: actions/checkout@v3
30
+ - run: git submodule update --init --recursive
31
+ - name: Set up Python ${{ matrix.python-version }}
32
+ uses: actions/setup-python@v4
33
+ with:
34
+ python-version: ${{ matrix.python-version }}
35
+ - name: Install dependencies
36
+ run: |
37
+ python -m pip install --no-cache-dir --upgrade pip
38
+ pip install --no-cache-dir pytest
39
+ - name: Build locally
40
+ run: python -m pip install .
41
+ - name: Test with PyTest
42
+ run: pytest python/test.py
43
+
44
+
45
+ test_python_37:
46
+ name: Test Python 3.7
47
+ runs-on: ${{ matrix.os }}
48
+ strategy:
49
+ matrix:
50
+ os: [ubuntu-20.04]
51
+ python-version: ["3.7"]
52
+
53
+ steps:
54
+ - uses: actions/checkout@v3
55
+ - run: git submodule update --init --recursive
56
+
57
+ - name: Set up Python ${{ matrix.python-version }}
58
+ uses: actions/setup-python@v4
59
+ with:
60
+ python-version: ${{ matrix.python-version }}
61
+
62
+ - name: Install dependencies
63
+ run: |
64
+ python -m pip install --no-cache-dir --upgrade pip
65
+ pip install --no-cache-dir pytest
66
+
67
+ - name: Build locally
68
+ run: python -m pip install .
69
+
70
+ - name: Test with PyTest
71
+ run: pytest python/test.py
72
+
73
+
@@ -0,0 +1,110 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+
7
+ env:
8
+ BUILD_TYPE: Release
9
+ GH_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }}
10
+ PYTHONUTF8: 1
11
+
12
+ # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13
+ permissions:
14
+ contents: write
15
+ pages: write
16
+ id-token: write
17
+
18
+ jobs:
19
+
20
+ versioning:
21
+ name: Semantic Release
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v3
25
+ with:
26
+ persist-credentials: false
27
+ - uses: actions/setup-node@v3
28
+ - run: npm install --save-dev @semantic-release/exec @semantic-release/git conventional-changelog-eslint semantic-release && npx semantic-release
29
+
30
+
31
+ rebase:
32
+ name: Rebase Development Branch
33
+ needs: versioning
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - name: Checkout the latest code
37
+ uses: actions/checkout@v3
38
+ with:
39
+ fetch-depth: 0
40
+
41
+ - name: Perform rebase
42
+ run: |
43
+ git checkout main-dev
44
+ git rebase main
45
+
46
+ - name: Push changes
47
+ uses: CasperWA/push-protected@v2
48
+ with:
49
+ token: ${{ secrets.SEMANTIC_REBASE_TOKEN }}
50
+ branch: main-dev
51
+ unprotect_reviews: True
52
+ force: True
53
+
54
+ build_wheels:
55
+ name: Build Wheels for ${{ matrix.os }}
56
+ runs-on: ${{ matrix.os }}
57
+ needs: versioning
58
+ strategy:
59
+ matrix:
60
+ os: [ubuntu-20.04, macOS-11, windows-2022]
61
+
62
+ steps:
63
+ - uses: actions/checkout@v3
64
+ with:
65
+ ref: 'main'
66
+ - uses: actions/setup-python@v3
67
+
68
+ - name: Setup Docker
69
+ if: matrix.os != 'windows-2022'
70
+ uses: crazy-max/ghaction-setup-docker@v1.0.0
71
+ with:
72
+ version: 23.0.1
73
+
74
+ - name: Setup QEMU
75
+ if: matrix.os != 'windows-2022'
76
+ uses: docker/setup-qemu-action@v2.1.0
77
+
78
+ - name: Install CIBuildWheel
79
+ run: python -m pip install --no-cache-dir cibuildwheel
80
+
81
+ - name: Build wheels
82
+ run: python -m cibuildwheel
83
+
84
+ - uses: actions/upload-artifact@v3
85
+ with:
86
+ path: ./wheelhouse/*.whl
87
+
88
+
89
+ publish_python:
90
+ name: Publish Python
91
+ needs: build_wheels
92
+ runs-on: ubuntu-20.04
93
+ environment:
94
+ name: pypi
95
+ url: https://pypi.org/p/stringzilla
96
+ permissions:
97
+ id-token: write
98
+
99
+ steps:
100
+ - name: Download artifacts
101
+ uses: actions/download-artifact@v3.0.2
102
+ with:
103
+ path: ./dist/
104
+
105
+ - name: Publish to PyPi
106
+ uses: pypa/gh-action-pypi-publish@release/v1
107
+ with:
108
+ packages-dir: ./dist/artifact
109
+ verbose: true
110
+ print-hash: true
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ echo $1 > VERSION
@@ -0,0 +1,77 @@
1
+ {
2
+ "branches": [
3
+ "main"
4
+ ],
5
+ "debug": true,
6
+ "ci": true,
7
+ "dryRun": false,
8
+ "plugins": [
9
+ [
10
+ "@semantic-release/commit-analyzer",
11
+ {
12
+ "preset": "eslint",
13
+ "releaseRules": [
14
+ {
15
+ "tag": "Add",
16
+ "release": "minor"
17
+ },
18
+ {
19
+ "tag": "Improve",
20
+ "release": "patch"
21
+ },
22
+ {
23
+ "tag": "Make",
24
+ "release": "patch"
25
+ },
26
+ {
27
+ "tag": "Refactor",
28
+ "release": false
29
+ }
30
+ ]
31
+ }
32
+ ],
33
+ [
34
+ "@semantic-release/release-notes-generator",
35
+ {
36
+ "preset": "eslint",
37
+ "releaseRules": [
38
+ {
39
+ "tag": "Add",
40
+ "release": "minor"
41
+ },
42
+ {
43
+ "tag": "Improve",
44
+ "release": "patch"
45
+ },
46
+ {
47
+ "tag": "Make",
48
+ "release": "patch"
49
+ },
50
+ {
51
+ "tag": "Refactor",
52
+ "release": false
53
+ }
54
+ ]
55
+ }
56
+ ],
57
+ "@semantic-release/github",
58
+ [
59
+ "@semantic-release/exec",
60
+ {
61
+ "prepareCmd": "bash .github/workflows/update_version.sh '${nextRelease.version}'"
62
+ }
63
+ ],
64
+ [
65
+ "@semantic-release/git",
66
+ {
67
+ "assets": [
68
+ "VERSION",
69
+ "conanfile.py",
70
+ "package.json",
71
+ "Cargo.toml",
72
+ ],
73
+ "message": "Build: Released ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
74
+ }
75
+ ]
76
+ ]
77
+ }